diff options
109 files changed, 2193 insertions, 936 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index 88c43f984847..cd34f98690d5 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -60,6 +60,10 @@ stubs_defaults { removed_api_file: "api/removed.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/public/api", + }, } stubs_defaults { @@ -74,6 +78,10 @@ stubs_defaults { removed_api_file: "api/system-removed.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system/api", + }, } // The defaults for module_libs comes in two parts - defaults for API checks @@ -93,6 +101,10 @@ stubs_defaults { removed_api_file: "api/module-lib-removed.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/module-lib/api", + }, } stubs_defaults { @@ -113,6 +125,10 @@ stubs_defaults { removed_api_file: "api/removed.txt", }, }, + dist: { + targets: ["sdk", "win_sdk"], + dir: "apistubs/android/system-server/api", + }, } // Empty for now, but a convenient place to add rules for all diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index cb87c6cefef5..3f254c043578 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -563,12 +563,10 @@ public class BlobStoreManager { /** * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that - * the calling app has acquired a lease on using {@link #acquireLease(BlobHandle, int)} or - * one of it's other variants. + * the calling app currently has a lease on. * - * @hide + * @return a list of {@link BlobHandle BlobHandles} that the caller has a lease on. */ - @TestApi @NonNull public List<BlobHandle> getLeasedBlobs() throws IOException { try { diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java index 1a45c4a5b7f6..8bd36a516b12 100644 --- a/apex/statsd/framework/java/android/util/StatsEvent.java +++ b/apex/statsd/framework/java/android/util/StatsEvent.java @@ -188,6 +188,12 @@ public final class StatsEvent { @VisibleForTesting public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000; + /** + * @hide + **/ + @VisibleForTesting + public static final int ERROR_ATOM_ID_INVALID_POSITION = 0x2000; + // Size limits. /** @@ -350,19 +356,32 @@ public final class StatsEvent { mPos = 0; writeTypeId(TYPE_OBJECT); - // Set mPos to after atom id's location in the buffer. - // First 2 elements in the buffer are event timestamp followed by the atom id. - mPos = POS_ATOM_ID + Byte.BYTES + Integer.BYTES; - mPosLastField = 0; - mLastType = 0; + // Write timestamp. + mPos = POS_TIMESTAMP_NS; + writeLong(mTimestampNs); } /** * Sets the atom id for this StatsEvent. + * + * This should be called immediately after StatsEvent.newBuilder() + * and should only be called once. + * Not calling setAtomId will result in ERROR_NO_ATOM_ID. + * Calling setAtomId out of order will result in ERROR_ATOM_ID_INVALID_POSITION. **/ @NonNull public Builder setAtomId(final int atomId) { - mAtomId = atomId; + if (0 == mAtomId) { + mAtomId = atomId; + + if (1 == mNumElements) { // Only timestamp is written so far. + writeInt(atomId); + } else { + // setAtomId called out of order. + mErrorMask |= ERROR_ATOM_ID_INVALID_POSITION; + } + } + return this; } @@ -557,7 +576,7 @@ public final class StatsEvent { public Builder addBooleanAnnotation( final byte annotationId, final boolean value) { // Ensure there's a field written to annotate. - if (0 == mPosLastField) { + if (mNumElements < 2) { mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; @@ -568,6 +587,7 @@ public final class StatsEvent { mCurrentAnnotationCount++; writeAnnotationCount(); } + return this; } @@ -576,7 +596,7 @@ public final class StatsEvent { **/ @NonNull public Builder addIntAnnotation(final byte annotationId, final int value) { - if (0 == mPosLastField) { + if (mNumElements < 2) { mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; @@ -587,6 +607,7 @@ public final class StatsEvent { mCurrentAnnotationCount++; writeAnnotationCount(); } + return this; } @@ -619,19 +640,20 @@ public final class StatsEvent { mErrorMask |= ERROR_TOO_MANY_FIELDS; } - int size = mPos; - mPos = POS_TIMESTAMP_NS; - writeLong(mTimestampNs); - writeInt(mAtomId); if (0 == mErrorMask) { mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements); } else { + // Write atom id and error mask. Overwrite any annotations for atom Id. + mPos = POS_ATOM_ID; + mPos += mBuffer.putByte(mPos, TYPE_INT); + mPos += mBuffer.putInt(mPos, mAtomId); mPos += mBuffer.putByte(mPos, TYPE_ERRORS); mPos += mBuffer.putInt(mPos, mErrorMask); mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3); - size = mPos; } + final int size = mPos; + if (mUsePooledBuffer) { return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size); } else { diff --git a/apex/statsd/framework/test/src/android/util/StatsEventTest.java b/apex/statsd/framework/test/src/android/util/StatsEventTest.java index ac25e2734ac9..7b511553a26f 100644 --- a/apex/statsd/framework/test/src/android/util/StatsEventTest.java +++ b/apex/statsd/framework/test/src/android/util/StatsEventTest.java @@ -85,6 +85,45 @@ public class StatsEventTest { } @Test + public void testOnlyAtomId() { + final int expectedAtomId = 109; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .usePooledBuffer() + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(2); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test public void testIntBooleanIntInt() { final int expectedAtomId = 109; final int field1 = 1; @@ -460,6 +499,151 @@ public class StatsEventTest { statsEvent.release(); } + @Test + public void testAtomIdAnnotations() { + final int expectedAtomId = 109; + final byte atomAnnotationId = 84; + final int atomAnnotationValue = 9; + final int field1 = 1; + final byte field1AnnotationId = 45; + final boolean field1AnnotationValue = false; + final boolean field2 = true; + final byte field2AnnotationId = 1; + final int field2AnnotationValue = 23; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .setAtomId(expectedAtomId) + .addIntAnnotation(atomAnnotationId, atomAnnotationValue) + .writeInt(field1) + .addBooleanAnnotation(field1AnnotationId, field1AnnotationValue) + .writeBoolean(field2) + .addIntAnnotation(field2AnnotationId, field2AnnotationValue) + .usePooledBuffer() + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(4); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + final byte atomIdHeader = buffer.get(); + final int atomIdAnnotationValueCount = atomIdHeader >> 4; + final byte atomIdValueType = (byte) (atomIdHeader & 0x0F); + assertWithMessage("Second element is not atom id") + .that(atomIdValueType).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("Atom id annotation count is wrong") + .that(atomIdAnnotationValueCount).isEqualTo(1); + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + assertWithMessage("Atom id's annotation id is wrong") + .that(buffer.get()).isEqualTo(atomAnnotationId); + assertWithMessage("Atom id's annotation type is wrong") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("Atom id's annotation value is wrong") + .that(buffer.getInt()).isEqualTo(atomAnnotationValue); + + final byte field1Header = buffer.get(); + final int field1AnnotationValueCount = field1Header >> 4; + final byte field1Type = (byte) (field1Header & 0x0F); + assertWithMessage("First field is not Int") + .that(field1Type).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("First field annotation count is wrong") + .that(field1AnnotationValueCount).isEqualTo(1); + assertWithMessage("Incorrect field 1") + .that(buffer.getInt()).isEqualTo(field1); + assertWithMessage("First field's annotation id is wrong") + .that(buffer.get()).isEqualTo(field1AnnotationId); + assertWithMessage("First field's annotation type is wrong") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_BOOLEAN); + assertWithMessage("First field's annotation value is wrong") + .that(buffer.get()).isEqualTo(field1AnnotationValue ? 1 : 0); + + final byte field2Header = buffer.get(); + final int field2AnnotationValueCount = field2Header >> 4; + final byte field2Type = (byte) (field2Header & 0x0F); + assertWithMessage("Second field is not boolean") + .that(field2Type).isEqualTo(StatsEvent.TYPE_BOOLEAN); + assertWithMessage("Second field annotation count is wrong") + .that(field2AnnotationValueCount).isEqualTo(1); + assertWithMessage("Incorrect field 2") + .that(buffer.get()).isEqualTo(field2 ? 1 : 0); + assertWithMessage("Second field's annotation id is wrong") + .that(buffer.get()).isEqualTo(field2AnnotationId); + assertWithMessage("Second field's annotation type is wrong") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + assertWithMessage("Second field's annotation value is wrong") + .that(buffer.getInt()).isEqualTo(field2AnnotationValue); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + + @Test + public void testSetAtomIdNotCalledImmediately() { + final int expectedAtomId = 109; + final int field1 = 25; + final boolean field2 = true; + + final long minTimestamp = SystemClock.elapsedRealtimeNanos(); + final StatsEvent statsEvent = StatsEvent.newBuilder() + .writeInt(field1) + .setAtomId(expectedAtomId) + .writeBoolean(field2) + .usePooledBuffer() + .build(); + final long maxTimestamp = SystemClock.elapsedRealtimeNanos(); + + assertThat(statsEvent.getAtomId()).isEqualTo(expectedAtomId); + + final ByteBuffer buffer = + ByteBuffer.wrap(statsEvent.getBytes()).order(ByteOrder.LITTLE_ENDIAN); + + assertWithMessage("Root element in buffer is not TYPE_OBJECT") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_OBJECT); + + assertWithMessage("Incorrect number of elements in root object") + .that(buffer.get()).isEqualTo(3); + + assertWithMessage("First element is not timestamp") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_LONG); + + assertWithMessage("Incorrect timestamp") + .that(buffer.getLong()).isIn(Range.closed(minTimestamp, maxTimestamp)); + + assertWithMessage("Second element is not atom id") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_INT); + + assertWithMessage("Incorrect atom id") + .that(buffer.getInt()).isEqualTo(expectedAtomId); + + assertWithMessage("Third element is not errors type") + .that(buffer.get()).isEqualTo(StatsEvent.TYPE_ERRORS); + + final int errorMask = buffer.getInt(); + + assertWithMessage("ERROR_ATOM_ID_INVALID_POSITION should be the only error in the mask") + .that(errorMask).isEqualTo(StatsEvent.ERROR_ATOM_ID_INVALID_POSITION); + + assertThat(statsEvent.getNumBytes()).isEqualTo(buffer.position()); + + statsEvent.release(); + } + private static byte[] getByteArrayFromByteBuffer(final ByteBuffer buffer) { final int numBytes = buffer.getInt(); byte[] bytes = new byte[numBytes]; diff --git a/api/current.txt b/api/current.txt index c19a70c5c250..680b2248249a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7605,6 +7605,7 @@ package android.app.blob { method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException; method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence) throws java.io.IOException; method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; + method @NonNull public java.util.List<android.app.blob.BlobHandle> getLeasedBlobs() throws java.io.IOException; method @IntRange(from=0) public long getRemainingLeaseQuotaBytes(); method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; method @NonNull public android.app.blob.BlobStoreManager.Session openSession(@IntRange(from=1) long) throws java.io.IOException; diff --git a/api/test-current.txt b/api/test-current.txt index 38cf6a16de56..0bd8a195edff 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -598,7 +598,6 @@ package android.app.blob { public class BlobStoreManager { method @Nullable public android.app.blob.LeaseInfo getLeaseInfo(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; - method @NonNull public java.util.List<android.app.blob.BlobHandle> getLeasedBlobs() throws java.io.IOException; method public void waitForIdle(long) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException; } diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp index d295b84baf67..78c322edcccc 100644 --- a/cmds/incidentd/src/FdBuffer.cpp +++ b/cmds/incidentd/src/FdBuffer.cpp @@ -17,6 +17,7 @@ #include "Log.h" #include "FdBuffer.h" +#include "incidentd_util.h" #include <log/log.h> #include <utils/SystemClock.h> @@ -31,17 +32,24 @@ namespace os { namespace incidentd { const ssize_t BUFFER_SIZE = 16 * 1024; // 16 KB -const ssize_t MAX_BUFFER_COUNT = 6144; // 96 MB max +const ssize_t MAX_BUFFER_SIZE = 96 * 1024 * 1024; // 96 MB -FdBuffer::FdBuffer() - :mBuffer(new EncodedBuffer(BUFFER_SIZE)), +FdBuffer::FdBuffer(): FdBuffer(get_buffer_from_pool(), /* isBufferPooled= */ true) { +} + +FdBuffer::FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled) + :mBuffer(buffer), mStartTime(-1), mFinishTime(-1), mTimedOut(false), - mTruncated(false) { + mTruncated(false), + mIsBufferPooled(isBufferPooled) { } FdBuffer::~FdBuffer() { + if (mIsBufferPooled) { + return_buffer_to_pool(mBuffer); + } } status_t FdBuffer::read(int fd, int64_t timeout) { @@ -51,7 +59,7 @@ status_t FdBuffer::read(int fd, int64_t timeout) { fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { mTruncated = true; VLOG("Truncating data"); break; @@ -106,7 +114,7 @@ status_t FdBuffer::readFully(int fd) { mStartTime = uptimeMillis(); while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { // Don't let it get too big. mTruncated = true; VLOG("Truncating data"); @@ -156,7 +164,7 @@ status_t FdBuffer::readProcessedDataInStream(int fd, unique_fd toFd, unique_fd f // This is the buffer used to store processed data while (true) { - if (mBuffer->size() >= MAX_BUFFER_COUNT * BUFFER_SIZE) { + if (mBuffer->size() >= MAX_BUFFER_SIZE) { VLOG("Truncating data"); mTruncated = true; break; diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h index a3493604f425..9b2794d165fb 100644 --- a/cmds/incidentd/src/FdBuffer.h +++ b/cmds/incidentd/src/FdBuffer.h @@ -35,6 +35,7 @@ using namespace android::util; class FdBuffer { public: FdBuffer(); + FdBuffer(sp<EncodedBuffer> buffer, bool isBufferPooled = false); ~FdBuffer(); /** @@ -114,6 +115,7 @@ private: int64_t mFinishTime; bool mTimedOut; bool mTruncated; + bool mIsBufferPooled; }; } // namespace incidentd diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp index d00ecdde5c63..0d427d1021a6 100644 --- a/cmds/incidentd/src/PrivacyFilter.cpp +++ b/cmds/incidentd/src/PrivacyFilter.cpp @@ -19,9 +19,6 @@ #include "incidentd_util.h" #include "PrivacyFilter.h" #include "proto_util.h" - -#include "incidentd_util.h" -#include "proto_util.h" #include "Section.h" #include <android-base/file.h> @@ -129,6 +126,8 @@ public: FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, uint8_t bufferLevel); + ~FieldStripper(); + /** * Take the data that we have, and filter it down so that no fields * are more sensitive than the given privacy policy. @@ -167,6 +166,7 @@ private: */ uint8_t mCurrentLevel; + sp<EncodedBuffer> mEncodedBuffer; }; FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& data, @@ -174,19 +174,25 @@ FieldStripper::FieldStripper(const Privacy* restrictions, const sp<ProtoReader>& :mRestrictions(restrictions), mData(data), mSize(data->size()), - mCurrentLevel(bufferLevel) { + mCurrentLevel(bufferLevel), + mEncodedBuffer(get_buffer_from_pool()) { if (mSize < 0) { ALOGW("FieldStripper constructed with a ProtoReader that doesn't support size." " Data will be missing."); } } +FieldStripper::~FieldStripper() { + return_buffer_to_pool(mEncodedBuffer); +} + status_t FieldStripper::strip(const uint8_t privacyPolicy) { // If the current strip level is less (fewer fields retained) than what's already in the // buffer, then we can skip it. if (mCurrentLevel < privacyPolicy) { PrivacySpec spec(privacyPolicy); - ProtoOutputStream proto; + mEncodedBuffer->clear(); + ProtoOutputStream proto(mEncodedBuffer); // Optimization when no strip happens. if (mRestrictions == NULL || spec.RequireAll()) { @@ -267,7 +273,7 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, // Order the writes by privacy filter, with increasing levels of filtration,k // so we can do the filter once, and then write many times. sort(mOutputs.begin(), mOutputs.end(), - [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { + [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool { return a->getPrivacyPolicy() < b->getPrivacyPolicy(); }); @@ -370,7 +376,7 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel, write_field_or_skip(NULL, reader, fieldTag, true); } } - + clear_buffer_pool(); err = reader->getError(); if (err != NO_ERROR) { ALOGW("filter_and_write_report reader had an error: %s", strerror(-err)); diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index ad253422452e..86a78f095f52 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -698,7 +698,7 @@ DONE: listener->onReportFailed(); }); } - + clear_buffer_pool(); ALOGI("Done taking incident report err=%s", strerror(-err)); } diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index c703c3ce0951..dec9cb0ad4ff 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -36,6 +36,7 @@ #include <log/log_read.h> #include <log/logprint.h> #include <private/android_logger.h> +#include <sys/mman.h> #include "FdBuffer.h" #include "Privacy.h" @@ -106,7 +107,6 @@ status_t FileSection::Execute(ReportWriter* writer) const { return NO_ERROR; } - FdBuffer buffer; Fpipe p2cPipe; Fpipe c2pPipe; // initiate pipes to pass data to/from incident_helper @@ -122,6 +122,7 @@ status_t FileSection::Execute(ReportWriter* writer) const { } // parent process + FdBuffer buffer; status_t readStatus = buffer.readProcessedDataInStream(fd.get(), std::move(p2cPipe.writeFd()), std::move(c2pPipe.readFd()), this->timeoutMs, mIsSysfs); @@ -356,7 +357,6 @@ CommandSection::CommandSection(int id, const char* command, ...) : Section(id) { CommandSection::~CommandSection() { free(mCommand); } status_t CommandSection::Execute(ReportWriter* writer) const { - FdBuffer buffer; Fpipe cmdPipe; Fpipe ihPipe; @@ -377,6 +377,7 @@ status_t CommandSection::Execute(ReportWriter* writer) const { } cmdPipe.writeFd().reset(); + FdBuffer buffer; status_t readStatus = buffer.read(ihPipe.readFd().get(), this->timeoutMs); writer->setSectionStats(buffer); if (readStatus != NO_ERROR || buffer.timedOut()) { @@ -574,6 +575,16 @@ static inline int32_t get4LE(uint8_t const* src) { } status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { + // heap profile shows that liblog malloc & free significant amount of memory in this process. + // Hence forking a new process to prevent memory fragmentation. + pid_t pid = fork(); + if (pid < 0) { + ALOGW("[%s] failed to fork", this->name.string()); + return errno; + } + if (pid > 0) { + return wait_child(pid, this->timeoutMs); + } // Open log buffer and getting logs since last retrieved time if any. unique_ptr<logger_list, void (*)(logger_list*)> loggers( gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end() @@ -583,31 +594,31 @@ status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { if (android_logger_open(loggers.get(), mLogID) == NULL) { ALOGE("[%s] Can't get logger.", this->name.string()); - return -1; + _exit(EXIT_FAILURE); } log_msg msg; log_time lastTimestamp(0); ProtoOutputStream proto; + status_t err = OK; while (true) { // keeps reading until logd buffer is fully read. - status_t err = android_logger_list_read(loggers.get(), &msg); - // err = 0 - no content, unexpected connection drop or EOF. - // err = +ive number - size of retrieved data from logger - // err = -ive number, OS supplied error _except_ for -EAGAIN - // err = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end of data. - if (err <= 0) { - if (err != -EAGAIN) { + status_t status = android_logger_list_read(loggers.get(), &msg); + // status = 0 - no content, unexpected connection drop or EOF. + // status = +ive number - size of retrieved data from logger + // status = -ive number, OS supplied error _except_ for -EAGAIN + // status = -EAGAIN, graceful indication for ANDRODI_LOG_NONBLOCK that this is the end. + if (status <= 0) { + if (status != -EAGAIN) { ALOGW("[%s] fails to read a log_msg.\n", this->name.string()); + err = -status; } - // dump previous logs and don't consider this error a failure. break; } if (mBinary) { // remove the first uint32 which is tag's index in event log tags android_log_context context = create_android_log_parser(msg.msg() + sizeof(uint32_t), msg.len() - sizeof(uint32_t)); - ; android_log_list_element elem; lastTimestamp.tv_sec = msg.entry.sec; @@ -667,9 +678,10 @@ status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { } } else { AndroidLogEntry entry; - err = android_log_processLogBuffer(&msg.entry, &entry); - if (err != NO_ERROR) { + status = android_log_processLogBuffer(&msg.entry, &entry); + if (status != OK) { ALOGW("[%s] fails to process to an entry.\n", this->name.string()); + err = status; break; } lastTimestamp.tv_sec = entry.tv_sec; @@ -688,17 +700,24 @@ status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const { trimTail(entry.message, entry.messageLen)); proto.end(token); } + if (!proto.flush(pipeWriteFd.get())) { + if (errno == EPIPE) { + ALOGW("[%s] wrote to a broken pipe\n", this->name.string()); + } + err = errno; + break; + } + proto.clear(); } gLastLogsRetrieved[mLogID] = lastTimestamp; - if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) { - ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); - return EPIPE; - } - return NO_ERROR; + _exit(err); } // ================================================================================ +const int LINK_NAME_LEN = 64; +const int EXE_NAME_LEN = 1024; + TombstoneSection::TombstoneSection(int id, const char* type, const int64_t timeoutMs) : WorkerThreadSection(id, timeoutMs), mType(type) { name = "tombstone "; @@ -716,25 +735,37 @@ status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const { const std::set<int> hal_pids = get_interesting_hal_pids(); - ProtoOutputStream proto; + auto pooledBuffer = get_buffer_from_pool(); + ProtoOutputStream proto(pooledBuffer); + // dumpBufferSize should be a multiple of page size (4 KB) to reduce memory fragmentation + size_t dumpBufferSize = 64 * 1024; // 64 KB is enough for most tombstone dump + char* dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); struct dirent* d; + char link_name[LINK_NAME_LEN]; + char exe_name[EXE_NAME_LEN]; status_t err = NO_ERROR; while ((d = readdir(proc.get()))) { int pid = atoi(d->d_name); if (pid <= 0) { continue; } - - const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid); - std::string exe; - if (!android::base::Readlink(link_name, &exe)) { - ALOGE("Section %s: Can't read '%s': %s\n", name.string(), - link_name.c_str(), strerror(errno)); + snprintf(link_name, LINK_NAME_LEN, "/proc/%d/exe", pid); + struct stat fileStat; + if (stat(link_name, &fileStat) != OK) { continue; } + size_t exe_name_len = readlink(link_name, exe_name, EXE_NAME_LEN); + if (exe_name_len < 0 || exe_name_len >= EXE_NAME_LEN) { + ALOGE("[%s] Can't read '%s': %s", name.string(), link_name, strerror(errno)); + continue; + } + // readlink(2) does not put a null terminator at the end + exe_name[exe_name_len] = '\0'; bool is_java_process; - if (exe == "/system/bin/app_process32" || exe == "/system/bin/app_process64") { + if (strncmp(exe_name, "/system/bin/app_process32", LINK_NAME_LEN) == 0 || + strncmp(exe_name, "/system/bin/app_process64", LINK_NAME_LEN) == 0) { if (mType != "java") continue; // Don't bother dumping backtraces for the zygote. if (IsZygote(pid)) { @@ -743,7 +774,7 @@ status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const { } is_java_process = true; - } else if (should_dump_native_traces(exe.c_str())) { + } else if (should_dump_native_traces(exe_name)) { if (mType != "native") continue; is_java_process = false; } else if (hal_pids.find(pid) != hal_pids.end()) { @@ -799,29 +830,37 @@ status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const { ALOGE("[%s] child had an issue: %s\n", this->name.string(), strerror(-cStatus)); } - auto dump = std::make_unique<char[]>(buffer.size()); + // Resize dump buffer + if (dumpBufferSize < buffer.size()) { + munmap(dumpBuffer, dumpBufferSize); + while(dumpBufferSize < buffer.size()) dumpBufferSize = dumpBufferSize << 1; + dumpBuffer = (char*)mmap(NULL, dumpBufferSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + } sp<ProtoReader> reader = buffer.data()->read(); int i = 0; while (reader->hasNext()) { - dump[i] = reader->next(); + dumpBuffer[i] = reader->next(); i++; } uint64_t token = proto.start(android::os::BackTraceProto::TRACES); proto.write(android::os::BackTraceProto::Stack::PID, pid); - proto.write(android::os::BackTraceProto::Stack::DUMP, dump.get(), i); + proto.write(android::os::BackTraceProto::Stack::DUMP, dumpBuffer, i); proto.write(android::os::BackTraceProto::Stack::DUMP_DURATION_NS, static_cast<long long>(Nanotime() - start)); proto.end(token); dumpPipe.readFd().reset(); - } - - if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) { - ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); - if (err != NO_ERROR) { - return EPIPE; + if (!proto.flush(pipeWriteFd.get())) { + if (errno == EPIPE) { + ALOGE("[%s] wrote to a broken pipe\n", this->name.string()); + } + err = errno; + break; } + proto.clear(); } - + munmap(dumpBuffer, dumpBufferSize); + return_buffer_to_pool(pooledBuffer); return err; } diff --git a/cmds/incidentd/src/incidentd_util.cpp b/cmds/incidentd/src/incidentd_util.cpp index 2649fb975792..150ab9991a2d 100644 --- a/cmds/incidentd/src/incidentd_util.cpp +++ b/cmds/incidentd/src/incidentd_util.cpp @@ -18,6 +18,7 @@ #include "incidentd_util.h" +#include <android/util/EncodedBuffer.h> #include <fcntl.h> #include <sys/prctl.h> #include <wait.h> @@ -28,8 +29,6 @@ namespace android { namespace os { namespace incidentd { -using namespace android::base; - const Privacy* get_privacy_of_section(int id) { int l = 0; int r = PRIVACY_POLICY_COUNT - 1; @@ -48,6 +47,30 @@ const Privacy* get_privacy_of_section(int id) { return NULL; } +std::vector<sp<EncodedBuffer>> gBufferPool; +std::mutex gBufferPoolLock; + +sp<EncodedBuffer> get_buffer_from_pool() { + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + if (gBufferPool.size() == 0) { + return new EncodedBuffer(); + } + sp<EncodedBuffer> buffer = gBufferPool.back(); + gBufferPool.pop_back(); + return buffer; +} + +void return_buffer_to_pool(sp<EncodedBuffer> buffer) { + buffer->clear(); + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + gBufferPool.push_back(buffer); +} + +void clear_buffer_pool() { + std::scoped_lock<std::mutex> lock(gBufferPoolLock); + gBufferPool.clear(); +} + // ================================================================================ Fpipe::Fpipe() : mRead(), mWrite() {} @@ -84,7 +107,7 @@ pid_t fork_execute_cmd(char* const argv[], int in, int out, int* status) { status = &dummy_status; } *status = 0; - pid_t pid = fork(); + pid_t pid = vfork(); if (pid < 0) { *status = -errno; return -1; diff --git a/cmds/incidentd/src/incidentd_util.h b/cmds/incidentd/src/incidentd_util.h index a54993fed42d..84998892e33c 100644 --- a/cmds/incidentd/src/incidentd_util.h +++ b/cmds/incidentd/src/incidentd_util.h @@ -19,18 +19,21 @@ #define INCIDENTD_UTIL_H #include <stdarg.h> -#include <unistd.h> - -#include <android-base/unique_fd.h> #include <utils/Errors.h> #include "Privacy.h" namespace android { + +namespace util { +class EncodedBuffer; +} + namespace os { namespace incidentd { -using namespace android::base; +using android::base::unique_fd; +using android::util::EncodedBuffer; /** * Looks up Privacy of a section in the auto-gen PRIVACY_POLICY_LIST; @@ -38,6 +41,25 @@ using namespace android::base; const Privacy* get_privacy_of_section(int id); /** + * Get an EncodedBuffer from an internal pool, or create and return a new one if the pool is empty. + * The EncodedBuffer should be returned after use. + * Thread safe. + */ +sp<EncodedBuffer> get_buffer_from_pool(); + +/** + * Return the EncodedBuffer back to the pool for reuse. + * Thread safe. + */ +void return_buffer_to_pool(sp<EncodedBuffer> buffer); + +/** + * Clear the buffer pool to free memory, after taking an incident report. + * Thread safe. + */ +void clear_buffer_pool(); + +/** * This class wraps android::base::Pipe. */ class Fpipe { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a62f0a6807bb..bd3fee2d1fc7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -201,6 +201,7 @@ import java.lang.reflect.Method; import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -7413,8 +7414,10 @@ public final class ActivityThread extends ClientTransactionHandler { if (e.errno == OsConstants.EXDEV && oldPath.startsWith("/storage/")) { Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath); try { - Files.move(new File(oldPath).toPath(), new File(newPath).toPath()); + Files.move(new File(oldPath).toPath(), new File(newPath).toPath(), + StandardCopyOption.REPLACE_EXISTING); } catch (IOException e2) { + Log.e(TAG, "Rename recovery failed ", e); throw e; } } else { diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 51cfa31ae4cf..c4458b30c2db 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2429,7 +2429,7 @@ public class DevicePolicyManager { PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT }) @Retention(RetentionPolicy.SOURCE) - public @interface PersonalAppSuspensionReason {} + public @interface PersonalAppsSuspensionReason {} /** * Return true if the given administrator component is currently active (enabled) in the system. @@ -11961,7 +11961,7 @@ public class DevicePolicyManager { * {@link #PERSONAL_APPS_NOT_SUSPENDED} if apps are not suspended. * @see #setPersonalAppsSuspended */ - public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons( + public @PersonalAppsSuspensionReason int getPersonalAppsSuspendedReasons( @NonNull ComponentName admin) { throwIfParentInstance("getPersonalAppsSuspendedReasons"); if (mService != null) { diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 179fc5c661a5..6ba811e077ac 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -268,15 +268,17 @@ public class CrossProfileApps { } /** - * Returns whether the calling package can request user consent to interact across profiles. + * Returns whether the calling package can request to navigate the user to + * the relevant settings page to request user consent to interact across profiles. * - * <p>If {@code true}, user consent can be obtained via {@link + * <p>If {@code true}, the navigation intent can be obtained via {@link * #createRequestInteractAcrossProfilesIntent()}. The package can then listen to {@link * #ACTION_CAN_INTERACT_ACROSS_PROFILES_CHANGED} broadcasts. * * <p>Specifically, returns whether the following are all true: * <ul> - * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li> + * <li>{@code UserManager#getEnabledProfileIds(int)} ()} returns at least one other profile for + * the calling user.</li> * <li>The calling app has requested</li> * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest. * <li>The calling package has either been whitelisted by default by the OEM or has been @@ -285,6 +287,10 @@ public class CrossProfileApps { * </li> * </ul> * + * <p>Note that in order for the user to be able to grant the consent, the requesting package + * must be whitelisted by the admin or the OEM and installed in the other profile. If this is + * not the case the user will be shown a message explaining why they can't grant the consent. + * * <p>Note that user consent could already be granted if given a return value of {@code true}. * The package's current ability to interact across profiles can be checked with {@link * #canInteractAcrossProfiles()}. @@ -422,6 +428,23 @@ public class CrossProfileApps { } /** + * Returns {@code true} if the given package has requested + * {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES} and the user has at least one + * other profile in the same profile group. + * + * <p>This differs from {@link #canConfigureInteractAcrossProfiles(String)} since it will + * not return {@code false} if the app is not whitelisted or not installed in the other profile. + * + * @hide + */ + public boolean canUserAttemptToConfigureInteractAcrossProfiles(String packageName) { + try { + return mService.canUserAttemptToConfigureInteractAcrossProfiles(packageName); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + /** * For each of the packages defined in {@code previousCrossProfilePackages} but not included in * {@code newCrossProfilePackages}, resets the app-op for {@link android.Manifest.permission * #INTERACT_ACROSS_PROFILES} back to its default value if it can no longer be configured by diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl index 4cecb30990e6..9b0dae221538 100644 --- a/core/java/android/content/pm/ICrossProfileApps.aidl +++ b/core/java/android/content/pm/ICrossProfileApps.aidl @@ -38,5 +38,6 @@ interface ICrossProfileApps { boolean canRequestInteractAcrossProfiles(in String callingPackage); void setInteractAcrossProfilesAppOp(in String packageName, int newMode); boolean canConfigureInteractAcrossProfiles(in String packageName); + boolean canUserAttemptToConfigureInteractAcrossProfiles(in String packageName); void resetInteractAcrossProfilesAppOps(in List<String> packageNames); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9f151cf073bd..370469ebe840 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -7088,7 +7088,7 @@ public abstract class PackageManager { * Returns any packages in a given set of packages that cannot be suspended via a call to {@link * #setPackagesSuspended(String[], boolean, PersistableBundle, PersistableBundle, * SuspendDialogInfo) setPackagesSuspended}. The platform prevents suspending certain critical - * packages to keep the device in a functioning state, e.g. the default dialer. + * packages to keep the device in a functioning state, e.g. the default dialer and launcher. * Apps need to hold {@link Manifest.permission#SUSPEND_APPS SUSPEND_APPS} to call this API. * * <p> @@ -7106,7 +7106,7 @@ public abstract class PackageManager { @RequiresPermission(Manifest.permission.SUSPEND_APPS) @NonNull public String[] getUnsuspendablePackages(@NonNull String[] packageNames) { - throw new UnsupportedOperationException("canSuspendPackages not implemented"); + throw new UnsupportedOperationException("getUnsuspendablePackages not implemented"); } /** diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 34a40aef147f..eb6901f6650e 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1214,7 +1214,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>If the camera device supports zoom-out from 1x zoom, minZoom will be less than 1.0, and * setting {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to values less than 1.0 increases the camera's field * of view.</p> - * <p><b>Units</b>: A pair of zoom ratio in floating points: (minZoom, maxZoom)</p> + * <p><b>Units</b>: A pair of zoom ratio in floating-points: (minZoom, maxZoom)</p> * <p><b>Range of valid values:</b><br></p> * <p>maxZoom >= 1.0 >= minZoom</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 0ee748287fa2..6905f83104cd 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2180,27 +2180,66 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical * crop to achieve aspect ratios different than the native camera sensor.</p> * <p>By using this control, the application gains a simpler way to control zoom, which can - * be a combination of optical and digital zoom. More specifically, for a logical - * multi-camera with more than one focal length, using a floating point zoom ratio offers - * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of - * less than 1.0 to zoom out to a wide field of view.</p> - * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes - * to the effective after-zoom field-of-view represented by rectangle of (0, 0, - * activeArrayWidth, activeArrayHeight).</p> - * <p>For example, if {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} is 4032*3024, and the preview stream - * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in - * one of two ways:</p> + * be a combination of optical and digital zoom. For example, a multi-camera system may + * contain more than one lens with different focal lengths, and the user can use optical + * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below: + * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides + * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}. + * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p> + * <p>To illustrate, here are several scenarios of different zoom ratios, crop regions, + * and output streams, for a hypothetical camera device with an active array of size + * <code>(2000,1500)</code>.</p> * <ul> - * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li> - * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li> + * <li>Camera Configuration:<ul> + * <li>Active array size: <code>2000x1500</code> (3 MP, 4:3 aspect ratio)</li> + * <li>Output stream #1: <code>640x480</code> (VGA, 4:3 aspect ratio)</li> + * <li>Output stream #2: <code>1280x720</code> (720p, 16:9 aspect ratio)</li> + * </ul> + * </li> + * <li>Case #1: 4:3 crop region with 2.0x zoom ratio<ul> + * <li>Zoomed field of view: 1/4 of original field of view</li> + * <li>Crop region: <code>Rect(0, 0, 2000, 1500) // (left, top, right, bottom)</code> (post zoom)</li> + * </ul> + * </li> + * <li><img alt="4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-43.png" /><ul> + * <li><code>640x480</code> stream source area: <code>(0, 0, 2000, 1500)</code> (equal to crop region)</li> + * <li><code>1280x720</code> stream source area: <code>(0, 187, 2000, 1312)</code> (letterboxed)</li> + * </ul> + * </li> + * <li>Case #2: 16:9 crop region with 2.0x zoom.<ul> + * <li>Zoomed field of view: 1/4 of original field of view</li> + * <li>Crop region: <code>Rect(0, 187, 2000, 1312)</code></li> + * <li><img alt="16:9 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-169.png" /></li> + * <li><code>640x480</code> stream source area: <code>(250, 187, 1750, 1312)</code> (pillarboxed)</li> + * <li><code>1280x720</code> stream source area: <code>(0, 187, 2000, 1312)</code> (equal to crop region)</li> + * </ul> + * </li> + * <li>Case #3: 1:1 crop region with 0.5x zoom out to ultrawide lens.<ul> + * <li>Zoomed field of view: 4x of original field of view (switched from wide lens to ultrawide lens)</li> + * <li>Crop region: <code>Rect(250, 0, 1750, 1500)</code></li> + * <li><img alt="1:1 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-0.5-crop-11.png" /></li> + * <li><code>640x480</code> stream source area: <code>(250, 187, 1750, 1312)</code> (letterboxed)</li> + * <li><code>1280x720</code> stream source area: <code>(250, 328, 1750, 1172)</code> (letterboxed)</li> + * </ul> + * </li> * </ul> - * <p>If the application intends to set aeRegions to be top-left quarter of the preview - * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 2016, 1512) with + * <p>As seen from the graphs above, the coordinate system of cropRegion now changes to the + * effective after-zoom field-of-view, and is represented by the rectangle of (0, 0, + * activeArrayWith, activeArrayHeight). The same applies to AE/AWB/AF regions, and faces. + * This coordinate system change isn't applicable to RAW capture and its related + * metadata such as intrinsicCalibration and lensShadingMap.</p> + * <p>Using the same hypothetical example above, and assuming output stream #1 (640x480) is + * the viewfinder stream, the application can achieve 2.0x zoom in one of two ways:</p> + * <ul> + * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 2000, 1500)</li> + * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (500, 375, 1500, 1125)</li> + * </ul> + * <p>If the application intends to set aeRegions to be top-left quarter of the viewfinder + * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 1000, 750) with * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent - * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't + * region of (500, 375, 1000, 750) for zoomRatio of 1.0. If the application doesn't * explicitly set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, its value defaults to 1.0.</p> - * <p>This coordinate system change isn't applicable to RAW capture and its related metadata - * such as intrinsicCalibration and lensShadingMap.</p> * <p>One limitation of controlling zoom using zoomRatio is that the {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} * must only be used for letterboxing or pillarboxing of the sensor active array, and no * FREEFORM cropping can be used with {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} other than 1.0.</p> @@ -2216,7 +2255,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CaptureRequest#SCALER_CROP_REGION - * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ @PublicKey @NonNull @@ -2574,12 +2612,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * frames before the lens can change to the requested focal length. * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will * be set to MOVING.</p> - * <p>Optical zoom will not be supported on most devices.</p> + * <p>Optical zoom via this control will not be supported on most devices. Starting from API + * level 30, the camera device may combine optical and digital zoom through the + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} control.</p> * <p><b>Units</b>: Millimeters</p> * <p><b>Range of valid values:</b><br> * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS android.lens.info.availableFocalLengths}</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_ZOOM_RATIO * @see CaptureRequest#LENS_APERTURE * @see CaptureRequest#LENS_FOCUS_DISTANCE * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 096aa0cd27b3..be03502eb943 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2410,27 +2410,66 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} can still be used to specify the horizontal or vertical * crop to achieve aspect ratios different than the native camera sensor.</p> * <p>By using this control, the application gains a simpler way to control zoom, which can - * be a combination of optical and digital zoom. More specifically, for a logical - * multi-camera with more than one focal length, using a floating point zoom ratio offers - * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of - * less than 1.0 to zoom out to a wide field of view.</p> - * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes - * to the effective after-zoom field-of-view represented by rectangle of (0, 0, - * activeArrayWidth, activeArrayHeight).</p> - * <p>For example, if {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} is 4032*3024, and the preview stream - * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in - * one of two ways:</p> + * be a combination of optical and digital zoom. For example, a multi-camera system may + * contain more than one lens with different focal lengths, and the user can use optical + * zoom by switching between lenses. Using zoomRatio has benefits in the scenarios below: + * <em> Zooming in from a wide-angle lens to a telephoto lens: A floating-point ratio provides + * better precision compared to an integer value of {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}. + * </em> Zooming out from a wide lens to an ultrawide lens: zoomRatio supports zoom-out whereas + * {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} doesn't.</p> + * <p>To illustrate, here are several scenarios of different zoom ratios, crop regions, + * and output streams, for a hypothetical camera device with an active array of size + * <code>(2000,1500)</code>.</p> * <ul> - * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li> - * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li> + * <li>Camera Configuration:<ul> + * <li>Active array size: <code>2000x1500</code> (3 MP, 4:3 aspect ratio)</li> + * <li>Output stream #1: <code>640x480</code> (VGA, 4:3 aspect ratio)</li> + * <li>Output stream #2: <code>1280x720</code> (720p, 16:9 aspect ratio)</li> + * </ul> + * </li> + * <li>Case #1: 4:3 crop region with 2.0x zoom ratio<ul> + * <li>Zoomed field of view: 1/4 of original field of view</li> + * <li>Crop region: <code>Rect(0, 0, 2000, 1500) // (left, top, right, bottom)</code> (post zoom)</li> + * </ul> + * </li> + * <li><img alt="4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-43.png" /><ul> + * <li><code>640x480</code> stream source area: <code>(0, 0, 2000, 1500)</code> (equal to crop region)</li> + * <li><code>1280x720</code> stream source area: <code>(0, 187, 2000, 1312)</code> (letterboxed)</li> + * </ul> + * </li> + * <li>Case #2: 16:9 crop region with 2.0x zoom.<ul> + * <li>Zoomed field of view: 1/4 of original field of view</li> + * <li>Crop region: <code>Rect(0, 187, 2000, 1312)</code></li> + * <li><img alt="16:9 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-169.png" /></li> + * <li><code>640x480</code> stream source area: <code>(250, 187, 1750, 1312)</code> (pillarboxed)</li> + * <li><code>1280x720</code> stream source area: <code>(0, 187, 2000, 1312)</code> (equal to crop region)</li> + * </ul> + * </li> + * <li>Case #3: 1:1 crop region with 0.5x zoom out to ultrawide lens.<ul> + * <li>Zoomed field of view: 4x of original field of view (switched from wide lens to ultrawide lens)</li> + * <li>Crop region: <code>Rect(250, 0, 1750, 1500)</code></li> + * <li><img alt="1:1 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-0.5-crop-11.png" /></li> + * <li><code>640x480</code> stream source area: <code>(250, 187, 1750, 1312)</code> (letterboxed)</li> + * <li><code>1280x720</code> stream source area: <code>(250, 328, 1750, 1172)</code> (letterboxed)</li> + * </ul> + * </li> * </ul> - * <p>If the application intends to set aeRegions to be top-left quarter of the preview - * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 2016, 1512) with + * <p>As seen from the graphs above, the coordinate system of cropRegion now changes to the + * effective after-zoom field-of-view, and is represented by the rectangle of (0, 0, + * activeArrayWith, activeArrayHeight). The same applies to AE/AWB/AF regions, and faces. + * This coordinate system change isn't applicable to RAW capture and its related + * metadata such as intrinsicCalibration and lensShadingMap.</p> + * <p>Using the same hypothetical example above, and assuming output stream #1 (640x480) is + * the viewfinder stream, the application can achieve 2.0x zoom in one of two ways:</p> + * <ul> + * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 2000, 1500)</li> + * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (500, 375, 1500, 1125)</li> + * </ul> + * <p>If the application intends to set aeRegions to be top-left quarter of the viewfinder + * field-of-view, the {@link CaptureRequest#CONTROL_AE_REGIONS android.control.aeRegions} should be set to (0, 0, 1000, 750) with * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent - * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't + * region of (500, 375, 1000, 750) for zoomRatio of 1.0. If the application doesn't * explicitly set {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}, its value defaults to 1.0.</p> - * <p>This coordinate system change isn't applicable to RAW capture and its related metadata - * such as intrinsicCalibration and lensShadingMap.</p> * <p>One limitation of controlling zoom using zoomRatio is that the {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion} * must only be used for letterboxing or pillarboxing of the sensor active array, and no * FREEFORM cropping can be used with {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} other than 1.0.</p> @@ -2446,7 +2485,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * @see CameraCharacteristics#CONTROL_ZOOM_RATIO_RANGE * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL * @see CaptureRequest#SCALER_CROP_REGION - * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE */ @PublicKey @NonNull @@ -2848,12 +2886,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * frames before the lens can change to the requested focal length. * While the focal length is still changing, {@link CaptureResult#LENS_STATE android.lens.state} will * be set to MOVING.</p> - * <p>Optical zoom will not be supported on most devices.</p> + * <p>Optical zoom via this control will not be supported on most devices. Starting from API + * level 30, the camera device may combine optical and digital zoom through the + * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} control.</p> * <p><b>Units</b>: Millimeters</p> * <p><b>Range of valid values:</b><br> * {@link CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS android.lens.info.availableFocalLengths}</p> * <p>This key is available on all devices.</p> * + * @see CaptureRequest#CONTROL_ZOOM_RATIO * @see CaptureRequest#LENS_APERTURE * @see CaptureRequest#LENS_FOCUS_DISTANCE * @see CameraCharacteristics#LENS_INFO_AVAILABLE_FOCAL_LENGTHS diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ccc3132c535d..60f10cd884ca 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10339,6 +10339,7 @@ public final class Settings { /** * Value to specify if wifi settings migration is complete or not. + * Note: This should only be used from within {@link android.net.wifi.WifiMigration} class. * * Type: int (0 for false, 1 for true) * @hide diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index dfe89a37a229..45e51f756489 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -318,8 +318,7 @@ interface IWindowSession { * Called when the client has changed the local insets state, and now the server should reflect * that new state. */ - void insetsModified(IWindow window, in InsetsState state); - + oneway void insetsModified(IWindow window, in InsetsState state); /** * Called when the system gesture exclusion has changed. diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index b70072877c66..07b6cc1c22d2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -258,7 +258,6 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return state.calculateInsets(frame, null /* ignoringVisibilityState */, false /* isScreenRound */, false /* alwaysConsumeSystemBars */, null /* displayCutout */, - null /* legacyContentInsets */, null /* legacyStableInsets */, LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, 0 /* legacySystemUiFlags */, typeSideMap) .getInsets(mTypes); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 048c0e26cdb8..43f80f1490ad 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -363,9 +363,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final Runnable mAnimCallback; - private final Rect mLastLegacyContentInsets = new Rect(); - private final Rect mLastLegacyStableInsets = new Rect(); - /** Pending control request that is waiting on IME to be ready to be shown */ private PendingControlRequest mPendingImeControlRequest; @@ -435,8 +432,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), - mLastDisplayCutout, mLastLegacyContentInsets, mLastLegacyStableInsets, - mLastLegacySoftInputMode, mLastLegacySystemUiFlags, null /* typeSideMap */); + mLastDisplayCutout, mLastLegacySoftInputMode, mLastLegacySystemUiFlags, + null /* typeSideMap */); mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets, mUnmodifiableTmpRunningAnims); @@ -466,13 +463,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public boolean onStateChanged(InsetsState state) { - if (mState.equals(state) && mLastDispachedState.equals(state)) { + boolean localStateChanged = !mState.equals(state); + if (!localStateChanged && mLastDispachedState.equals(state)) { return false; } mState.set(state); mLastDispachedState.set(state, true /* copySources */); applyLocalVisibilityOverride(); - mViewRoot.notifyInsetsChanged(); + if (localStateChanged) { + mViewRoot.notifyInsetsChanged(); + } if (!mState.equals(mLastDispachedState)) { sendStateToWindowManager(); } @@ -484,26 +484,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation */ @VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, - boolean alwaysConsumeSystemBars, DisplayCutout cutout, Rect legacyContentInsets, - Rect legacyStableInsets, int legacySoftInputMode, int legacySystemUiFlags) { - mLastLegacyContentInsets.set(legacyContentInsets); - mLastLegacyStableInsets.set(legacyStableInsets); + boolean alwaysConsumeSystemBars, DisplayCutout cutout, + int legacySoftInputMode, int legacySystemUiFlags) { mLastLegacySoftInputMode = legacySoftInputMode; mLastLegacySystemUiFlags = legacySystemUiFlags; mLastDisplayCutout = cutout; mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/, - isScreenRound, alwaysConsumeSystemBars, cutout, legacyContentInsets, - legacyStableInsets, legacySoftInputMode, legacySystemUiFlags, + isScreenRound, alwaysConsumeSystemBars, cutout, + legacySoftInputMode, legacySystemUiFlags, null /* typeSideMap */); return mLastInsets; } /** - * @see InsetsState#calculateVisibleInsets(Rect, Rect, int) + * @see InsetsState#calculateVisibleInsets(Rect, int) */ - public Rect calculateVisibleInsets(Rect legacyVisibleInsets, - @SoftInputModeFlags int softInputMode) { - return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode); + public Rect calculateVisibleInsets(@SoftInputModeFlags int softInputMode) { + return mState.calculateVisibleInsets(mFrame, softInputMode); } /** @@ -954,7 +951,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } } - // TODO: Put this on a dispatcher thread. try { mViewRoot.mWindowSession.insetsModified(mViewRoot.mWindow, tmpState); } catch (RemoteException e) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c2ad74a566e9..40e6f57f2286 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -160,7 +160,6 @@ public class InsetsState implements Parcelable { */ public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, boolean alwaysConsumeSystemBars, DisplayCutout cutout, - @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets, int legacySoftInputMode, int legacySystemUiFlags, @Nullable @InternalInsetsSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; @@ -168,11 +167,6 @@ public class InsetsState implements Parcelable { boolean[] typeVisibilityMap = new boolean[SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); - if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL - && legacyContentInsets != null && legacyStableInsets != null) { - WindowInsets.assignCompatInsets(typeInsetsMap, legacyContentInsets); - WindowInsets.assignCompatInsets(typeMaxInsetsMap, legacyStableInsets); - } for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); if (source == null) { @@ -217,12 +211,7 @@ public class InsetsState implements Parcelable { && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } - public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets, - @SoftInputModeFlags int softInputMode) { - if (sNewInsetsMode == NEW_INSETS_MODE_NONE) { - return legacyVisibleInsets; - } - + public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) { Insets insets = Insets.NONE; for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) { InsetsSource source = mSources.get(type); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index fcab9d11f3d6..d69357bc503d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -80,6 +80,7 @@ import android.graphics.drawable.GradientDrawable; import android.hardware.display.DisplayManagerGlobal; import android.net.Uri; import android.os.Build; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -28745,7 +28746,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * of the screen decorations, these are the current insets for the * content of the window. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, + publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") final Rect mContentInsets = new Rect(); /** @@ -28753,7 +28755,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * of the screen decorations, these are the current insets for the * actual visible parts of the window. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, + publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") final Rect mVisibleInsets = new Rect(); /** @@ -28761,7 +28764,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * of the screen decorations, these are the current insets for the * stable system windows. */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = VERSION_CODES.Q, + publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") final Rect mStableInsets = new Rect(); final DisplayCutout.ParcelableWrapper mDisplayCutout = diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 315c8777ca8f..50202aed36d2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -547,13 +547,11 @@ public final class ViewRootImpl implements ViewParent, boolean mAddedTouchMode; final Rect mTmpFrame = new Rect(); + final Rect mTmpRect = new Rect(); // These are accessed by multiple threads. final Rect mWinFrame; // frame given by window manager. - final Rect mPendingVisibleInsets = new Rect(); - final Rect mPendingStableInsets = new Rect(); - final Rect mPendingContentInsets = new Rect(); final Rect mPendingBackDropFrame = new Rect(); final DisplayCutout.ParcelableWrapper mPendingDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); @@ -562,10 +560,6 @@ public final class ViewRootImpl implements ViewParent, final ViewTreeObserver.InternalInsetsInfo mLastGivenInsets = new ViewTreeObserver.InternalInsetsInfo(); - final Rect mDispatchContentInsets = new Rect(); - final Rect mDispatchStableInsets = new Rect(); - DisplayCutout mDispatchDisplayCutout = DisplayCutout.NO_CUTOUT; - private WindowInsets mLastWindowInsets; // Insets types hidden by legacy window flags or system UI flags. @@ -1019,10 +1013,7 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } - mPendingContentInsets.set(mAttachInfo.mContentInsets); - mPendingStableInsets.set(mAttachInfo.mStableInsets); mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout); - mPendingVisibleInsets.set(0, 0, 0, 0); mAttachInfo.mAlwaysConsumeSystemBars = (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0; mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars; @@ -2200,45 +2191,20 @@ public final class ViewRootImpl implements ViewParent, /* package */ WindowInsets getWindowInsets(boolean forceConstruct) { if (mLastWindowInsets == null || forceConstruct) { - mDispatchContentInsets.set(mAttachInfo.mContentInsets); - mDispatchStableInsets.set(mAttachInfo.mStableInsets); - mDispatchDisplayCutout = mAttachInfo.mDisplayCutout.get(); - - Rect contentInsets = mDispatchContentInsets; - Rect stableInsets = mDispatchStableInsets; - DisplayCutout displayCutout = mDispatchDisplayCutout; - // For dispatch we preserve old logic, but for direct requests from Views we allow to - // immediately use pending insets. This is such that getRootWindowInsets returns the - // result from the layout hint before we ran a traversal shortly after adding a window. - if (!forceConstruct - && (!mPendingContentInsets.equals(contentInsets) || - !mPendingStableInsets.equals(stableInsets) || - !mPendingDisplayCutout.get().equals(displayCutout))) { - contentInsets = mPendingContentInsets; - stableInsets = mPendingStableInsets; - displayCutout = mPendingDisplayCutout.get(); - } - contentInsets = ensureInsetsNonNegative(contentInsets, "content"); - stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); mLastWindowInsets = mInsetsController.calculateInsets( mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeSystemBars, displayCutout, - contentInsets, stableInsets, mWindowAttributes.softInputMode, - (mWindowAttributes.systemUiVisibility + mAttachInfo.mAlwaysConsumeSystemBars, mPendingDisplayCutout.get(), + mWindowAttributes.softInputMode, (mWindowAttributes.systemUiVisibility | mWindowAttributes.subtreeSystemUiVisibility)); - } - return mLastWindowInsets; - } - private Rect ensureInsetsNonNegative(Rect insets, String kind) { - if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) { - Log.wtf(mTag, "Negative " + kind + "Insets: " + insets + ", mFirst=" + mFirst); - return new Rect(Math.max(0, insets.left), - Math.max(0, insets.top), - Math.max(0, insets.right), - Math.max(0, insets.bottom)); + Rect visibleInsets = mInsetsController.calculateVisibleInsets( + mWindowAttributes.softInputMode); + + mAttachInfo.mVisibleInsets.set(visibleInsets); + mAttachInfo.mContentInsets.set(mLastWindowInsets.getSystemWindowInsets().toRect()); + mAttachInfo.mStableInsets.set(mLastWindowInsets.getStableInsets().toRect()); } - return insets; + return mLastWindowInsets; } public void dispatchApplyInsets(View host) { @@ -2262,12 +2228,6 @@ public final class ViewRootImpl implements ViewParent, == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; } - private void updateVisibleInsets() { - Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets, - mWindowAttributes.softInputMode); - mAttachInfo.mVisibleInsets.set(visibleInsets); - } - @VisibleForTesting public InsetsController getInsetsController() { return mInsetsController; @@ -2403,7 +2363,7 @@ public final class ViewRootImpl implements ViewParent, // Execute enqueued actions on every traversal in case a detached view enqueued an action getRunQueue().executeActions(mAttachInfo.mHandler); - boolean insetsChanged = false; + boolean cutoutChanged = false; boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if (layoutRequested) { @@ -2416,22 +2376,8 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mInTouchMode = !mAddedTouchMode; ensureTouchModeLocally(mAddedTouchMode); } else { - if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) { - insetsChanged = true; - } - if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) { - insetsChanged = true; - } if (!mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout)) { - insetsChanged = true; - } - if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { - updateVisibleInsets(); - if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " - + mAttachInfo.mVisibleInsets); - } - if (mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars) { - insetsChanged = true; + cutoutChanged = true; } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { @@ -2494,7 +2440,6 @@ public final class ViewRootImpl implements ViewParent, } if (mApplyInsetsRequested) { - updateVisibleInsets(); dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so @@ -2562,8 +2507,8 @@ public final class ViewRootImpl implements ViewParent, controlInsetsForCompatibility(params); } - if (mFirst || windowShouldResize || insetsChanged || - viewVisibilityChanged || params != null || mForceNextWindowRelayout) { + if (mFirst || windowShouldResize || viewVisibilityChanged || cutoutChanged || params != null + || mForceNextWindowRelayout) { mForceNextWindowRelayout = false; if (isViewVisible) { @@ -2585,7 +2530,7 @@ public final class ViewRootImpl implements ViewParent, } boolean hwInitialized = false; - boolean contentInsetsChanged = false; + boolean dispatchApplyInsets = false; boolean hadSurface = mSurface.isValid(); try { @@ -2608,9 +2553,6 @@ public final class ViewRootImpl implements ViewParent, relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString() - + " content=" + mPendingContentInsets.toShortString() - + " visible=" + mPendingVisibleInsets.toShortString() - + " stable=" + mPendingStableInsets.toShortString() + " cutout=" + mPendingDisplayCutout.get().toString() + " surface=" + mSurface); @@ -2627,14 +2569,7 @@ public final class ViewRootImpl implements ViewParent, updatedConfiguration = true; } - contentInsetsChanged = !mPendingContentInsets.equals( - mAttachInfo.mContentInsets); - final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals( - mAttachInfo.mVisibleInsets); - final boolean stableInsetsChanged = !mPendingStableInsets.equals( - mAttachInfo.mStableInsets); - final boolean cutoutChanged = !mPendingDisplayCutout.equals( - mAttachInfo.mDisplayCutout); + cutoutChanged = !mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout); surfaceSizeChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0; final boolean alwaysConsumeSystemBarsChanged = @@ -2645,42 +2580,25 @@ public final class ViewRootImpl implements ViewParent, surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()) && mSurface.isValid(); - if (contentInsetsChanged) { - mAttachInfo.mContentInsets.set(mPendingContentInsets); - if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: " - + mAttachInfo.mContentInsets); - } - if (stableInsetsChanged) { - mAttachInfo.mStableInsets.set(mPendingStableInsets); - if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: " - + mAttachInfo.mStableInsets); - // Need to relayout with content insets. - contentInsetsChanged = true; - } if (cutoutChanged) { mAttachInfo.mDisplayCutout.set(mPendingDisplayCutout); if (DEBUG_LAYOUT) { Log.v(mTag, "DisplayCutout changing to: " + mAttachInfo.mDisplayCutout); } // Need to relayout with content insets. - contentInsetsChanged = true; + dispatchApplyInsets = true; } if (alwaysConsumeSystemBarsChanged) { mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars; - contentInsetsChanged = true; + dispatchApplyInsets = true; } - if (contentInsetsChanged || mLastSystemUiVisibility != + if (dispatchApplyInsets || mLastSystemUiVisibility != mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) { mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; dispatchApplyInsets(host); // We applied insets so force contentInsetsChanged to ensure the // hierarchy is measured below. - contentInsetsChanged = true; - } - if (visibleInsetsChanged) { - updateVisibleInsets(); - if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: " - + mAttachInfo.mVisibleInsets); + dispatchApplyInsets = true; } if (colorModeChanged && mAttachInfo.mThreadedRenderer != null) { mAttachInfo.mThreadedRenderer.setWideGamut( @@ -2771,7 +2689,8 @@ public final class ViewRootImpl implements ViewParent, && mWinFrame.height() == mPendingBackDropFrame.height(); // TODO: Need cutout? startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame, - mPendingVisibleInsets, mPendingStableInsets, mResizeMode); + mLastWindowInsets.getSystemWindowInsets().toRect(), + mLastWindowInsets.getStableInsets().toRect(), mResizeMode); } else { // We shouldn't come here, but if we come we should end the resize. endDragResizing(); @@ -2862,7 +2781,7 @@ public final class ViewRootImpl implements ViewParent, boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() - || mHeight != host.getMeasuredHeight() || contentInsetsChanged || + || mHeight != host.getMeasuredHeight() || dispatchApplyInsets || updatedConfiguration) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); @@ -2871,7 +2790,7 @@ public final class ViewRootImpl implements ViewParent, + mWidth + " measuredWidth=" + host.getMeasuredWidth() + " mHeight=" + mHeight + " measuredHeight=" + host.getMeasuredHeight() - + " coveredInsetsChanged=" + contentInsetsChanged); + + " dispatchApplyInsets=" + dispatchApplyInsets); // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); @@ -4917,12 +4836,9 @@ public final class ViewRootImpl implements ViewParent, // Recycled in the fall through... SomeArgs args = (SomeArgs) msg.obj; if (mWinFrame.equals(args.arg1) - && mPendingContentInsets.equals(args.arg2) - && mPendingStableInsets.equals(args.arg6) && mPendingDisplayCutout.get().equals(args.arg9) - && mPendingVisibleInsets.equals(args.arg3) && mPendingBackDropFrame.equals(args.arg8) - && args.arg4 == null + && mLastReportedMergedConfiguration.equals(args.arg4) && args.argi1 == 0 && mDisplay.getDisplayId() == args.argi3) { break; @@ -4950,16 +4866,10 @@ public final class ViewRootImpl implements ViewParent, } final boolean framesChanged = !mWinFrame.equals(args.arg1) - || !mPendingContentInsets.equals(args.arg2) - || !mPendingStableInsets.equals(args.arg6) - || !mPendingDisplayCutout.get().equals(args.arg9) - || !mPendingVisibleInsets.equals(args.arg3); + || !mPendingDisplayCutout.get().equals(args.arg9); setFrame((Rect) args.arg1); - mPendingContentInsets.set((Rect) args.arg2); - mPendingStableInsets.set((Rect) args.arg6); mPendingDisplayCutout.set((DisplayCutout) args.arg9); - mPendingVisibleInsets.set((Rect) args.arg3); mPendingBackDropFrame.set((Rect) args.arg8); mForceNextWindowRelayout = args.argi1 != 0; mPendingAlwaysConsumeSystemBars = args.argi2 != 0; @@ -7413,10 +7323,9 @@ public final class ViewRootImpl implements ViewParent, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, - mTmpFrame, mPendingContentInsets, mPendingVisibleInsets, - mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout, - mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mSurfaceSize, - mBlastSurfaceControl); + mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame, + mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets, + mSurfaceSize, mBlastSurfaceControl); if (mSurfaceControl.isValid()) { if (!mUseBLASTAdapter) { mSurface.copyFrom(mSurfaceControl); @@ -7437,9 +7346,6 @@ public final class ViewRootImpl implements ViewParent, if (mTranslator != null) { mTranslator.translateRectInScreenToAppWinFrame(mTmpFrame); - mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets); - mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets); - mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets); } setFrame(mTmpFrame); mInsetsController.onStateChanged(mTempInsets); diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 4050da1b5cb1..8bf1ade876ca 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -278,7 +278,7 @@ public final class WindowManagerImpl implements WindowManager { if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL) { return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars, displayCutout.get(), - systemWindowInsets, stableInsets, SOFT_INPUT_ADJUST_NOTHING, + SOFT_INPUT_ADJUST_NOTHING, SYSTEM_UI_FLAG_VISIBLE, null /* typeSideMap */); } else { return new WindowInsets.Builder() diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 7d97a91b7435..8943da4a2fc0 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -755,8 +755,9 @@ public class Toast { /** * Callback object to be called when the toast is shown or hidden. * - * Callback methods will be called on the looper thread provided on construction. + * <p>Callback methods will be called on the looper thread used for the {@link Toast} object. * + * @see #makeText(Context, Looper, CharSequence, int) * @see #addCallback(Callback) */ public abstract static class Callback { diff --git a/core/java/android/window/IDisplayAreaOrganizer.aidl b/core/java/android/window/IDisplayAreaOrganizer.aidl new file mode 100644 index 000000000000..1045ab2fb509 --- /dev/null +++ b/core/java/android/window/IDisplayAreaOrganizer.aidl @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.window.IWindowContainer; + +/** + * Interface for WindowManager to delegate control of display areas. + * {@hide} + */ +oneway interface IDisplayAreaOrganizer { + void onDisplayAreaAppeared(in IWindowContainer displayArea); + void onDisplayAreaVanished(in IWindowContainer displayArea); +} diff --git a/core/java/android/window/IDisplayAreaOrganizerController.aidl b/core/java/android/window/IDisplayAreaOrganizerController.aidl new file mode 100644 index 000000000000..fc6fbef39ce2 --- /dev/null +++ b/core/java/android/window/IDisplayAreaOrganizerController.aidl @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.window; + +import android.window.IDisplayAreaOrganizer; + +/** @hide */ +interface IDisplayAreaOrganizerController { + + /** Register a DisplayAreaOrganizer to manage display areas for a given feature. */ + void registerOrganizer(in IDisplayAreaOrganizer organizer, int displayAreaFeature); +} diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl index 0a04462c4209..fcf4830fba60 100644 --- a/core/java/android/window/ITaskOrganizer.aidl +++ b/core/java/android/window/ITaskOrganizer.aidl @@ -25,8 +25,8 @@ import android.window.IWindowContainer; * {@hide} */ oneway interface ITaskOrganizer { - void taskAppeared(in ActivityManager.RunningTaskInfo taskInfo); - void taskVanished(in ActivityManager.RunningTaskInfo taskInfo); + void onTaskAppeared(in ActivityManager.RunningTaskInfo taskInfo); + void onTaskVanished(in ActivityManager.RunningTaskInfo taskInfo); /** * Will fire when core attributes of a Task's info change. Relevant properties include the diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl index 4b47924dc5a6..7f4b26dba479 100644 --- a/core/java/android/window/IWindowOrganizerController.aidl +++ b/core/java/android/window/IWindowOrganizerController.aidl @@ -16,6 +16,7 @@ package android.window; +import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; import android.window.IWindowContainerTransactionCallback; import android.window.WindowContainerTransaction; @@ -43,4 +44,7 @@ interface IWindowOrganizerController { /** @return An interface enabling the management of task organizers. */ ITaskOrganizerController getTaskOrganizerController(); + + /** @return An interface enabling the management of display area organizers. */ + IDisplayAreaOrganizerController getDisplayAreaOrganizerController(); } diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java index b8969c162309..4bd5b29f98ac 100644 --- a/core/java/android/window/WindowOrganizer.java +++ b/core/java/android/window/WindowOrganizer.java @@ -152,6 +152,50 @@ public class WindowOrganizer { } } }; + } + + /** Class for organizing display areas. */ + public static class DisplayAreaOrganizer { + + public static final int FEATURE_UNDEFINED = -1; + public static final int FEATURE_SYSTEM_FIRST = 0; + // The Root display area on a display + public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST; + // Display area hosting the task container. + public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1; + // Display area hosting non-activity window tokens. + public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2; + + public static final int FEATURE_SYSTEM_LAST = 10_000; + + // Vendor specific display area definition can start with this value. + public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1; + + /** @hide */ + @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) + public static void registerOrganizer( + IDisplayAreaOrganizer organizer, int displayAreaFeature) throws RemoteException { + getController().registerOrganizer(organizer, displayAreaFeature); + } + + /** @hide */ + private static IDisplayAreaOrganizerController getController() { + return IDisplayAreaOrganizerControllerSingleton.get(); + } + + private static final Singleton<IDisplayAreaOrganizerController> + IDisplayAreaOrganizerControllerSingleton = + new Singleton<IDisplayAreaOrganizerController>() { + @Override + protected IDisplayAreaOrganizerController create() { + try { + return getWindowOrganizerController() + .getDisplayAreaOrganizerController(); + } catch (RemoteException e) { + return null; + } + } + }; } } diff --git a/core/res/res/drawable/resolver_turn_on_work_button_ripple_background.xml b/core/res/res/drawable/resolver_turn_on_work_button_ripple_background.xml new file mode 100644 index 000000000000..786f5e676c36 --- /dev/null +++ b/core/res/res/drawable/resolver_turn_on_work_button_ripple_background.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?attr/colorAccent"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <solid android:color="?attr/colorControlHighlight" /> + <corners android:radius="?attr/buttonCornerRadius" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index af803fcf09cc..210feaf092a7 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -57,11 +57,11 @@ android:text="@string/resolver_switch_on_work" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:background="@null" android:fontFamily="@string/config_headlineFontFamilyMedium" android:textSize="14sp" android:textColor="?attr/colorAccent" - android:layout_centerHorizontal="true" /> + android:layout_centerHorizontal="true" + android:background="@drawable/resolver_turn_on_work_button_ripple_background"/> <ProgressBar android:id="@+id/resolver_empty_state_progress" style="@style/Widget.Material.Light.ProgressBar" diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7ec7eccd7f8d..06f776013233 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -416,10 +416,10 @@ <!-- Content title for a notification. This notification indicates that the device owner has changed the location settings. [CHAR LIMIT=NONE] --> - <string name="location_changed_notification_title">Location settings changed by your admin</string> + <string name="location_changed_notification_title">Apps can access your location</string> <!-- Content text for a notification. Tapping opens device location settings. [CHAR LIMIT=NONE] --> - <string name="location_changed_notification_text">Tap to see your location settings.</string> + <string name="location_changed_notification_text">Contact your IT admin to learn more</string> <!-- Feature Id for Country Detector. [CHAR LIMIT=NONE]--> <string name="country_detector">Country Detector</string> diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index 7f0e0d2f54c7..03aba25bf9f7 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -74,7 +74,7 @@ public class ImeInsetsSourceConsumerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - rect, rect, SOFT_INPUT_ADJUST_RESIZE, 0); + SOFT_INPUT_ADJUST_RESIZE, 0); mImeConsumer = new ImeInsetsSourceConsumer( new InsetsState(), Transaction::new, mController); }); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index b449bb00a85d..34a1016f0ae9 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -162,7 +162,7 @@ public class InsetsControllerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - rect, rect, SOFT_INPUT_ADJUST_RESIZE, 0); + SOFT_INPUT_ADJUST_RESIZE, 0); mController.onFrameChanged(new Rect(0, 0, 100, 100)); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 1d8e0a3186e8..721dc98ff4d1 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -73,7 +73,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setVisible(true); SparseIntArray typeSideMap = new SparseIntArray(); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, typeSideMap); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); assertEquals(ISIDE_TOP, typeSideMap.get(ITYPE_STATUS_BAR)); @@ -92,7 +92,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 100, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, null); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, null); assertEquals(100, insets.getStableInsetBottom()); assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(Type.systemBars())); assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); @@ -111,7 +111,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, null, null, 0, 0, null); + false, DisplayCutout.NO_CUTOUT, 0, 0, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -127,7 +127,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, 0, null); + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0, null); assertEquals(0, insets.getSystemWindowInsetBottom()); assertEquals(100, insets.getInsets(ime()).bottom); assertTrue(insets.isVisible(ime())); @@ -143,11 +143,11 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300)); mState.getSource(ITYPE_IME).setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, + false, DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, SYSTEM_UI_FLAG_LAYOUT_STABLE, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, - DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, + DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_NOTHING, 0 /* legacySystemUiFlags */, null); assertEquals(0, insets.getSystemWindowInsetTop()); } @@ -161,7 +161,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_IME).setVisible(true); mState.removeSource(ITYPE_IME); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, - DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, 0, null); + DisplayCutout.NO_CUTOUT, SOFT_INPUT_ADJUST_RESIZE, 0, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } @@ -255,7 +255,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); Rect visibleInsets = mState.calculateVisibleInsets( - new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN); + new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_PAN); assertEquals(new Rect(0, 100, 0, 100), visibleInsets); } } @@ -273,7 +273,7 @@ public class InsetsStateTest { mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300)); mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true); Rect visibleInsets = mState.calculateVisibleInsets( - new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING); + new Rect(0, 0, 100, 300), SOFT_INPUT_ADJUST_NOTHING); assertEquals(new Rect(0, 100, 0, 0), visibleInsets); } } diff --git a/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-0.5-crop-11.png b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-0.5-crop-11.png Binary files differnew file mode 100644 index 000000000000..1acc59d167fc --- /dev/null +++ b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-0.5-crop-11.png diff --git a/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-169.png b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-169.png Binary files differnew file mode 100644 index 000000000000..4ab9ca4fa580 --- /dev/null +++ b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-169.png diff --git a/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-43.png b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-43.png Binary files differnew file mode 100644 index 000000000000..d74e673a9ce1 --- /dev/null +++ b/docs/html/reference/images/camera2/metadata/android.control.zoomRatio/zoom-ratio-2-crop-43.png diff --git a/libs/protoutil/include/android/util/ProtoOutputStream.h b/libs/protoutil/include/android/util/ProtoOutputStream.h index 42bf03e6de05..f4a358de1c41 100644 --- a/libs/protoutil/include/android/util/ProtoOutputStream.h +++ b/libs/protoutil/include/android/util/ProtoOutputStream.h @@ -90,6 +90,7 @@ class ProtoOutputStream { public: ProtoOutputStream(); + ProtoOutputStream(sp<EncodedBuffer> buffer); ~ProtoOutputStream(); /** diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp index 7ffd8874a8fb..96b54c63a836 100644 --- a/libs/protoutil/src/EncodedBuffer.cpp +++ b/libs/protoutil/src/EncodedBuffer.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "libprotoutil" #include <stdlib.h> +#include <sys/mman.h> #include <android/util/EncodedBuffer.h> #include <android/util/protobuf.h> @@ -82,14 +83,16 @@ EncodedBuffer::Pointer::copy() const } // =========================================================== -EncodedBuffer::EncodedBuffer() : EncodedBuffer(0) +EncodedBuffer::EncodedBuffer() : EncodedBuffer(BUFFER_SIZE) { } EncodedBuffer::EncodedBuffer(size_t chunkSize) :mBuffers() { - mChunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize; + // Align chunkSize to memory page size + chunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize; + mChunkSize = (chunkSize / PAGE_SIZE + ((chunkSize % PAGE_SIZE == 0) ? 0 : 1)) * PAGE_SIZE; mWp = Pointer(mChunkSize); mEp = Pointer(mChunkSize); } @@ -98,7 +101,7 @@ EncodedBuffer::~EncodedBuffer() { for (size_t i=0; i<mBuffers.size(); i++) { uint8_t* buf = mBuffers[i]; - free(buf); + munmap(buf, mChunkSize); } } @@ -135,7 +138,10 @@ EncodedBuffer::writeBuffer() if (mWp.index() > mBuffers.size()) return NULL; uint8_t* buf = NULL; if (mWp.index() == mBuffers.size()) { - buf = (uint8_t*)malloc(mChunkSize); + // Use mmap instead of malloc to ensure memory alignment i.e. no fragmentation so that + // the mem region can be immediately reused by the allocator after calling munmap() + buf = (uint8_t*)mmap(NULL, mChunkSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); if (buf == NULL) return NULL; // This indicates NO_MEMORY diff --git a/libs/protoutil/src/ProtoOutputStream.cpp b/libs/protoutil/src/ProtoOutputStream.cpp index ea9b79a0353f..fcf82eed4eb1 100644 --- a/libs/protoutil/src/ProtoOutputStream.cpp +++ b/libs/protoutil/src/ProtoOutputStream.cpp @@ -26,8 +26,12 @@ namespace android { namespace util { -ProtoOutputStream::ProtoOutputStream() - :mBuffer(new EncodedBuffer()), +ProtoOutputStream::ProtoOutputStream(): ProtoOutputStream(new EncodedBuffer()) +{ +} + +ProtoOutputStream::ProtoOutputStream(sp<EncodedBuffer> buffer) + :mBuffer(buffer), mCopyBegin(0), mCompact(false), mDepth(0), diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index da93db7b37f6..f2d40cef83c7 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -300,11 +300,6 @@ android:exported="false" android:permission="com.android.systemui.permission.SELF" /> - <service android:name=".assist.AssistHandleService" - android:exported="true" - android:enabled="false" - /> - <!-- started from PhoneWindowManager TODO: Should have an android:permission attribute --> <service android:name=".screenshot.TakeScreenshotService" diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml new file mode 100644 index 000000000000..9c2d244cb8ec --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_media_header.xml @@ -0,0 +1,153 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> + +<!-- Layout for media controls on the lockscreen --> +<com.android.systemui.statusbar.notification.stack.MediaHeaderView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="0dp" + android:paddingEnd="0dp" + android:focusable="true" + android:clickable="true" +> + + <!-- Background views required by ActivatableNotificationView. --> + <com.android.systemui.statusbar.notification.row.NotificationBackgroundView + android:id="@+id/backgroundNormal" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <com.android.systemui.statusbar.notification.row.NotificationBackgroundView + android:id="@+id/backgroundDimmed" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <com.android.systemui.statusbar.notification.FakeShadowView + android:id="@+id/fake_shadow" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + + <!-- Layout for media controls. --> + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/keyguard_media_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center_horizontal|fill_vertical" + android:padding="16dp" + > + <ImageView + android:id="@+id/album_art" + android:layout_width="@dimen/qs_media_album_size" + android:layout_height="@dimen/qs_media_album_size" + android:layout_marginRight="16dp" + android:layout_weight="0" + /> + + <!-- Media information --> + <LinearLayout + android:orientation="vertical" + android:layout_width="0dp" + android:layout_height="@dimen/qs_media_album_size" + android:layout_weight="1" + > + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + > + <com.android.internal.widget.CachingIconView + android:id="@+id/icon" + android:layout_width="16dp" + android:layout_height="16dp" + android:layout_marginEnd="5dp" + /> + <TextView + android:id="@+id/app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:singleLine="true" + /> + </LinearLayout> + + <!-- Song name --> + <TextView + android:id="@+id/header_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="true" + android:fontFamily="@*android:string/config_headlineFontFamilyMedium" + android:textSize="18sp" + android:paddingBottom="6dp" + android:gravity="center"/> + + <!-- Artist name --> + <TextView + android:id="@+id/header_artist" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:fontFamily="@*android:string/config_bodyFontFamily" + android:textSize="14sp" + android:singleLine="true" + /> + </LinearLayout> + + <!-- Controls --> + <LinearLayout + android:id="@+id/media_actions" + android:orientation="horizontal" + android:layoutDirection="ltr" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:gravity="center" + android:layout_gravity="center" + > + <ImageButton + style="@android:style/Widget.Material.Button.Borderless.Small" + android:layout_width="48dp" + android:layout_height="48dp" + android:gravity="center" + android:visibility="gone" + android:id="@+id/action0" + /> + <ImageButton + style="@android:style/Widget.Material.Button.Borderless.Small" + android:layout_width="48dp" + android:layout_height="48dp" + android:gravity="center" + android:visibility="gone" + android:id="@+id/action1" + /> + <ImageButton + style="@android:style/Widget.Material.Button.Borderless.Small" + android:layout_width="48dp" + android:layout_height="48dp" + android:gravity="center" + android:visibility="gone" + android:id="@+id/action2" + /> + </LinearLayout> + </LinearLayout> + +</com.android.systemui.statusbar.notification.stack.MediaHeaderView> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 38c931a9847f..c8c35c704297 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2693,7 +2693,7 @@ <string name="controls_structure_tooltip">Swipe to see more</string> <!-- Message to tell the user to wait while systemui attempts to load a set of - recommended controls [CHAR_LIMIT=30] --> + recommended controls [CHAR_LIMIT=60] --> <string name="controls_seeding_in_progress">Loading recommendations</string> <!-- Close the controls associated with a specific media session [CHAR_LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java new file mode 100644 index 000000000000..b0017269ff7e --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMediaPlayer.java @@ -0,0 +1,266 @@ +/* + * 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.keyguard; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.media.MediaMetadata; +import android.util.Log; +import android.view.View; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.core.graphics.drawable.RoundedBitmapDrawable; +import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory; +import androidx.palette.graphics.Palette; + +import com.android.internal.util.ContrastColorUtil; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.statusbar.notification.MediaNotificationProcessor; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.stack.MediaHeaderView; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Media controls to display on the lockscreen + * + * TODO: Should extend MediaControlPanel to avoid code duplication. + * Unfortunately, it isn't currently possible because the ActivatableNotificationView background is + * different. + */ +@Singleton +public class KeyguardMediaPlayer { + + private static final String TAG = "KeyguardMediaPlayer"; + // Buttons that can be displayed on lock screen media controls. + private static final int[] ACTION_IDS = {R.id.action0, R.id.action1, R.id.action2}; + + private final Context mContext; + private final Executor mBackgroundExecutor; + private float mAlbumArtRadius; + private int mAlbumArtSize; + private View mMediaNotifView; + + @Inject + public KeyguardMediaPlayer(Context context, @Background Executor backgroundExecutor) { + mContext = context; + mBackgroundExecutor = backgroundExecutor; + loadDimens(); + } + + /** Binds media controls to a view hierarchy. */ + public void bindView(View v) { + if (mMediaNotifView != null) { + throw new IllegalStateException("cannot bind views, already bound"); + } + mMediaNotifView = v; + loadDimens(); + } + + /** Unbinds media controls. */ + public void unbindView() { + if (mMediaNotifView == null) { + throw new IllegalStateException("cannot unbind views, nothing bound"); + } + mMediaNotifView = null; + } + + /** Clear the media controls because there isn't an active session. */ + public void clearControls() { + if (mMediaNotifView != null) { + mMediaNotifView.setVisibility(View.GONE); + } + } + + /** + * Update the media player + * + * TODO: consider registering a MediaLister instead of exposing this update method. + * + * @param entry Media notification that will be used to update the player + * @param appIcon Icon for the app playing the media + * @param mediaMetadata Media metadata that will be used to update the player + */ + public void updateControls(NotificationEntry entry, Icon appIcon, + MediaMetadata mediaMetadata) { + if (mMediaNotifView == null) { + throw new IllegalStateException("cannot update controls, views not bound"); + } + if (mediaMetadata == null) { + throw new IllegalArgumentException("media metadata was null"); + } + mMediaNotifView.setVisibility(View.VISIBLE); + + Notification notif = entry.getSbn().getNotification(); + + // Computed foreground and background color based on album art. + int fgColor = notif.color; + int bgColor = entry.getRow() == null ? -1 : entry.getRow().getCurrentBackgroundTint(); + Bitmap artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART); + if (artworkBitmap == null) { + artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); + } + if (artworkBitmap != null) { + // If we have art, get colors from that + Palette p = MediaNotificationProcessor.generateArtworkPaletteBuilder(artworkBitmap) + .generate(); + Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(p); + bgColor = swatch.getRgb(); + fgColor = MediaNotificationProcessor.selectForegroundColor(bgColor, p); + } + // Make sure colors will be legible + boolean isDark = !ContrastColorUtil.isColorLight(bgColor); + fgColor = ContrastColorUtil.resolveContrastColor(mContext, fgColor, bgColor, + isDark); + fgColor = ContrastColorUtil.ensureTextContrast(fgColor, bgColor, isDark); + + // Album art + ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); + if (albumView != null) { + // Resize art in a background thread + final Bitmap bm = artworkBitmap; + mBackgroundExecutor.execute(() -> processAlbumArt(bm, albumView)); + } + + // App icon + ImageView appIconView = mMediaNotifView.findViewById(R.id.icon); + if (appIconView != null) { + Drawable iconDrawable = appIcon.loadDrawable(mContext); + iconDrawable.setTint(fgColor); + appIconView.setImageDrawable(iconDrawable); + } + + // App name + TextView appName = mMediaNotifView.findViewById(R.id.app_name); + if (appName != null) { + Notification.Builder builder = Notification.Builder.recoverBuilder(mContext, notif); + String appNameString = builder.loadHeaderAppName(); + appName.setText(appNameString); + appName.setTextColor(fgColor); + } + + // Song name + TextView titleText = mMediaNotifView.findViewById(R.id.header_title); + if (titleText != null) { + String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE); + titleText.setText(songName); + titleText.setTextColor(fgColor); + } + + // Artist name + TextView artistText = mMediaNotifView.findViewById(R.id.header_artist); + if (artistText != null) { + String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST); + artistText.setText(artistName); + artistText.setTextColor(fgColor); + } + + // Background color + if (mMediaNotifView instanceof MediaHeaderView) { + MediaHeaderView head = (MediaHeaderView) mMediaNotifView; + head.setBackgroundColor(bgColor); + } + + // Control buttons + final List<Icon> icons = new ArrayList<>(); + final List<PendingIntent> intents = new ArrayList<>(); + Notification.Action[] actions = notif.actions; + final int[] actionsToShow = notif.extras.getIntArray(Notification.EXTRA_COMPACT_ACTIONS); + + for (int i = 0; i < ACTION_IDS.length; i++) { + if (actionsToShow != null && actions != null && i < actionsToShow.length + && actionsToShow[i] < actions.length) { + final int idx = actionsToShow[i]; + icons.add(actions[idx].getIcon()); + intents.add(actions[idx].actionIntent); + } else { + icons.add(null); + intents.add(null); + } + } + + Context packageContext = entry.getSbn().getPackageContext(mContext); + for (int i = 0; i < ACTION_IDS.length; i++) { + ImageButton button = mMediaNotifView.findViewById(ACTION_IDS[i]); + if (button == null) { + continue; + } + Icon icon = icons.get(i); + if (icon == null) { + button.setVisibility(View.GONE); + } else { + button.setVisibility(View.VISIBLE); + button.setImageDrawable(icon.loadDrawable(packageContext)); + button.setImageTintList(ColorStateList.valueOf(fgColor)); + final PendingIntent intent = intents.get(i); + if (intent != null) { + button.setOnClickListener(v -> { + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + Log.d(TAG, "failed to send action intent", e); + } + }); + } + } + } + } + + /** + * Process album art for layout + * @param albumArt bitmap to use for album art + * @param albumView view to hold the album art + */ + private void processAlbumArt(Bitmap albumArt, ImageView albumView) { + RoundedBitmapDrawable roundedDrawable = null; + if (albumArt != null) { + Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); + Bitmap scaled = Bitmap.createScaledBitmap(original, mAlbumArtSize, mAlbumArtSize, + false); + roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled); + roundedDrawable.setCornerRadius(mAlbumArtRadius); + } else { + Log.e(TAG, "No album art available"); + } + + // Now that it's resized, update the UI + final RoundedBitmapDrawable result = roundedDrawable; + albumView.post(() -> { + albumView.setImageDrawable(result); + albumView.setVisibility(result == null ? View.GONE : View.VISIBLE); + }); + } + + private void loadDimens() { + mAlbumArtRadius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); + mAlbumArtSize = (int) mContext.getResources().getDimension( + R.dimen.qs_media_album_size); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 18357a95714b..3afe19f926ec 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -966,17 +966,20 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean changed = false; if (enabled && (oldIntent == null)) { - Intent intent = new Intent( - DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE); - ComponentName profileOwnerComponent = - mDevicePolicyManager.getProfileOwnerAsUser(userId); - intent.setComponent(profileOwnerComponent); - ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0); - if (resolveInfo != null) { - Intent newIntent = new Intent(); - newIntent.setComponent(profileOwnerComponent); - mSecondaryLockscreenRequirement.put(userId, newIntent); - changed = true; + ComponentName poComponent = mDevicePolicyManager.getProfileOwnerAsUser(userId); + if (poComponent == null) { + Log.e(TAG, "No profile owner found for User " + userId); + } else { + Intent intent = + new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE) + .setPackage(poComponent.getPackageName()); + ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0); + if (resolveInfo != null && resolveInfo.serviceInfo != null) { + Intent launchIntent = + new Intent().setComponent(resolveInfo.serviceInfo.getComponentName()); + mSecondaryLockscreenRequirement.put(userId, launchIntent); + changed = true; + } } } else if (!enabled && (oldIntent != null)) { mSecondaryLockscreenRequirement.put(userId, null); diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index e2b12daf441e..59af458e2402 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -86,7 +86,8 @@ public class ExpandHelper implements Gefingerpoken { private float mInitialTouchSpan; private float mLastFocusY; private float mLastSpanY; - private int mTouchSlop; + private final int mTouchSlop; + private final float mSlopMultiplier; private float mLastMotionY; private float mPullGestureMinXSpan; private Callback mCallback; @@ -177,6 +178,7 @@ public class ExpandHelper implements Gefingerpoken { final ViewConfiguration configuration = ViewConfiguration.get(mContext); mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getAmbiguousGestureMultiplier(); mSGD = new ScaleGestureDetector(context, mScaleGestureListener); mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(), @@ -258,6 +260,13 @@ public class ExpandHelper implements Gefingerpoken { mScrollAdapter = adapter; } + private float getTouchSlop(MotionEvent event) { + // Adjust the touch slop if another gesture may be being performed. + return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (!isEnabled()) { @@ -303,7 +312,7 @@ public class ExpandHelper implements Gefingerpoken { if (mWatchingForPull) { final float yDiff = ev.getRawY() - mInitialTouchY; final float xDiff = ev.getRawX() - mInitialTouchX; - if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) { + if (yDiff > getTouchSlop(ev) && yDiff > Math.abs(xDiff)) { if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)"); mWatchingForPull = false; if (mResizedView != null && !isFullyExpanded(mResizedView)) { @@ -431,7 +440,7 @@ public class ExpandHelper implements Gefingerpoken { if (mWatchingForPull) { final float yDiff = ev.getRawY() - mInitialTouchY; final float xDiff = ev.getRawX() - mInitialTouchX; - if (yDiff > mTouchSlop && yDiff > Math.abs(xDiff)) { + if (yDiff > getTouchSlop(ev) && yDiff > Math.abs(xDiff)) { if (DEBUG) Log.v(TAG, "got venetian gesture (dy=" + yDiff + "px)"); mWatchingForPull = false; if (mResizedView != null && !isFullyExpanded(mResizedView)) { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 02a452182d36..d17ca4041b31 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -69,6 +69,7 @@ public class SwipeHelper implements Gefingerpoken { private final FlingAnimationUtils mFlingAnimationUtils; private float mPagingTouchSlop; + private final float mSlopMultiplier; private final Callback mCallback; private final int mSwipeDirection; private final VelocityTracker mVelocityTracker; @@ -84,11 +85,28 @@ public class SwipeHelper implements Gefingerpoken { private float mTranslation = 0; private boolean mMenuRowIntercepting; - private boolean mLongPressSent; - private Runnable mWatchLongPress; private final long mLongPressTimeout; + private boolean mLongPressSent; + private final float[] mDownLocation = new float[2]; + private final Runnable mPerformLongPress = new Runnable() { + + private final int[] mViewOffset = new int[2]; + + @Override + public void run() { + if (mCurrView != null && !mLongPressSent) { + mLongPressSent = true; + if (mCurrView instanceof ExpandableNotificationRow) { + mCurrView.getLocationOnScreen(mViewOffset); + final int x = (int) mDownLocation[0] - mViewOffset[0]; + final int y = (int) mDownLocation[1] - mViewOffset[1]; + mCurrView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); + ((ExpandableNotificationRow) mCurrView).doLongClickCallback(x, y); + } + } + } + }; - final private int[] mTmpPos = new int[2]; private final int mFalsingThreshold; private boolean mTouchAboveFalsingThreshold; private boolean mDisableHwLayers; @@ -102,7 +120,9 @@ public class SwipeHelper implements Gefingerpoken { mHandler = new Handler(); mSwipeDirection = swipeDirection; mVelocityTracker = VelocityTracker.obtain(); - mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop(); + final ViewConfiguration configuration = ViewConfiguration.get(context); + mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); // Extra long-press! mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); @@ -255,10 +275,7 @@ public class SwipeHelper implements Gefingerpoken { } public void cancelLongPress() { - if (mWatchLongPress != null) { - mHandler.removeCallbacks(mWatchLongPress); - mWatchLongPress = null; - } + mHandler.removeCallbacks(mPerformLongPress); } @Override @@ -287,27 +304,9 @@ public class SwipeHelper implements Gefingerpoken { mInitialTouchPos = getPos(ev); mPerpendicularInitialTouchPos = getPerpendicularPos(ev); mTranslation = getTranslation(mCurrView); - if (mWatchLongPress == null) { - mWatchLongPress = new Runnable() { - @Override - public void run() { - if (mCurrView != null && !mLongPressSent) { - mLongPressSent = true; - mCurrView.getLocationOnScreen(mTmpPos); - final int x = (int) ev.getRawX() - mTmpPos[0]; - final int y = (int) ev.getRawY() - mTmpPos[1]; - if (mCurrView instanceof ExpandableNotificationRow) { - mCurrView.sendAccessibilityEvent( - AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); - ExpandableNotificationRow currRow = - (ExpandableNotificationRow) mCurrView; - currRow.doLongClickCallback(x, y); - } - } - } - }; - } - mHandler.postDelayed(mWatchLongPress, mLongPressTimeout); + mDownLocation[0] = ev.getRawX(); + mDownLocation[1] = ev.getRawY(); + mHandler.postDelayed(mPerformLongPress, mLongPressTimeout); } break; @@ -318,7 +317,12 @@ public class SwipeHelper implements Gefingerpoken { float perpendicularPos = getPerpendicularPos(ev); float delta = pos - mInitialTouchPos; float deltaPerpendicular = perpendicularPos - mPerpendicularInitialTouchPos; - if (Math.abs(delta) > mPagingTouchSlop + // Adjust the touch slop if another gesture may be being performed. + final float pagingTouchSlop = + ev.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mPagingTouchSlop * mSlopMultiplier + : mPagingTouchSlop; + if (Math.abs(delta) > pagingTouchSlop && Math.abs(delta) > Math.abs(deltaPerpendicular)) { if (mCallback.canChildBeDragged(mCurrView)) { mCallback.onBeginDrag(mCurrView); @@ -327,6 +331,11 @@ public class SwipeHelper implements Gefingerpoken { mTranslation = getTranslation(mCurrView); } cancelLongPress(); + } else if (ev.getClassification() == MotionEvent.CLASSIFICATION_DEEP_PRESS + && mHandler.hasCallbacks(mPerformLongPress)) { + // Accelerate the long press signal. + cancelLongPress(); + mPerformLongPress.run(); } } break; diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java index 3707d61da37f..8cd89ddabe72 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleBehaviorController.java @@ -23,6 +23,7 @@ import android.content.Context; import android.os.Handler; import android.os.SystemClock; import android.util.Log; +import android.view.accessibility.AccessibilityManager; import androidx.annotation.Nullable; @@ -45,6 +46,8 @@ import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; +import dagger.Lazy; + /** * A class for managing Assistant handle logic. * @@ -73,6 +76,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac private final Provider<AssistHandleViewController> mAssistHandleViewController; private final DeviceConfigHelper mDeviceConfigHelper; private final Map<AssistHandleBehavior, BehaviorController> mBehaviorMap; + private final Lazy<AccessibilityManager> mA11yManager; private boolean mHandlesShowing = false; private long mHandlesLastHiddenAt; @@ -93,6 +97,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac DeviceConfigHelper deviceConfigHelper, Map<AssistHandleBehavior, BehaviorController> behaviorMap, NavigationModeController navigationModeController, + Lazy<AccessibilityManager> a11yManager, DumpManager dumpManager) { mContext = context; mAssistUtils = assistUtils; @@ -100,6 +105,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac mAssistHandleViewController = assistHandleViewController; mDeviceConfigHelper = deviceConfigHelper; mBehaviorMap = behaviorMap; + mA11yManager = a11yManager; mInGesturalMode = QuickStepContract.isGesturalMode( navigationModeController.addListener(this::handleNavigationModeChange)); @@ -211,9 +217,11 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac } private long getShowAndGoDuration() { - return mDeviceConfigHelper.getLong( + long configuredTime = mDeviceConfigHelper.getLong( SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS, DEFAULT_SHOW_AND_GO_DURATION_MS); + return mA11yManager.get().getRecommendedTimeoutMillis( + (int) configuredTime, AccessibilityManager.FLAG_CONTENT_ICONS); } private String getBehaviorMode() { @@ -291,7 +299,7 @@ public final class AssistHandleBehaviorController implements AssistHandleCallbac pw.println(" Phenotype Flags:"); pw.println(" " - + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + + SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS + "(a11y modded)" + "=" + getShowAndGoDuration()); pw.println(" " diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt b/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt deleted file mode 100644 index 9ceafc674d34..000000000000 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistHandleService.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.assist - -import android.app.Service -import android.content.Intent -import android.os.IBinder -import dagger.Lazy -import javax.inject.Inject - -class AssistHandleService @Inject constructor(private val assistManager: Lazy<AssistManager>) - : Service() { - - private val binder = object : IAssistHandleService.Stub() { - override fun requestAssistHandles() { - assistManager.get().requestAssistHandles() - } - } - - override fun onBind(intent: Intent?): IBinder? { - return binder - } -}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java index 96939b010555..6f5a17dca432 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java @@ -16,7 +16,6 @@ package com.android.systemui.assist; -import android.app.Service; import android.content.Context; import android.os.Handler; import android.os.HandlerThread; @@ -34,11 +33,8 @@ import java.util.Map; import javax.inject.Named; import javax.inject.Singleton; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.ClassKey; -import dagger.multibindings.IntoMap; /** Module for dagger injections related to the Assistant. */ @Module @@ -91,9 +87,4 @@ public abstract class AssistModule { static Clock provideSystemClock() { return SystemClock::uptimeMillis; } - - @Binds - @IntoMap - @ClassKey(AssistHandleService.class) - abstract Service bindAssistHandleService(AssistHandleService assistHandleService); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index fcbd9121fcda..077ffd3729e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -403,6 +403,9 @@ public class BubbleData { } private void doRemove(String key, @DismissReason int reason) { + if (DEBUG_BUBBLE_DATA) { + 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)) { @@ -445,15 +448,14 @@ public class BubbleData { if (reason == BubbleController.DISMISS_AGED || reason == BubbleController.DISMISS_USER_GESTURE) { if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "overflowing bubble: " + bubble); + Log.d(TAG, "Overflowing: " + bubble); } mOverflowBubbles.add(0, bubble); bubble.stopInflation(); - if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) { // Remove oldest bubble. if (DEBUG_BUBBLE_DATA) { - Log.d(TAG, "Overflow full. Remove bubble: " + mOverflowBubbles.get( + Log.d(TAG, "Overflow full. Remove: " + mOverflowBubbles.get( mOverflowBubbles.size() - 1)); } mOverflowBubbles.remove(mOverflowBubbles.size() - 1); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index cff371f93f3d..541c8cf19943 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -940,7 +940,6 @@ public class BubbleStackView extends FrameLayout { ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters); animateInFlyoutForBubble(bubble); updatePointerPosition(); - updateOverflowBtnVisibility( /*apply */ true); requestUpdate(); logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); } @@ -951,16 +950,18 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "removeBubble: " + bubble); } // Remove it from the views - int removedIndex = mBubbleContainer.indexOfChild(bubble.getIconView()); - if (removedIndex >= 0) { - mBubbleContainer.removeViewAt(removedIndex); - bubble.cleanupExpandedState(); - bubble.setInflated(false); - logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); - } else { - Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); + for (int i = 0; i < getBubbleCount(); i++) { + View v = mBubbleContainer.getChildAt(i); + if (v instanceof BadgedImageView + && ((BadgedImageView) v).getKey().equals(bubble.getKey())) { + mBubbleContainer.removeViewAt(i); + bubble.cleanupExpandedState(); + bubble.setInflated(false); + logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED); + return; + } } - updateOverflowBtnVisibility(/* apply */ true); + Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble); } private void updateOverflowBtnVisibility(boolean apply) { diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt index 3fd583fa7481..11181e56838e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt @@ -65,11 +65,18 @@ class AllModel( override val elements: List<ElementWrapper> = createWrappers(controls) override fun changeFavoriteStatus(controlId: String, favorite: Boolean) { + val toChange = elements.firstOrNull { + it is ControlWrapper && it.controlStatus.control.controlId == controlId + } as ControlWrapper? + if (favorite == toChange?.controlStatus?.favorite) return if (favorite) { favoriteIds.add(controlId) } else { favoriteIds.remove(controlId) } + toChange?.let { + it.controlStatus.favorite = favorite + } } private fun createWrappers(list: List<ControlStatus>): List<ElementWrapper> { diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index c28705575e86..902b5785c30a 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -217,7 +217,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } @Override - public void taskAppeared(ActivityManager.RunningTaskInfo info) { + public void onTaskAppeared(ActivityManager.RunningTaskInfo info) { Objects.requireNonNull(info, "Requires RunningTaskInfo"); final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds( getAspectRatioOrDefault(info.pictureInPictureParams), @@ -250,7 +250,7 @@ public class PipTaskOrganizer extends ITaskOrganizer.Stub { } @Override - public void taskVanished(ActivityManager.RunningTaskInfo info) { + public void onTaskVanished(ActivityManager.RunningTaskInfo info) { IWindowContainer token = info.token; Objects.requireNonNull(token, "Requires valid IWindowContainer"); if (token.asBinder() != mToken.asBinder()) { diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java index 131f4e1c9d59..3879c164a84c 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/DividerView.java @@ -339,7 +339,6 @@ public class DividerView extends FrameLayout implements OnTouchListener, insets = state.calculateInsets(state.getDisplayFrame(), null /* ignoringVisibilityState */, insets.isRound(), insets.shouldAlwaysConsumeSystemBars(), insets.getDisplayCutout(), - null /* legacyContentInsets */, null /* legacyStableInsets */, 0 /* legacySystemUiFlags */, SOFT_INPUT_ADJUST_NOTHING, null /* typeSideMap */); } diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java index 8bbb548b0ecf..c4089e5dd070 100644 --- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java @@ -82,11 +82,11 @@ class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub { } @Override - public void taskAppeared(RunningTaskInfo taskInfo) { + public void onTaskAppeared(RunningTaskInfo taskInfo) { } @Override - public void taskVanished(RunningTaskInfo taskInfo) { + public void onTaskVanished(RunningTaskInfo taskInfo) { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java index 5adee40613e6..4fa782269c2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java @@ -48,7 +48,8 @@ public class DragDownHelper implements Gefingerpoken { private float mInitialTouchX; private float mInitialTouchY; private boolean mDraggingDown; - private float mTouchSlop; + private final float mTouchSlop; + private final float mSlopMultiplier; private DragDownCallback mDragDownCallback; private View mHost; private final int[] mTemp2 = new int[2]; @@ -62,7 +63,9 @@ public class DragDownHelper implements Gefingerpoken { FalsingManager falsingManager) { mMinDragDistance = context.getResources().getDimensionPixelSize( R.dimen.keyguard_drag_down_min_distance); - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + final ViewConfiguration configuration = ViewConfiguration.get(context); + mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mCallback = callback; mDragDownCallback = dragDownCallback; mHost = host; @@ -85,7 +88,12 @@ public class DragDownHelper implements Gefingerpoken { case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; - if (h > mTouchSlop && h > Math.abs(x - mInitialTouchX)) { + // Adjust the touch slop if another gesture may be being performed. + final float touchSlop = + event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + if (h > touchSlop && h > Math.abs(x - mInitialTouchX)) { mFalsingManager.onNotificatonStartDraggingDown(); mDraggingDown = true; captureStartingChild(mInitialTouchX, mInitialTouchY); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index d8fdf928f6ad..e32d174d7c77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -46,6 +46,7 @@ import android.widget.ImageView; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.statusbar.NotificationVisibility; +import com.android.keyguard.KeyguardMediaPlayer; import com.android.systemui.Dependency; import com.android.systemui.Dumpable; import com.android.systemui.Interpolators; @@ -65,6 +66,7 @@ import com.android.systemui.statusbar.phone.ScrimState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.util.Utils; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -111,6 +113,7 @@ public class NotificationMediaManager implements Dumpable { private ScrimController mScrimController; @Nullable private LockscreenWallpaper mLockscreenWallpaper; + private final KeyguardMediaPlayer mMediaPlayer; private final Executor mMainExecutor; @@ -184,11 +187,13 @@ public class NotificationMediaManager implements Dumpable { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, + KeyguardMediaPlayer keyguardMediaPlayer, @Main Executor mainExecutor, DeviceConfigProxy deviceConfig) { mContext = context; mMediaArtworkProcessor = mediaArtworkProcessor; mKeyguardBypassController = keyguardBypassController; + mMediaPlayer = keyguardMediaPlayer; mMediaListeners = new ArrayList<>(); // TODO: use MediaSessionManager.SessionListener to hook us up to future updates // in session state @@ -468,6 +473,7 @@ public class NotificationMediaManager implements Dumpable { && mBiometricUnlockController.isWakeAndUnlock(); if (mKeyguardStateController.isLaunchTransitionFadingAway() || wakeAndUnlock) { mBackdrop.setVisibility(View.INVISIBLE); + mMediaPlayer.clearControls(); Trace.endSection(); return; } @@ -490,6 +496,14 @@ public class NotificationMediaManager implements Dumpable { } } + NotificationEntry entry = mEntryManager + .getActiveNotificationUnfiltered(mMediaNotificationKey); + if (entry != null) { + mMediaPlayer.updateControls(entry, getMediaIcon(), mediaMetadata); + } else { + mMediaPlayer.clearControls(); + } + // Process artwork on a background thread and send the resulting bitmap to // finishUpdateMediaMetaData. if (metaDataChanged) { @@ -498,7 +512,7 @@ public class NotificationMediaManager implements Dumpable { } mProcessArtworkTasks.clear(); } - if (artworkBitmap != null) { + if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) { mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged, allowEnterAnimation).execute(artworkBitmap)); } else { @@ -612,6 +626,7 @@ public class NotificationMediaManager implements Dumpable { // We are unlocking directly - no animation! mBackdrop.setVisibility(View.GONE); mBackdropBack.setImageDrawable(null); + mMediaPlayer.clearControls(); if (windowController != null) { windowController.setBackdropShowing(false); } @@ -628,6 +643,7 @@ public class NotificationMediaManager implements Dumpable { mBackdrop.setVisibility(View.GONE); mBackdropFront.animate().cancel(); mBackdropBack.setImageDrawable(null); + mMediaPlayer.clearControls(); mMainExecutor.execute(mHideBackdropFront); }); if (mKeyguardStateController.isKeyguardFadingAway()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java index 4c99a90e7da0..e64b423aab60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java @@ -21,6 +21,7 @@ import android.content.Context; import android.os.Handler; import com.android.internal.statusbar.IStatusBarService; +import com.android.keyguard.KeyguardMediaPlayer; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -93,6 +94,7 @@ public interface StatusBarDependenciesModule { NotificationEntryManager notificationEntryManager, MediaArtworkProcessor mediaArtworkProcessor, KeyguardBypassController keyguardBypassController, + KeyguardMediaPlayer keyguardMediaPlayer, @Main Executor mainExecutor, DeviceConfigProxy deviceConfigProxy) { return new NotificationMediaManager( @@ -102,6 +104,7 @@ public interface StatusBarDependenciesModule { notificationEntryManager, mediaArtworkProcessor, keyguardBypassController, + keyguardMediaPlayer, mainExecutor, deviceConfigProxy); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java index 5d1ab4fd9ad2..db5458664023 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java @@ -152,7 +152,13 @@ public class MediaNotificationProcessor { } } - private int selectForegroundColor(int backgroundColor, Palette palette) { + /** + * Select a foreground color depending on whether the background color is dark or light + * @param backgroundColor Background color to coordinate with + * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} + * @return foreground color + */ + public static int selectForegroundColor(int backgroundColor, Palette palette) { if (ContrastColorUtil.isColorLight(backgroundColor)) { return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(), palette.getVibrantSwatch(), @@ -170,7 +176,7 @@ public class MediaNotificationProcessor { } } - private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, + private static int selectForegroundColorForSwatches(Palette.Swatch moreVibrant, Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch, Palette.Swatch dominantSwatch, int fallbackColor) { Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant); @@ -194,7 +200,7 @@ public class MediaNotificationProcessor { } } - private Palette.Swatch selectMutedCandidate(Palette.Swatch first, + private static Palette.Swatch selectMutedCandidate(Palette.Swatch first, Palette.Swatch second) { boolean firstValid = hasEnoughPopulation(first); boolean secondValid = hasEnoughPopulation(second); @@ -215,7 +221,8 @@ public class MediaNotificationProcessor { return null; } - private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) { + private static Palette.Swatch selectVibrantCandidate(Palette.Swatch first, + Palette.Swatch second) { boolean firstValid = hasEnoughPopulation(first); boolean secondValid = hasEnoughPopulation(second); if (firstValid && secondValid) { @@ -235,7 +242,7 @@ public class MediaNotificationProcessor { return null; } - private boolean hasEnoughPopulation(Palette.Swatch swatch) { + private static boolean hasEnoughPopulation(Palette.Swatch swatch) { // We want a fraction that is at least 1% of the image return swatch != null && (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION); @@ -257,7 +264,7 @@ public class MediaNotificationProcessor { * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder} * @return Swatch that should be used as the background of the media notification. */ - private static Palette.Swatch findBackgroundSwatch(Palette palette) { + public static Palette.Swatch findBackgroundSwatch(Palette palette) { // by default we use the dominant palette Palette.Swatch dominantSwatch = palette.getDominantSwatch(); if (dominantSwatch == null) { @@ -301,7 +308,7 @@ public class MediaNotificationProcessor { * @param artwork Media artwork * @return Builder that generates the {@link Palette} for the media artwork. */ - private static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { + public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) { // for the background we only take the left side of the image to ensure // a smooth transition return Palette.from(artwork) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt index 48386dce5d3f..e2b01ffe59b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -23,9 +23,11 @@ import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP +import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT import com.android.systemui.util.DeviceConfigProxy +import com.android.systemui.util.Utils import javax.inject.Inject @@ -43,9 +45,18 @@ class NotificationSectionsFeatureManager @Inject constructor( return usePeopleFiltering(proxy) } + fun isMediaControlsEnabled(): Boolean { + return Utils.useQsMediaPlayer(context) + } + fun getNotificationBuckets(): IntArray { return when { - isFilteringEnabled() -> + isFilteringEnabled() && isMediaControlsEnabled() -> + intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING, + BUCKET_SILENT) + !isFilteringEnabled() && isMediaControlsEnabled() -> + intArrayOf(BUCKET_HEADS_UP, BUCKET_MEDIA_CONTROLS, BUCKET_ALERTING, BUCKET_SILENT) + isFilteringEnabled() && !isMediaControlsEnabled() -> intArrayOf(BUCKET_HEADS_UP, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT) NotificationUtils.useNewInterruptionModel(context) -> intArrayOf(BUCKET_ALERTING, BUCKET_SILENT) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java new file mode 100644 index 000000000000..ab055e1bdc36 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.stack; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import com.android.systemui.R; +import com.android.systemui.statusbar.notification.row.ActivatableNotificationView; + +/** + * Root view to insert Lock screen media controls into the notification stack. + */ +public class MediaHeaderView extends ActivatableNotificationView { + + private View mContentView; + + public MediaHeaderView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContentView = findViewById(R.id.keyguard_media_view); + } + + @Override + protected View getContentView() { + return mContentView; + } + + /** + * Sets the background color, to be used when album art changes. + * @param color background + */ + public void setBackgroundColor(int color) { + setTintColor(color); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java index f6f836335c45..5fd11fd3ef14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java @@ -30,6 +30,7 @@ import android.view.LayoutInflater; import android.view.View; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.KeyguardMediaPlayer; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -71,6 +72,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section private final StatusBarStateController mStatusBarStateController; private final ConfigurationController mConfigurationController; private final PeopleHubViewAdapter mPeopleHubViewAdapter; + private final KeyguardMediaPlayer mKeyguardMediaPlayer; private final NotificationSectionsFeatureManager mSectionsFeatureManager; private final int mNumberOfSections; @@ -110,17 +112,21 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section private boolean mPeopleHubVisible = false; @Nullable private Subscription mPeopleHubSubscription; + private MediaHeaderView mMediaControlsView; + @Inject NotificationSectionsManager( ActivityStarter activityStarter, StatusBarStateController statusBarStateController, ConfigurationController configurationController, PeopleHubViewAdapter peopleHubViewAdapter, + KeyguardMediaPlayer keyguardMediaPlayer, NotificationSectionsFeatureManager sectionsFeatureManager) { mActivityStarter = activityStarter; mStatusBarStateController = statusBarStateController; mConfigurationController = configurationController; mPeopleHubViewAdapter = peopleHubViewAdapter; + mKeyguardMediaPlayer = keyguardMediaPlayer; mSectionsFeatureManager = sectionsFeatureManager; mNumberOfSections = mSectionsFeatureManager.getNumberOfBuckets(); } @@ -188,6 +194,13 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section } mPeopleHubView = reinflateView(mPeopleHubView, layoutInflater, R.layout.people_strip); mPeopleHubSubscription = mPeopleHubViewAdapter.bindView(mPeopleHubViewBoundary); + + if (mMediaControlsView != null) { + mKeyguardMediaPlayer.unbindView(); + } + mMediaControlsView = reinflateView(mMediaControlsView, layoutInflater, + R.layout.keyguard_media_header); + mKeyguardMediaPlayer.bindView(mMediaControlsView); } /** Listener for when the "clear all" button is clicked on the gentle notification header. */ @@ -198,6 +211,7 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section @Override public boolean beginsSection(@NonNull View view, @Nullable View previous) { return view == mGentleHeader + || view == mMediaControlsView || view == mPeopleHubView || view == mAlertingHeader || !Objects.equals(getBucket(view), getBucket(previous)); @@ -211,6 +225,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section private Integer getBucket(View view) { if (view == mGentleHeader) { return BUCKET_SILENT; + } else if (view == mMediaControlsView) { + return BUCKET_MEDIA_CONTROLS; } else if (view == mPeopleHubView) { return BUCKET_PEOPLE; } else if (view == mAlertingHeader) { @@ -238,9 +254,15 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section final boolean showHeaders = mStatusBarStateController.getState() != StatusBarState.KEYGUARD; final boolean usingPeopleFiltering = mSectionsFeatureManager.isFilteringEnabled(); + final boolean isKeyguard = mStatusBarStateController.getState() == StatusBarState.KEYGUARD; + final boolean usingMediaControls = mSectionsFeatureManager.isMediaControlsEnabled(); boolean peopleNotifsPresent = false; + int currentMediaControlsIdx = -1; + // Currently, just putting media controls in the front and incrementing the position based + // on the number of heads-up notifs. + int mediaControlsTarget = isKeyguard && usingMediaControls ? 0 : -1; int currentPeopleHeaderIdx = -1; int peopleHeaderTarget = -1; int currentAlertingHeaderIdx = -1; @@ -255,6 +277,10 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section View child = mParent.getChildAt(i); // Track the existing positions of the headers + if (child == mMediaControlsView) { + currentMediaControlsIdx = i; + continue; + } if (child == mPeopleHubView) { currentPeopleHeaderIdx = i; continue; @@ -276,6 +302,9 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section // Once we enter a new section, calculate the target position for the header. switch (row.getEntry().getBucket()) { case BUCKET_HEADS_UP: + if (mediaControlsTarget != -1) { + mediaControlsTarget++; + } break; case BUCKET_PEOPLE: peopleNotifsPresent = true; @@ -345,6 +374,8 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section alertingHeaderTarget, mAlertingHeader, currentAlertingHeaderIdx); adjustHeaderVisibilityAndPosition( peopleHeaderTarget, mPeopleHubView, currentPeopleHeaderIdx); + adjustViewPosition( + mediaControlsTarget, mMediaControlsView, currentMediaControlsIdx); // Update headers to reflect state of section contents mGentleHeader.setAreThereDismissableGentleNotifs( @@ -378,6 +409,28 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section } } + private void adjustViewPosition(int targetPosition, ExpandableView header, + int currentPosition) { + if (targetPosition == -1) { + if (currentPosition != -1) { + mParent.removeView(header); + } + } else { + if (currentPosition == -1) { + // If the header is animating away, it will still have a parent, so detach it first + // TODO: We should really cancel the active animations here. This will happen + // automatically when the view's intro animation starts, but it's a fragile link. + if (header.getTransientContainer() != null) { + header.getTransientContainer().removeTransientView(header); + header.setTransientContainer(null); + } + mParent.addView(header, targetPosition); + } else { + mParent.changeViewPosition(header, targetPosition); + } + } + } + /** * Updates the boundaries (as tracked by their first and last views) of the priority sections. * @@ -463,6 +516,11 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section } @VisibleForTesting + ExpandableView getMediaControlsView() { + return mMediaControlsView; + } + + @VisibleForTesting void setPeopleHubVisible(boolean visible) { mPeopleHubVisible = visible; } @@ -501,13 +559,15 @@ public class NotificationSectionsManager implements StackScrollAlgorithm.Section @Retention(SOURCE) @IntDef(prefix = { "BUCKET_" }, value = { BUCKET_HEADS_UP, + BUCKET_MEDIA_CONTROLS, BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT }) public @interface PriorityBucket {} public static final int BUCKET_HEADS_UP = 0; - public static final int BUCKET_PEOPLE = 1; - public static final int BUCKET_ALERTING = 2; - public static final int BUCKET_SILENT = 3; + public static final int BUCKET_MEDIA_CONTROLS = 1; + public static final int BUCKET_PEOPLE = 2; + public static final int BUCKET_ALERTING = 3; + public static final int BUCKET_SILENT = 4; } 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 4d4a2ded08ca..be8af82f8baf 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 @@ -222,6 +222,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private boolean mIsScrollerBoundSet; private Runnable mFinishScrollingCallback; private int mTouchSlop; + private float mSlopMultiplier; private int mMinimumVelocity; private int mMaximumVelocity; private int mOverflingDistance; @@ -1022,6 +1023,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd setClipChildren(false); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mOverflingDistance = configuration.getScaledOverflingDistance(); @@ -3744,15 +3746,23 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mLongPressListener = listener; } + private float getTouchSlop(MotionEvent event) { + // Adjust the touch slop if another gesture may be being performed. + return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + } + @Override @ShadeViewRefactor(RefactorComponent.INPUT) public boolean onTouchEvent(MotionEvent ev) { + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL || ev.getActionMasked() == MotionEvent.ACTION_UP; handleEmptySpaceClick(ev); boolean expandWantsIt = false; boolean swipingInProgress = mSwipingInProgress; - if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion) { + if (mIsExpanded && !swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) { if (isCancelOrUp) { mExpandHelper.onlyObserveMovements(false); } @@ -3778,7 +3788,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } // Check if we need to clear any snooze leavebehinds - NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts) && guts.getGutsContent() instanceof NotificationSnooze) { NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent(); @@ -3891,12 +3900,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd int deltaY = mLastMotionY - y; final int xDiff = Math.abs(x - mDownX); final int yDiff = Math.abs(deltaY); - if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) { + final float touchSlop = getTouchSlop(ev); + if (!mIsBeingDragged && yDiff > touchSlop && yDiff > xDiff) { setIsBeingDragged(true); if (deltaY > 0) { - deltaY -= mTouchSlop; + deltaY -= touchSlop; } else { - deltaY += mTouchSlop; + deltaY += touchSlop; } } if (mIsBeingDragged) { @@ -4046,9 +4056,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd public boolean onInterceptTouchEvent(MotionEvent ev) { initDownStates(ev); handleEmptySpaceClick(ev); + + NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); boolean expandWantsIt = false; boolean swipingInProgress = mSwipingInProgress; - if (!swipingInProgress && !mOnlyScrollingInThisMotion) { + if (!swipingInProgress && !mOnlyScrollingInThisMotion && guts == null) { expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev); } boolean scrollWantsIt = false; @@ -4065,7 +4077,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } // Check if we need to clear any snooze leavebehinds boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP; - NotificationGuts guts = mNotificationGutsManager.getExposedGuts(); if (!NotificationSwipeHelper.isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt && !scrollWantsIt) { mCheckForLeavebehind = false; @@ -4083,8 +4094,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd private void handleEmptySpaceClick(MotionEvent ev) { switch (ev.getActionMasked()) { case MotionEvent.ACTION_MOVE: - if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop - || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop)) { + final float touchSlop = getTouchSlop(ev); + if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > touchSlop + || Math.abs(ev.getX() - mInitialTouchX) > touchSlop)) { mTouchIsClick = false; } break; @@ -4168,7 +4180,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd final int x = (int) ev.getX(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); final int xDiff = Math.abs(x - mDownX); - if (yDiff > mTouchSlop && yDiff > xDiff) { + if (yDiff > getTouchSlop(ev) && yDiff > xDiff) { setIsBeingDragged(true); mLastMotionY = y; mDownX = x; 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 f9726d2d77f9..5588c24f2fd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -1021,7 +1021,8 @@ public class NotificationPanelViewController extends PanelViewController { trackMovement(event); return true; } - if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) + if (Math.abs(h) > getTouchSlop(event) + && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { mQsTracking = true; onQsExpansionStarted(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 2719a3278d24..481401b3eb7b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -30,8 +30,6 @@ public abstract class PanelView extends FrameLayout { protected StatusBar mStatusBar; protected HeadsUpManagerPhone mHeadsUpManager; - protected int mTouchSlop; - protected KeyguardBottomAreaView mKeyguardBottomArea; private OnConfigurationChangedListener mOnConfigurationChangedListener; 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 30367ed026ac..83cc4e33e2db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -91,7 +91,8 @@ public abstract class PanelViewController { protected boolean mTracking; private boolean mTouchSlopExceeded; private int mTrackingPointer; - protected int mTouchSlop; + private int mTouchSlop; + private float mSlopMultiplier; protected boolean mHintAnimationRunning; private boolean mOverExpandedBeforeFling; private boolean mTouchAboveFalsingThreshold; @@ -260,11 +261,19 @@ public abstract class PanelViewController { protected void loadDimens() { final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext()); mTouchSlop = configuration.getScaledTouchSlop(); + mSlopMultiplier = configuration.getScaledAmbiguousGestureMultiplier(); mHintDistance = mResources.getDimension(R.dimen.hint_move_distance); mUnlockFalsingThreshold = mResources.getDimensionPixelSize( R.dimen.unlock_falsing_threshold); } + protected float getTouchSlop(MotionEvent event) { + // Adjust the touch slop if another gesture may be being performed. + return event.getClassification() == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE + ? mTouchSlop * mSlopMultiplier + : mTouchSlop; + } + private void addMovement(MotionEvent event) { // Add movement to velocity tracker using raw screen X and Y coordinates instead // of window coordinates because the window frame may be moving at the same time. @@ -1111,7 +1120,8 @@ public abstract class PanelViewController { addMovement(event); if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) { float hAbs = Math.abs(h); - if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop)) + float touchSlop = getTouchSlop(event); + if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) && hAbs > Math.abs(x - mInitialTouchX)) { cancelHeightAnimator(); startExpandMotion(x, y, true /* startTracking */, mExpandedHeight); @@ -1228,7 +1238,8 @@ public abstract class PanelViewController { // If the panel was collapsed when touching, we only need to check for the // y-component of the gesture, as we have no conflicting horizontal gesture. - if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX) + if (Math.abs(h) > getTouchSlop(event) + && (Math.abs(h) > Math.abs(x - mInitialTouchX) || mIgnoreXTouchSlop)) { mTouchSlopExceeded = true; if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt new file mode 100644 index 000000000000..464a740c931c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMediaPlayerTest.kt @@ -0,0 +1,120 @@ +/* + * 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.keyguard + +import android.graphics.drawable.Icon +import android.media.MediaMetadata +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import android.widget.TextView +import androidx.test.filters.SmallTest + +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder +import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat + +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +public class KeyguardMediaPlayerTest : SysuiTestCase() { + + private lateinit var keyguardMediaPlayer: KeyguardMediaPlayer + private lateinit var fakeExecutor: FakeExecutor + private lateinit var mediaMetadata: MediaMetadata.Builder + private lateinit var entry: NotificationEntryBuilder + @Mock private lateinit var mockView: View + private lateinit var textView: TextView + @Mock private lateinit var mockIcon: Icon + + @Before + public fun setup() { + fakeExecutor = FakeExecutor(FakeSystemClock()) + keyguardMediaPlayer = KeyguardMediaPlayer(context, fakeExecutor) + mockView = mock(View::class.java) + textView = TextView(context) + mockIcon = mock(Icon::class.java) + mediaMetadata = MediaMetadata.Builder() + entry = NotificationEntryBuilder() + + keyguardMediaPlayer.bindView(mockView) + } + + @After + public fun tearDown() { + keyguardMediaPlayer.unbindView() + } + + @Test + public fun testBind() { + keyguardMediaPlayer.unbindView() + keyguardMediaPlayer.bindView(mockView) + } + + @Test + public fun testUnboundClearControls() { + keyguardMediaPlayer.unbindView() + keyguardMediaPlayer.clearControls() + keyguardMediaPlayer.bindView(mockView) + } + + @Test + public fun testUpdateControls() { + keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + verify(mockView).setVisibility(View.VISIBLE) + } + + @Test + public fun testClearControls() { + keyguardMediaPlayer.clearControls() + verify(mockView).setVisibility(View.GONE) + } + + @Test + public fun testSongName() { + whenever<TextView>(mockView.findViewById(R.id.header_title)).thenReturn(textView) + val song: String = "Song" + mediaMetadata.putText(MediaMetadata.METADATA_KEY_TITLE, song) + + keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + + assertThat(textView.getText()).isEqualTo(song) + } + + @Test + public fun testArtistName() { + whenever<TextView>(mockView.findViewById(R.id.header_artist)).thenReturn(textView) + val artist: String = "Artist" + mediaMetadata.putText(MediaMetadata.METADATA_KEY_ARTIST, artist) + + keyguardMediaPlayer.updateControls(entry.build(), mockIcon, mediaMetadata.build()) + + assertThat(textView.getText()).isEqualTo(artist) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java index 731101cb7329..afcd4414c667 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/assist/AssistHandleBehaviorControllerTest.java @@ -33,6 +33,7 @@ import android.content.ComponentName; import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import android.view.accessibility.AccessibilityManager; import androidx.test.filters.SmallTest; @@ -40,14 +41,13 @@ import com.android.internal.app.AssistUtils; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.systemui.SysuiTestCase; import com.android.systemui.dump.DumpManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.statusbar.phone.NavigationModeController; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -73,15 +73,16 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { @Mock private AssistHandleBehaviorController.BehaviorController mMockTestBehavior; @Mock private NavigationModeController mMockNavigationModeController; @Mock private AssistHandleViewController mMockAssistHandleViewController; + @Mock private AccessibilityManager mMockA11yManager; @Before public void setup() { MockitoAnnotations.initMocks(this); - mDependency.injectMockDependency(StatusBarStateController.class); - mDependency.injectMockDependency(OverviewProxyService.class); doAnswer(answerVoid(Runnable::run)).when(mMockHandler).post(any(Runnable.class)); doAnswer(answerVoid(Runnable::run)).when(mMockHandler) .postDelayed(any(Runnable.class), anyLong()); + doAnswer(invocation -> invocation.getArgument(0)).when(mMockA11yManager) + .getRecommendedTimeoutMillis(anyInt(), anyInt()); Map<AssistHandleBehavior, AssistHandleBehaviorController.BehaviorController> behaviorMap = new EnumMap<>(AssistHandleBehavior.class); @@ -99,6 +100,7 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { mMockDeviceConfigHelper, behaviorMap, mMockNavigationModeController, + () -> mMockA11yManager, mock(DumpManager.class)); } @@ -243,6 +245,28 @@ public class AssistHandleBehaviorControllerTest extends SysuiTestCase { } @Test + public void showAndGo_usesA11yTimeout() { + // Arrange + when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); + when(mMockDeviceConfigHelper.getLong( + eq(SystemUiDeviceConfigFlags.ASSIST_HANDLES_SHOW_AND_GO_DURATION_MS), anyLong())) + .thenReturn(12345L); + mAssistHandleBehaviorController.hide(); + reset(mMockAssistHandleViewController, mMockA11yManager); + when(mMockA11yManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenReturn(54321); + ArgumentCaptor<Long> delay = ArgumentCaptor.forClass(Long.class); + + // Act + mAssistHandleBehaviorController.showAndGo(); + + // Assert + verify(mMockA11yManager).getRecommendedTimeoutMillis( + eq(12345), eq(AccessibilityManager.FLAG_CONTENT_ICONS)); + verify(mMockHandler).postDelayed(any(Runnable.class), delay.capture()); + assert delay.getValue() == 54321L; + } + + @Test public void showAndGoDelayed_showsThenHidesHandlesWhenHiding() { // Arrange when(mMockAssistUtils.getAssistComponentForUser(anyInt())).thenReturn(COMPONENT_NAME); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt index 9adab5d2369f..5e0d28f6f795 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt @@ -25,6 +25,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.controls.ControlStatus import com.android.systemui.controls.controller.ControlInfo import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -156,12 +157,26 @@ class AllModelTest : SysuiTestCase() { } @Test + fun testAddFavorite_changesModelFlag() { + val indexToAdd = 6 + val id = "$idPrefix$indexToAdd" + model.changeFavoriteStatus(id, true) + assertTrue( + (model.elements.first { + it is ControlWrapper && it.controlStatus.control.controlId == id + } as ControlWrapper) + .controlStatus.favorite + ) + } + + @Test fun testAddFavorite_alreadyThere() { val indexToAdd = 7 model.changeFavoriteStatus("$idPrefix$indexToAdd", true) val expectedFavorites = favoritesIndices.map(controls::get).map(ControlStatus::control) + assertEquals(expectedFavorites.size, model.favorites.size) model.favorites.zip(expectedFavorites).forEach { assertTrue(sameControl(it.first, it.second)) } @@ -182,6 +197,19 @@ class AllModelTest : SysuiTestCase() { } @Test + fun testRemoveFavorite_changesModelFlag() { + val indexToRemove = 3 + val id = "$idPrefix$indexToRemove" + model.changeFavoriteStatus(id, false) + assertFalse( + (model.elements.first { + it is ControlWrapper && it.controlStatus.control.controlId == id + } as ControlWrapper) + .controlStatus.favorite + ) + } + + @Test fun testRemoveFavorite_notThere() { val indexToRemove = 4 model.changeFavoriteStatus("$idPrefix$indexToRemove", false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt index 6388fe1a69c1..b501a2ebeb64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt @@ -27,6 +27,7 @@ import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS import com.android.systemui.SysuiTestCase import com.android.systemui.util.DeviceConfigProxyFake +import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -38,6 +39,7 @@ import org.junit.runner.RunWith class NotificationSectionsFeatureManagerTest : SysuiTestCase() { var manager: NotificationSectionsFeatureManager? = null val proxyFake = DeviceConfigProxyFake() + var originalQsMediaPlayer: Int = 0 @Before public fun setup() { @@ -45,6 +47,15 @@ class NotificationSectionsFeatureManagerTest : SysuiTestCase() { NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) manager = NotificationSectionsFeatureManager(proxyFake, mContext) manager!!.clearCache() + originalQsMediaPlayer = Settings.System.getInt(context.getContentResolver(), + "qs_media_player", 1) + Settings.System.putInt(context.getContentResolver(), "qs_media_player", 0) + } + + @After + public fun teardown() { + Settings.System.putInt(context.getContentResolver(), "qs_media_player", + originalQsMediaPlayer) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java index a263a7232352..646bc9699ff8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java @@ -42,6 +42,7 @@ import android.view.ViewGroup; import androidx.test.filters.SmallTest; +import com.android.keyguard.KeyguardMediaPlayer; import com.android.systemui.ActivityStarterDelegate; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -73,6 +74,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; @Mock private ConfigurationController mConfigurationController; @Mock private PeopleHubViewAdapter mPeopleHubAdapter; + @Mock private KeyguardMediaPlayer mKeyguardMediaPlayer; @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager; @Mock private NotificationRowComponent mNotificationRowComponent; @Mock private ActivatableNotificationViewController mActivatableNotificationViewController; @@ -91,6 +93,7 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { mStatusBarStateController, mConfigurationController, mPeopleHubAdapter, + mKeyguardMediaPlayer, mSectionsFeatureManager ); // Required in order for the header inflation to work properly @@ -333,13 +336,82 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { verify(mNssl).changeViewPosition(mSectionsManager.getPeopleHeaderView(), 0); } + @Test + public void testMediaControls_AddWhenEnterKeyguard() { + enableMediaControls(); + + // GIVEN a stack that doesn't include media controls + setStackState(ChildType.ALERTING, ChildType.GENTLE_HEADER, ChildType.GENTLE); + + // WHEN we go back to the keyguard + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); + mSectionsManager.updateSectionBoundaries(); + + // Then the media controls are added + verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 0); + } + + @Test + public void testMediaControls_AddWhenEnterKeyguardWithHeadsUp() { + enableMediaControls(); + + // GIVEN a stack that doesn't include media controls but includes HEADS_UP + setStackState(ChildType.HEADS_UP, ChildType.ALERTING, ChildType.GENTLE_HEADER, + ChildType.GENTLE); + + // WHEN we go back to the keyguard + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); + mSectionsManager.updateSectionBoundaries(); + + // Then the media controls are added after HEADS_UP + verify(mNssl).addView(mSectionsManager.getMediaControlsView(), 1); + } + + @Test + public void testMediaControls_RemoveWhenExitKeyguard() { + enableMediaControls(); + + // GIVEN a stack with media controls + setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER, + ChildType.GENTLE); + + // WHEN we leave the keyguard + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); + mSectionsManager.updateSectionBoundaries(); + + // Then the media controls is removed + verify(mNssl).removeView(mSectionsManager.getMediaControlsView()); + } + + @Test + public void testMediaControls_RemoveWhenPullDownShade() { + enableMediaControls(); + + // GIVEN a stack with media controls + setStackState(ChildType.MEDIA_CONTROLS, ChildType.ALERTING, ChildType.GENTLE_HEADER, + ChildType.GENTLE); + + // WHEN we pull down the shade on the keyguard + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); + mSectionsManager.updateSectionBoundaries(); + + // Then the media controls is removed + verify(mNssl).removeView(mSectionsManager.getMediaControlsView()); + } + private void enablePeopleFiltering() { when(mSectionsFeatureManager.isFilteringEnabled()).thenReturn(true); when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4); } + private void enableMediaControls() { + when(mSectionsFeatureManager.isMediaControlsEnabled()).thenReturn(true); + when(mSectionsFeatureManager.getNumberOfBuckets()).thenReturn(4); + } + private enum ChildType { - PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING, GENTLE, OTHER + MEDIA_CONTROLS, PEOPLE_HEADER, ALERTING_HEADER, GENTLE_HEADER, HEADS_UP, PERSON, ALERTING, + GENTLE, OTHER } private void setStackState(ChildType... children) { @@ -347,6 +419,9 @@ public class NotificationSectionsManagerTest extends SysuiTestCase { for (int i = 0; i < children.length; i++) { View child; switch (children[i]) { + case MEDIA_CONTROLS: + child = mSectionsManager.getMediaControlsView(); + break; case PEOPLE_HEADER: child = mSectionsManager.getPeopleHeaderView(); break; diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 9b04e7931f7c..fe33fae98306 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -1876,7 +1876,7 @@ class AlarmManagerService extends SystemService { // package was t(q) then the next delivery must be after t(q) + <window_size> final long t = mAppWakeupHistory.getNthLastWakeupForPackage( sourcePackage, sourceUserId, quotaForBucket); - minElapsed = t + 1 + mConstants.APP_STANDBY_WINDOW; + minElapsed = t + mConstants.APP_STANDBY_WINDOW; } if (alarm.expectedWhenElapsed < minElapsed) { alarm.whenElapsed = alarm.maxWhenElapsed = minElapsed; diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index cea3251d26a1..3148a6205871 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -103,7 +103,7 @@ public final class PinnerService extends SystemService { private static boolean PROP_PIN_CAMERA = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT, "pin_camera", - SystemProperties.getBoolean("pinner.pin_camera", true)); + SystemProperties.getBoolean("pinner.pin_camera", false)); // Pin using pinlist.meta when pinning apps. private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean( "pinner.use_pinlist", true); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 4b925ef7c245..9bbbc3b93fd7 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -155,13 +155,18 @@ class MediaRouter2ServiceImpl { final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); - final boolean trusted = mContext.checkCallingOrSelfPermission( + final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission( android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) == PackageManager.PERMISSION_GRANTED; + final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission( + android.Manifest.permission.MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED; + final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerRouter2Locked(router, uid, pid, packageName, userId, trusted); + registerRouter2Locked(router, uid, pid, packageName, userId, + hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission); } } finally { Binder.restoreCallingIdentity(token); @@ -341,8 +346,6 @@ class MediaRouter2ServiceImpl { throw new IllegalArgumentException("packageName must not be empty"); } - final boolean trusted = true; - final int uid = Binder.getCallingUid(); final int pid = Binder.getCallingPid(); final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); @@ -350,7 +353,7 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerManagerLocked(manager, uid, pid, packageName, userId, trusted); + registerManagerLocked(manager, uid, pid, packageName, userId); } } finally { Binder.restoreCallingIdentity(token); @@ -532,7 +535,8 @@ class MediaRouter2ServiceImpl { //////////////////////////////////////////////////////////////// private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid, - @NonNull String packageName, int userId, boolean trusted) { + @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission, + boolean hasModifyAudioRoutingPermission) { final IBinder binder = router.asBinder(); if (mAllRouterRecords.get(binder) != null) { Slog.w(TAG, "Same router already exists. packageName=" + packageName); @@ -540,8 +544,8 @@ class MediaRouter2ServiceImpl { } UserRecord userRecord = getOrCreateUserRecordLocked(userId); - RouterRecord routerRecord = new RouterRecord( - userRecord, router, uid, pid, packageName, trusted); + RouterRecord routerRecord = new RouterRecord(userRecord, router, uid, pid, packageName, + hasConfigureWifiDisplayPermission, hasModifyAudioRoutingPermission); try { binder.linkToDeath(routerRecord, 0); } catch (RemoteException ex) { @@ -711,7 +715,7 @@ class MediaRouter2ServiceImpl { } private void registerManagerLocked(@NonNull IMediaRouter2Manager manager, - int uid, int pid, @NonNull String packageName, int userId, boolean trusted) { + int uid, int pid, @NonNull String packageName, int userId) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); @@ -721,7 +725,7 @@ class MediaRouter2ServiceImpl { } UserRecord userRecord = getOrCreateUserRecordLocked(userId); - managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted); + managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName); try { binder.linkToDeath(managerRecord, 0); } catch (RemoteException ex) { @@ -778,7 +782,7 @@ class MediaRouter2ServiceImpl { @NonNull IMediaRouter2Manager manager, @NonNull String packageName, @NonNull MediaRoute2Info route) { ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder()); - if (managerRecord == null || !managerRecord.mTrusted) { + if (managerRecord == null) { return; } @@ -976,14 +980,16 @@ class MediaRouter2ServiceImpl { public final IMediaRouter2 mRouter; public final int mUid; public final int mPid; - public final boolean mTrusted; + public final boolean mHasConfigureWifiDisplayPermission; + public final boolean mHasModifyAudioRoutingPermission; public final int mRouterId; public RouteDiscoveryPreference mDiscoveryPreference; public MediaRoute2Info mSelectedRoute; - RouterRecord(UserRecord userRecord, IMediaRouter2 router, - int uid, int pid, String packageName, boolean trusted) { + RouterRecord(UserRecord userRecord, IMediaRouter2 router, int uid, int pid, + String packageName, boolean hasConfigureWifiDisplayPermission, + boolean hasModifyAudioRoutingPermission) { mUserRecord = userRecord; mPackageName = packageName; mSelectRouteSequenceNumbers = new ArrayList<>(); @@ -991,7 +997,8 @@ class MediaRouter2ServiceImpl { mRouter = router; mUid = uid; mPid = pid; - mTrusted = trusted; + mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission; + mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission; mRouterId = mNextRouterOrManagerId.getAndIncrement(); } @@ -1011,17 +1018,15 @@ class MediaRouter2ServiceImpl { public final int mUid; public final int mPid; public final String mPackageName; - public final boolean mTrusted; public final int mManagerId; ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager, - int uid, int pid, String packageName, boolean trusted) { + int uid, int pid, String packageName) { mUserRecord = userRecord; mManager = manager; mUid = uid; mPid = pid; mPackageName = packageName; - mTrusted = trusted; mManagerId = mNextRouterOrManagerId.getAndIncrement(); } @@ -1036,9 +1041,6 @@ class MediaRouter2ServiceImpl { public void dump(PrintWriter pw, String prefix) { pw.println(prefix + this); - - final String indent = prefix + " "; - pw.println(indent + "mTrusted=" + mTrusted); } @Override diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index e5cb5540b781..77921236e0d7 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -15,9 +15,7 @@ */ package com.android.server.notification; -import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_BUBBLE; -import static android.app.Notification.FLAG_FOREGROUND_SERVICE; import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING; import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE; @@ -25,7 +23,6 @@ import static com.android.internal.util.FrameworkStatsLog.BUBBLE_DEVELOPER_ERROR import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; -import android.app.Person; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -34,8 +31,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; -import java.util.ArrayList; - /** * Determines whether a bubble can be shown for this notification */ @@ -152,41 +147,13 @@ public class BubbleExtractor implements NotificationSignalExtractor { return false; } - // At this point the bubble must fulfill communication policy - - // Communication always needs a person - ArrayList<Person> peopleList = notification.extras != null - ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST) - : null; - // Message style requires a person & it's not included in the list boolean isMessageStyle = Notification.MessagingStyle.class.equals( notification.getNotificationStyle()); - if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) { - logBubbleError(r.getKey(), "Must have a person and be " - + "Notification.MessageStyle or Notification.CATEGORY_CALL"); + if (!isMessageStyle) { + logBubbleError(r.getKey(), "must be Notification.MessageStyle"); return false; } - - // Communication is a message or a call - boolean isCall = CATEGORY_CALL.equals(notification.category); - boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0; - if (hasForegroundService && !isCall) { - logBubbleError(r.getKey(), - "foreground services must be Notification.CATEGORY_CALL to bubble"); - return false; - } - if (isMessageStyle) { - return true; - } else if (isCall) { - if (hasForegroundService) { - return true; - } - logBubbleError(r.getKey(), "calls require foreground service"); - return false; - } - logBubbleError(r.getKey(), "Must be " - + "Notification.MessageStyle or Notification.CATEGORY_CALL"); - return false; + return true; } /** diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f65f187eddc5..35dec5a59cec 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -243,7 +243,6 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; -import com.android.internal.util.FunctionalUtils; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.TriPredicate; diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 83da38195053..7069818e3894 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -249,16 +249,13 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { } private boolean canRequestInteractAcrossProfilesUnchecked(String packageName) { - List<UserHandle> targetUserProfiles = - getTargetUserProfilesUnchecked(packageName, mInjector.getCallingUserId()); - if (targetUserProfiles.isEmpty()) { - return false; - } - if (!hasRequestedAppOpPermission( - AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName)) { + final int[] enabledProfileIds = + mInjector.getUserManager().getEnabledProfileIds(mInjector.getCallingUserId()); + if (enabledProfileIds.length < 2) { return false; } - return isCrossProfilePackageWhitelisted(packageName); + return hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName); } private boolean hasRequestedAppOpPermission(String permission, String packageName) { @@ -540,6 +537,17 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { return isCrossProfilePackageWhitelisted(packageName); } + @Override + public boolean canUserAttemptToConfigureInteractAcrossProfiles(String packageName) { + final int[] profileIds = mInjector.getUserManager().getProfileIds( + mInjector.getCallingUserId(), /* enabledOnly= */ false); + if (profileIds.length < 2) { + return false; + } + return hasRequestedAppOpPermission( + AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), packageName); + } + private boolean hasOtherProfileWithPackageInstalled(String packageName, @UserIdInt int userId) { return mInjector.withCleanCallingIdentity(() -> { final int[] profileIds = diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index d9e41afa9141..8cac487f50b9 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -21,6 +21,9 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE; import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER; +import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_ROOT; +import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_UNDEFINED; +import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS; import static com.android.internal.util.Preconditions.checkState; import static com.android.server.wm.DisplayAreaProto.NAME; @@ -28,13 +31,16 @@ import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.WindowContainerChildProto.DISPLAY_AREA; +import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; +import android.window.IDisplayAreaOrganizer; import com.android.server.policy.WindowManagerPolicy; import com.android.server.protolog.common.ProtoLog; import java.util.Comparator; +import java.util.function.Consumer; import java.util.function.Predicate; /** @@ -57,13 +63,24 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { protected final Type mType; private final String mName; + final int mFeatureId; + private final DisplayAreaOrganizerController mOrganizerController; + IDisplayAreaOrganizer mOrganizer; DisplayArea(WindowManagerService wms, Type type, String name) { + this(wms, type, name, FEATURE_UNDEFINED); + } + + DisplayArea(WindowManagerService wms, Type type, String name, int featureId) { super(wms); // TODO(display-area): move this up to ConfigurationContainer mOrientation = SCREEN_ORIENTATION_UNSET; mType = type; mName = name; + mFeatureId = featureId; + mRemoteToken = new RemoteToken(this); + mOrganizerController = + wms.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController; } @Override @@ -116,6 +133,33 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { return DISPLAY_AREA; } + void forAllDisplayAreas(Consumer<DisplayArea> callback) { + super.forAllDisplayAreas(callback); + callback.accept(this); + } + + void setOrganizer(IDisplayAreaOrganizer organizer) { + if (mOrganizer == organizer) return; + sendDisplayAreaVanished(); + mOrganizer = organizer; + sendDisplayAreaAppeared(); + } + + void sendDisplayAreaAppeared() { + if (mOrganizer == null) return; + mOrganizerController.onDisplayAreaAppeared(mOrganizer, this); + } + + void sendDisplayAreaVanished() { + if (mOrganizer == null) return; + mOrganizerController.onDisplayAreaVanished(mOrganizer, this); + } + + @Override + boolean isOrganized() { + return mOrganizer != null; + } + /** * DisplayArea that contains WindowTokens, and orders them according to their type. */ @@ -152,7 +196,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { }; Tokens(WindowManagerService wms, Type type, String name) { - super(wms, type, name); + super(wms, type, name, FEATURE_WINDOW_TOKENS); } void addChild(WindowToken token) { @@ -191,7 +235,7 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { private final Rect mTmpDimBoundsRect = new Rect(); Root(WindowManagerService wms) { - super(wms, Type.ANY, "DisplayArea.Root"); + super(wms, Type.ANY, "DisplayArea.Root", FEATURE_ROOT); } @Override diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java new file mode 100644 index 000000000000..464b127a5e37 --- /dev/null +++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; + +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.window.IDisplayAreaOrganizer; +import android.window.IDisplayAreaOrganizerController; + +import java.util.HashMap; + +public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerController.Stub { + private static final String TAG = "DisplayAreaOrganizerController"; + + final ActivityTaskManagerService mService; + private final WindowManagerGlobalLock mGlobalLock; + private final HashMap<Integer, IDisplayAreaOrganizer> mOrganizersByFeatureIds = new HashMap(); + + private class DeathRecipient implements IBinder.DeathRecipient { + int mFeature; + IDisplayAreaOrganizer mOrganizer; + + DeathRecipient(IDisplayAreaOrganizer organizer, int feature) { + mOrganizer = organizer; + mFeature = feature; + } + + @Override + public void binderDied() { + synchronized (mGlobalLock) { + mOrganizersByFeatureIds.remove(mFeature); + mService.mRootWindowContainer.forAllDisplayAreas((da) -> { + if (da.mOrganizer != mOrganizer) return; + da.setOrganizer(null); + }); + } + } + } + + DisplayAreaOrganizerController(ActivityTaskManagerService atm) { + mService = atm; + mGlobalLock = atm.mGlobalLock; + } + + private void enforceStackPermission(String func) { + mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func); + } + + @Override + public void registerOrganizer(IDisplayAreaOrganizer organizer, int feature) { + enforceStackPermission("registerOrganizer()"); + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + if (mOrganizersByFeatureIds.get(feature) != null) { + throw new IllegalStateException( + "Replacing existing organizer currently unsupported"); + } + + final DeathRecipient dr = new DeathRecipient(organizer, feature); + try { + organizer.asBinder().linkToDeath(dr, 0); + } catch (RemoteException e) { + // Oh well... + } + mService.mRootWindowContainer.forAllDisplayAreas((da) -> { + if (da.mFeatureId != feature) return; + da.setOrganizer(organizer); + }); + + mOrganizersByFeatureIds.put(feature, organizer); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + void onDisplayAreaAppeared(IDisplayAreaOrganizer organizer, DisplayArea da) { + try { + organizer.onDisplayAreaAppeared(da.mRemoteToken); + } catch (RemoteException e) { + // Oh well... + } + } + + void onDisplayAreaVanished(IDisplayAreaOrganizer organizer, DisplayArea da) { + try { + organizer.onDisplayAreaVanished(da.mRemoteToken); + } catch (RemoteException e) { + // Oh well... + } + } +} diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java index 885456a8488c..0c6e48314c48 100644 --- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java +++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java @@ -73,16 +73,36 @@ class DisplayAreaPolicyBuilder { */ static class Feature { private final String mName; + private final int mId; private final boolean[] mWindowLayers; - private Feature(String name, boolean[] windowLayers) { + private Feature(String name, int id, boolean[] windowLayers) { mName = name; + mId = id; mWindowLayers = windowLayers; } + /** + * Returns the id of the feature. + * + * Must be unique among the features added to a {@link DisplayAreaPolicyBuilder}. + * + * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_SYSTEM_FIRST + * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_VENDOR_FIRST + */ + public int getId() { + return mId; + } + + @Override + public String toString() { + return "Feature(\"" + mName + "\", " + mId + '}'; + } + static class Builder { private final WindowManagerPolicy mPolicy; private final String mName; + private final int mId; private final boolean[] mLayers; /** @@ -96,10 +116,12 @@ class DisplayAreaPolicyBuilder { * The builder starts out with the feature not applying to any types. * * @param name the name of the feature. + * @param id of the feature. {@see Feature#getId} */ - Builder(WindowManagerPolicy policy, String name) { + Builder(WindowManagerPolicy policy, String name, int id) { mPolicy = policy; mName = name; + mId = id; mLayers = new boolean[mPolicy.getMaxWindowLayer()]; } @@ -147,7 +169,7 @@ class DisplayAreaPolicyBuilder { } Feature build() { - return new Feature(mName, mLayers.clone()); + return new Feature(mName, mId, mLayers.clone()); } private void set(int type, boolean value) { @@ -364,7 +386,7 @@ class DisplayAreaPolicyBuilder { return leaf; } else { return new DisplayArea(parent.mWmService, type, mFeature.mName + ":" - + mMinLayer + ":" + mMaxLayer); + + mMinLayer + ":" + mMaxLayer, mFeature.mId); } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a20318dcd990..7df731b3efab 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -79,6 +79,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN; import static android.view.WindowManager.TRANSIT_TASK_OPEN; import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT; +import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; @@ -4294,7 +4295,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private ActivityStack mRootSplitScreenPrimaryTask = null; TaskContainers(WindowManagerService service) { - super(service, Type.ANY, "TaskContainers"); + super(service, Type.ANY, "TaskContainers", FEATURE_TASK_CONTAINER); } /** diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 34acabe76296..5e88fb0437c6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -115,7 +115,6 @@ import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE; import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW; import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT; -import static com.android.server.wm.ActivityTaskManagerInternal.SleepToken; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT; @@ -178,7 +177,6 @@ import android.view.WindowManagerPolicyConstants; import android.view.accessibility.AccessibilityManager; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.internal.policy.ScreenDecorationsUtils; @@ -333,9 +331,6 @@ public class DisplayPolicy { } }; - @GuardedBy("mHandler") - private SleepToken mDreamingSleepToken; - // The windows we were told about in focusChanged. private WindowState mFocusedWindow; private WindowState mLastFocusedWindow; @@ -394,7 +389,6 @@ public class DisplayPolicy { private boolean mShowingDream; private boolean mLastShowingDream; private boolean mDreamingLockscreen; - private boolean mDreamingSleepTokenNeeded; private boolean mAllowLockscreenWhenOn; private InputConsumer mInputConsumer = null; @@ -414,7 +408,6 @@ public class DisplayPolicy { private RefreshRatePolicy mRefreshRatePolicy; // -------- PolicyHandler -------- - private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 1; private static final int MSG_REQUEST_TRANSIENT_BARS = 2; private static final int MSG_DISPOSE_INPUT_CONSUMER = 3; private static final int MSG_ENABLE_POINTER_LOCATION = 4; @@ -434,9 +427,6 @@ public class DisplayPolicy { @Override public void handleMessage(Message msg) { switch (msg.what) { - case MSG_UPDATE_DREAMING_SLEEP_TOKEN: - updateDreamingSleepToken(msg.arg1 != 0); - break; case MSG_REQUEST_TRANSIENT_BARS: synchronized (mLock) { WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS) @@ -2638,15 +2628,6 @@ public class DisplayPolicy { // while the dream is showing. if (!mShowingDream) { mDreamingLockscreen = mService.mPolicy.isKeyguardShowingAndNotOccluded(); - if (mDreamingSleepTokenNeeded) { - mDreamingSleepTokenNeeded = false; - mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 0, 1).sendToTarget(); - } - } else { - if (!mDreamingSleepTokenNeeded) { - mDreamingSleepTokenNeeded = true; - mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 1, 1).sendToTarget(); - } } if (mStatusBar != null) { @@ -3160,21 +3141,6 @@ public class DisplayPolicy { return !mShowingDream; } - private void updateDreamingSleepToken(boolean acquire) { - if (acquire) { - final int displayId = getDisplayId(); - if (mDreamingSleepToken == null) { - mDreamingSleepToken = mService.mAtmInternal.acquireSleepToken( - "DreamOnDisplay" + displayId, displayId); - } - } else { - if (mDreamingSleepToken != null) { - mDreamingSleepToken.release(); - mDreamingSleepToken = null; - } - } - } - private void requestTransientBars(WindowState swipeTarget) { if (!mService.mPolicy.isUserSetupComplete()) { // Swipe-up for navigation bar is disabled during setup @@ -3854,7 +3820,6 @@ public class DisplayPolicy { } pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream); pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen); - pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken); if (mStatusBar != null) { pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar); } diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index e2c7d52fa673..88cdd1781aee 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -301,7 +301,7 @@ final class InputMonitor { * we may have some issues with modal-windows, but I guess we can * cross that bridge when we come to implementing full-screen TaskOrg */ - if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) { + if (child.getTask() != null && child.getTask().isOrganized()) { inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index eca7d2e19288..55dc694bb611 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -509,22 +509,6 @@ class Task extends WindowContainer<WindowContainer> { _voiceSession, _voiceInteractor, stack); } - class TaskToken extends RemoteToken { - TaskToken(WindowContainer container) { - super(container); - } - - @Override - public SurfaceControl getLeash() { - // We need to copy the SurfaceControl instead of returning the original - // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls - // to release themselves. - SurfaceControl sc = new SurfaceControl(); - sc.copyFrom(getSurfaceControl()); - return sc; - } - } - /** Don't use constructor directly. This is only used by XML parser. */ Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent, Intent _affinityIntent, String _affinity, String _rootAffinity, ComponentName _realActivity, @@ -550,7 +534,7 @@ class Task extends WindowContainer<WindowContainer> { mTaskDescription = _lastTaskDescription; // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED). setOrientation(SCREEN_ORIENTATION_UNSET); - mRemoteToken = new TaskToken(this); + mRemoteToken = new RemoteToken(this); affinityIntent = _affinityIntent; affinity = _affinity; rootAffinity = _rootAffinity; @@ -3110,7 +3094,7 @@ class Task extends WindowContainer<WindowContainer> { /** * Animations are handled by the TaskOrganizer implementation. */ - if (isControlledByTaskOrganizer()) { + if (isOrganized()) { return false; } // Don't animate while the task runs recents animation but only if we are in the mode @@ -4014,7 +3998,8 @@ class Task extends WindowContainer<WindowContainer> { } } - boolean isControlledByTaskOrganizer() { + @Override + boolean isOrganized() { final Task rootTask = getRootTask(); // if the rootTask is a "child" of a tile, then don't consider it a root task. // TODO: remove this along with removing tile. @@ -4030,7 +4015,7 @@ class Task extends WindowContainer<WindowContainer> { * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the * Surface in to its own hierarchy. */ - if (isControlledByTaskOrganizer()) { + if (isOrganized()) { return; } super.reparentSurfaceControl(t, newParent); @@ -4117,27 +4102,6 @@ class Task extends WindowContainer<WindowContainer> { sendTaskAppeared(); } - @Override - public void updateSurfacePosition() { - // Avoid fighting with the TaskOrganizer over Surface position. - if (isControlledByTaskOrganizer()) { - return; - } else { - super.updateSurfacePosition(); - } - } - - @Override - void getRelativeDisplayedPosition(Point outPos) { - // In addition to updateSurfacePosition, we keep other code that sets - // position from fighting with the TaskOrganizer - if (isControlledByTaskOrganizer()) { - outPos.set(0, 0); - return; - } - super.getRelativeDisplayedPosition(outPos); - } - /** * @return true if the task is currently focused. */ diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index fc58ee785cf8..552367801678 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -111,7 +111,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void addTask(Task t) { mOrganizedTasks.add(t); try { - mOrganizer.taskAppeared(t.getTaskInfo()); + mOrganizer.onTaskAppeared(t.getTaskInfo()); } catch (Exception e) { Slog.e(TAG, "Exception sending taskAppeared callback" + e); } @@ -119,7 +119,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void removeTask(Task t) { try { - mOrganizer.taskVanished(t.getTaskInfo()); + mOrganizer.onTaskVanished(t.getTaskInfo()); } catch (Exception e) { Slog.e(TAG, "Exception sending taskVanished callback" + e); } diff --git a/services/core/java/com/android/server/wm/TaskTile.java b/services/core/java/com/android/server/wm/TaskTile.java index 822f840005b7..51142b1d2eb1 100644 --- a/services/core/java/com/android/server/wm/TaskTile.java +++ b/services/core/java/com/android/server/wm/TaskTile.java @@ -218,7 +218,7 @@ public class TaskTile extends ActivityStack { static TaskTile forToken(IBinder token) { try { - return (TaskTile) ((TaskToken) token).getContainer(); + return (TaskTile) ((RemoteToken) token).getContainer(); } catch (ClassCastException e) { Slog.w(TAG, "Bad tile token: " + token, e); return null; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index a49cffb664fd..ae0b685cfac5 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1612,6 +1612,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return null; } + void forAllDisplayAreas(Consumer<DisplayArea> callback) { + for (int i = mChildren.size() - 1; i >= 0; --i) { + mChildren.get(i).forAllDisplayAreas(callback); + } + } + /** * Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than * the input container in terms of z-order. @@ -2325,6 +2331,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void updateSurfacePosition() { + // Avoid fighting with the organizer over Surface position. + if (isOrganized()) return; + if (mSurfaceControl == null) { return; } @@ -2374,6 +2383,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } void getRelativeDisplayedPosition(Point outPos) { + // In addition to updateSurfacePosition, we keep other code that sets + // position from fighting with the organizer + if (isOrganized()) { + outPos.set(0, 0); + return; + } + final Rect dispBounds = getDisplayedBounds(); outPos.set(dispBounds.left, dispBounds.top); final WindowContainer parent = getParent(); @@ -2414,8 +2430,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return null; } - RemoteToken getRemoteToken() { - return mRemoteToken; + /** + * @return {@code true} if window container is manage by a + * {@link android.window.WindowOrganizer} + */ + boolean isOrganized() { + return false; } static WindowContainer fromBinder(IBinder binder) { @@ -2439,7 +2459,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< @Override public SurfaceControl getLeash() { - throw new RuntimeException("Not implemented"); + final WindowContainer wc = getContainer(); + if (wc == null) return null; + // We need to copy the SurfaceControl instead of returning the original + // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls + // to release themselves. + SurfaceControl sc = new SurfaceControl(); + sc.copyFrom(wc.getSurfaceControl()); + return sc; } @Override diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index ebfe1096540e..e416e8073a75 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -33,6 +33,7 @@ import android.os.RemoteException; import android.util.ArraySet; import android.util.Slog; import android.view.SurfaceControl; +import android.window.IDisplayAreaOrganizerController; import android.window.ITaskOrganizerController; import android.window.IWindowContainerTransactionCallback; import android.window.IWindowOrganizerController; @@ -76,11 +77,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mTransactionCallbacksByPendingSyncId = new HashMap(); final TaskOrganizerController mTaskOrganizerController; + final DisplayAreaOrganizerController mDisplayAreaOrganizerController; WindowOrganizerController(ActivityTaskManagerService atm) { mService = atm; mGlobalLock = atm.mGlobalLock; mTaskOrganizerController = new TaskOrganizerController(mService); + mDisplayAreaOrganizerController = new DisplayAreaOrganizerController(mService); } @Override @@ -318,6 +321,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return mTaskOrganizerController; } + @Override + public IDisplayAreaOrganizerController getDisplayAreaOrganizerController() { + enforceStackPermission("getDisplayAreaOrganizerController()"); + return mDisplayAreaOrganizerController; + } + int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) { int id = mBLASTSyncEngine.startSyncSet(this); mTransactionCallbacksByPendingSyncId.put(id, callback); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 740c5cb3e75a..023a1e8ede9f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -136,7 +136,7 @@ import android.app.admin.DevicePolicyCache; import android.app.admin.DevicePolicyEventLogger; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager.PasswordComplexity; -import android.app.admin.DevicePolicyManager.PersonalAppSuspensionReason; +import android.app.admin.DevicePolicyManager.PersonalAppsSuspensionReason; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DeviceStateCache; import android.app.admin.FactoryResetProtectionPolicy; @@ -935,10 +935,14 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) { handlePackagesChanged(null /* check all admins */, userHandle); - } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action) - || (Intent.ACTION_PACKAGE_ADDED.equals(action) - && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false))) { + } else if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle); + } else { + handleNewPackageInstalled(intent.getData().getSchemeSpecificPart(), userHandle); + } } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action) && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { handlePackagesChanged(intent.getData().getSchemeSpecificPart(), userHandle); @@ -2028,6 +2032,26 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return false; } + private void handleNewPackageInstalled(String packageName, int userHandle) { + // If personal apps were suspended by the admin, suspend the newly installed one. + if (!getUserData(userHandle).mAppsSuspended) { + return; + } + final String[] packagesToSuspend = { packageName }; + // Check if package is considered not suspendable? + if (mInjector.getPackageManager(userHandle) + .getUnsuspendablePackages(packagesToSuspend).length != 0) { + Slog.i(LOG_TAG, "Newly installed package is unsuspendable: " + packageName); + return; + } + try { + mIPackageManager.setPackagesSuspendedAsUser(packagesToSuspend, true /*suspend*/, + null, null, null, PLATFORM_PACKAGE_NAME, userHandle); + } catch (RemoteException ignored) { + // shouldn't happen. + } + } + /** * Unit test will subclass it to inject mocks. */ @@ -2110,6 +2134,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return mContext.getPackageManager(); } + PackageManager getPackageManager(int userId) { + return mContext + .createContextAsUser(UserHandle.of(userId), 0 /* flags */).getPackageManager(); + } + PowerManagerInternal getPowerManagerInternal() { return LocalServices.getService(PowerManagerInternal.class); } @@ -15650,7 +15679,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override - public @PersonalAppSuspensionReason int getPersonalAppsSuspendedReasons(ComponentName who) { + public @PersonalAppsSuspensionReason int getPersonalAppsSuspendedReasons(ComponentName who) { synchronized (getLockObject()) { final ActiveAdmin admin = getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, @@ -15669,7 +15698,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } - private @PersonalAppSuspensionReason int makeSuspensionReasons( + private @PersonalAppsSuspensionReason int makeSuspensionReasons( boolean explicit, boolean timeout) { int result = PERSONAL_APPS_NOT_SUSPENDED; if (explicit) { @@ -15793,7 +15822,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void applyPersonalAppsSuspension( - int profileUserId, @PersonalAppSuspensionReason int suspensionState) { + int profileUserId, @PersonalAppsSuspensionReason int suspensionState) { final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mAppsSuspended; final boolean shouldSuspend = suspensionState != PERSONAL_APPS_NOT_SUSPENDED; if (suspended != shouldSuspend) { @@ -15813,8 +15842,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mInjector.binderWithCleanCallingIdentity(() -> { try { final String[] appsToSuspend = - new PersonalAppsSuspensionHelper(mContext, mInjector.getPackageManager()) - .getPersonalAppsForSuspension(userId); + new PersonalAppsSuspensionHelper( + mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */)) + .getPersonalAppsForSuspension(); final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser( appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId); if (!ArrayUtils.isEmpty(failedPackages)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index 180acc85e5f6..d9db17eba887 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -20,7 +20,6 @@ import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,7 +31,7 @@ import android.os.IBinder; import android.os.ServiceManager; import android.provider.Settings; import android.text.TextUtils; -import android.util.Log; +import android.util.ArraySet; import android.util.Slog; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; @@ -43,7 +42,6 @@ import com.android.server.inputmethod.InputMethodManagerInternal; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -56,18 +54,21 @@ public class PersonalAppsSuspensionHelper { private final Context mContext; private final PackageManager mPackageManager; - public PersonalAppsSuspensionHelper(Context context, PackageManager packageManager) { + /** + * @param context Context for the user whose apps should to be suspended. + */ + public PersonalAppsSuspensionHelper(Context context) { mContext = context; - mPackageManager = packageManager; + mPackageManager = context.getPackageManager(); } /** * @return List of packages that should be suspended to limit personal use. */ - String[] getPersonalAppsForSuspension(@UserIdInt int userId) { + String[] getPersonalAppsForSuspension() { final List<PackageInfo> installedPackageInfos = - mPackageManager.getInstalledPackagesAsUser(0 /* flags */, userId); - final Set<String> result = new HashSet<>(); + mPackageManager.getInstalledPackages(0 /* flags */); + final Set<String> result = new ArraySet<>(); for (final PackageInfo packageInfo : installedPackageInfos) { final ApplicationInfo info = packageInfo.applicationInfo; if ((!info.isSystemApp() && !info.isUpdatedSystemApp()) @@ -77,11 +78,15 @@ public class PersonalAppsSuspensionHelper { } result.removeAll(getCriticalPackages()); result.removeAll(getSystemLauncherPackages()); - result.removeAll(getAccessibilityServices(userId)); - result.removeAll(getInputMethodPackages(userId)); - result.remove(getActiveLauncherPackages(userId)); - result.remove(getDialerPackage(userId)); - result.remove(getSettingsPackageName(userId)); + result.removeAll(getAccessibilityServices()); + result.removeAll(getInputMethodPackages()); + result.remove(getSettingsPackageName()); + + final String[] unsuspendablePackages = + mPackageManager.getUnsuspendablePackages(result.toArray(new String[0])); + for (final String pkg : unsuspendablePackages) { + result.remove(pkg); + } Slog.i(LOG_TAG, "Packages subject to suspension: " + String.join(",", result)); return result.toArray(new String[0]); @@ -104,7 +109,6 @@ public class PersonalAppsSuspensionHelper { final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(packageName, 0); if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) { - Log.d(LOG_TAG, "Not suspending system launcher package: " + packageName); result.add(packageName); } } catch (PackageManager.NameNotFoundException e) { @@ -114,81 +118,53 @@ public class PersonalAppsSuspensionHelper { return result; } - private List<String> getAccessibilityServices(int userId) { + private List<String> getAccessibilityServices() { final List<AccessibilityServiceInfo> accessibilityServiceInfos = - getAccessibilityManagerForUser(userId) + getAccessibilityManagerForUser(mContext.getUserId()) .getEnabledAccessibilityServiceList(FEEDBACK_ALL_MASK); final List<String> result = new ArrayList<>(); for (final AccessibilityServiceInfo serviceInfo : accessibilityServiceInfos) { final ComponentName componentName = ComponentName.unflattenFromString(serviceInfo.getId()); if (componentName != null) { - final String packageName = componentName.getPackageName(); - Slog.d(LOG_TAG, "Not suspending a11y service: " + packageName); - result.add(packageName); + result.add(componentName.getPackageName()); } } return result; } - private List<String> getInputMethodPackages(int userId) { - final List<InputMethodInfo> enabledImes = - InputMethodManagerInternal.get().getEnabledInputMethodListAsUser(userId); + private List<String> getInputMethodPackages() { + final List<InputMethodInfo> enabledImes = InputMethodManagerInternal.get() + .getEnabledInputMethodListAsUser(mContext.getUserId()); final List<String> result = new ArrayList<>(); for (final InputMethodInfo info : enabledImes) { - Slog.d(LOG_TAG, "Not suspending IME: " + info.getPackageName()); result.add(info.getPackageName()); } return result; } @Nullable - private String getActiveLauncherPackages(int userId) { - final Intent intent = new Intent(Intent.ACTION_MAIN); - intent.addCategory(Intent.CATEGORY_HOME); - intent.addCategory(Intent.CATEGORY_DEFAULT); - return getPackageNameForIntent("active launcher", intent, userId); - } - - @Nullable - private String getSettingsPackageName(int userId) { + private String getSettingsPackageName() { final Intent intent = new Intent(Settings.ACTION_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); - return getPackageNameForIntent("settings", intent, userId); - } - - @Nullable - private String getDialerPackage(int userId) { - final Intent intent = new Intent(Intent.ACTION_DIAL); - intent.addCategory(Intent.CATEGORY_DEFAULT); - return getPackageNameForIntent("dialer", intent, userId); - } - - @Nullable - private String getPackageNameForIntent(String name, Intent intent, int userId) { - final ResolveInfo resolveInfo = - mPackageManager.resolveActivityAsUser(intent, /* flags= */ 0, userId); + final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, /* flags= */ 0); if (resolveInfo != null) { - final String packageName = resolveInfo.activityInfo.packageName; - Slog.d(LOG_TAG, "Not suspending " + name + " package: " + packageName); - return packageName; + return resolveInfo.activityInfo.packageName; } return null; } private List<String> getCriticalPackages() { - final List<String> result = Arrays.asList(mContext.getResources() + return Arrays.asList(mContext.getResources() .getStringArray(R.array.config_packagesExemptFromSuspension)); - Slog.d(LOG_TAG, "Not suspending critical packages: " + String.join(",", result)); - return result; } private boolean hasLauncherIntent(String packageName) { final Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); intentToResolve.setPackage(packageName); - final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities( - intentToResolve, PackageManager.GET_UNINSTALLED_PACKAGES); + final List<ResolveInfo> resolveInfos = + mPackageManager.queryIntentActivities(intentToResolve, /* flags= */ 0); return resolveInfos != null && !resolveInfos.isEmpty(); } diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java index fa0febd7f20f..acdb68142178 100644 --- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java +++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java @@ -419,6 +419,38 @@ public class CrossProfileAppsServiceImplRoboTest { .isTrue(); } + @Test + public void canUserAttemptToConfigureInteractAcrossProfiles_packageNotInstalledInProfile_returnsTrue() { + mockUninstallCrossProfileAppFromWorkProfile(); + assertThat(mCrossProfileAppsServiceImpl + .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isTrue(); + } + + @Test + public void canUserAttemptToConfigureInteractAcrossProfiles_packageDoesNotRequestInteractAcrossProfiles_returnsFalse() + throws Exception { + mockCrossProfileAppDoesNotRequestInteractAcrossProfiles(); + assertThat(mCrossProfileAppsServiceImpl + .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isFalse(); + } + + @Test + public void canUserAttemptToConfigureInteractAcrossProfiles_packageNotWhitelisted_returnsTrue() { + mockCrossProfileAppNotWhitelisted(); + assertThat(mCrossProfileAppsServiceImpl + .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isTrue(); + } + + @Test + public void canUserAttemptToConfigureInteractAcrossProfiles_returnsTrue() { + assertThat(mCrossProfileAppsServiceImpl + .canUserAttemptToConfigureInteractAcrossProfiles(CROSS_PROFILE_APP_PACKAGE_NAME)) + .isTrue(); + } + private void explicitlySetInteractAcrossProfilesAppOp(@Mode int mode) { explicitlySetInteractAcrossProfilesAppOp(PERSONAL_PROFILE_UID, mode); } diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java index bb149cf327b8..09af4421406d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -496,7 +496,7 @@ public class AlarmManagerServiceTest { // This one should get deferred on set setTestAlarm(ELAPSED_REALTIME_WAKEUP, firstTrigger + quota, getNewMockPendingIntent()); - final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; + final long expectedNextTrigger = firstTrigger + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @@ -516,7 +516,7 @@ public class AlarmManagerServiceTest { mNowElapsedTest = mTestTimer.getElapsed(); mTestTimer.expire(); } - final long expectedNextTrigger = firstTrigger + 1 + mAppStandbyWindow; + final long expectedNextTrigger = firstTrigger + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @@ -676,7 +676,7 @@ public class AlarmManagerServiceTest { final int rareQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_RARE); // The last alarm should now be deferred. final long expectedNextTrigger = (firstTrigger + workingQuota - 1 - rareQuota) - + mAppStandbyWindow + 1; + + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } @@ -695,7 +695,7 @@ public class AlarmManagerServiceTest { } } // The last alarm should be deferred due to exceeding the quota - final long deferredTrigger = firstTrigger + 1 + mAppStandbyWindow; + final long deferredTrigger = firstTrigger + mAppStandbyWindow; assertEquals(deferredTrigger, mTestTimer.getElapsed()); // Upgrading the bucket now @@ -730,7 +730,7 @@ public class AlarmManagerServiceTest { mTestTimer.expire(); } // Any subsequent alarms in queue should all be deferred - assertEquals(firstTrigger + mAppStandbyWindow + 1, mTestTimer.getElapsed()); + assertEquals(firstTrigger + mAppStandbyWindow, mTestTimer.getElapsed()); // Paroling now assertAndHandleParoleChanged(true); @@ -744,7 +744,7 @@ public class AlarmManagerServiceTest { assertAndHandleParoleChanged(false); // Subsequent alarms should again get deferred - final long expectedNextTrigger = (firstTrigger + 5) + 1 + mAppStandbyWindow; + final long expectedNextTrigger = (firstTrigger + 5) + mAppStandbyWindow; assertEquals("Incorrect next alarm trigger", expectedNextTrigger, mTestTimer.getElapsed()); } 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 9db7d5e3acdd..3c2944560435 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -18,7 +18,6 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE; -import static android.app.Notification.CATEGORY_CALL; import static android.app.Notification.FLAG_AUTO_CANCEL; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; @@ -5237,141 +5236,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it a phone call - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setCategory(CATEGORY_CALL) - .addPerson(person) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_flag_phonecall", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - // Make sure it has foreground service - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - // yes phone call, yes person, yes foreground service, yes bubble - assertTrue(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it a phone call - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setCategory(CATEGORY_CALL) - .addPerson(person) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - // yes phone call, yes person, NO foreground service, no bubble - assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Make it a phone call - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setCategory(CATEGORY_CALL) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_phonecall_noPerson", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - // Make sure it has foreground service - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - // yes phone call, yes foreground service, BUT NO person, no bubble - assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException { - // Bubbles are allowed! - setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // No category - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .addPerson(person) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_phonecall_noCategory", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - // Make sure it has foreground service - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - // yes person, yes foreground service, BUT NO call, no bubble - assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException { // Bubbles are NOT allowed! setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */); @@ -5432,77 +5296,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException { - // Bubbles are not allowed! - setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it a phone call - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setCategory(CATEGORY_CALL) - .addPerson(person) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_phonecall_notAllowed", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - // Make sure it has foreground service - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - - mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - sbn.getId(), sbn.getNotification(), sbn.getUserId()); - waitForIdle(); - - // yes phone call, yes person, yes foreground service, but not allowed, no bubble - assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test - public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException { - // Bubbles are allowed, but not on channel. - setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */); - - // Give it bubble metadata - Notification.BubbleMetadata data = getBubbleMetadataBuilder().build(); - // Give it a person - Person person = new Person.Builder() - .setName("bubblebot") - .build(); - // Make it a phone call - Notification.Builder nb = new Notification.Builder(mContext, - mTestNotificationChannel.getId()) - .setCategory(CATEGORY_CALL) - .addPerson(person) - .setContentTitle("foo") - .setBubbleMetadata(data) - .setSmallIcon(android.R.drawable.sym_def_app_icon); - - StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, - "testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed", mUid, 0, - nb.build(), new UserHandle(mUid), null, 0); - // Make sure it has foreground service - sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE; - NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(), - nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); - waitForIdle(); - - // yes phone call, yes person, yes foreground service, but channel not allowed, no bubble - assertFalse(mService.getNotificationRecord( - sbn.getKey()).getNotification().isBubbleNotification()); - } - - @Test public void testCancelNotificationsFromApp_cancelsBubbles() throws Exception { final NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel); nrBubble.getSbn().getNotification().flags |= FLAG_BUBBLE; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 658ba1ba8a4a..6ec0f919ced7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1020,10 +1020,10 @@ public class ActivityStarterTests extends ActivityTestsBase { mSecondary = TaskTile.forToken(secondary.asBinder()); } @Override - public void taskAppeared(ActivityManager.RunningTaskInfo info) { + public void onTaskAppeared(ActivityManager.RunningTaskInfo info) { } @Override - public void taskVanished(ActivityManager.RunningTaskInfo info) { + public void onTaskVanished(ActivityManager.RunningTaskInfo info) { } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java index cc9173ad12ad..12bdec6ec1e1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaPolicyBuilderTest.java @@ -78,11 +78,11 @@ public class DisplayAreaPolicyBuilderTest { final Feature bar; DisplayAreaPolicyBuilder.Result policy = new DisplayAreaPolicyBuilder() - .addFeature(foo = new Feature.Builder(mPolicy, "Foo") + .addFeature(foo = new Feature.Builder(mPolicy, "Foo", 0) .upTo(TYPE_STATUS_BAR) .and(TYPE_NAVIGATION_BAR) .build()) - .addFeature(bar = new Feature.Builder(mPolicy, "Bar") + .addFeature(bar = new Feature.Builder(mPolicy, "Bar", 1) .all() .except(TYPE_STATUS_BAR) .build()) diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java index 5359bd33e70f..a96f40143e0f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java @@ -60,7 +60,6 @@ import android.platform.test.annotations.Presubmit; import android.util.ArrayMap; import android.util.Rational; import android.view.Display; -import android.view.SurfaceControl; import android.window.ITaskOrganizer; import android.window.WindowContainerTransaction; @@ -103,10 +102,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any()); + verify(organizer).onTaskAppeared(any()); task.removeImmediately(); - verify(organizer).taskVanished(any()); + verify(organizer).onTaskVanished(any()); } @Test @@ -117,10 +116,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); task.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any()); + verify(organizer).onTaskAppeared(any()); task.setTaskOrganizer(organizer2); - verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any()); + verify(organizer).onTaskVanished(any()); + verify(organizer2).onTaskAppeared(any()); } @Test @@ -131,10 +130,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_PINNED); stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer).taskAppeared(any()); + verify(organizer).onTaskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_PINNED); - verify(organizer).taskVanished(any()); - verify(organizer2).taskAppeared(any()); + verify(organizer).onTaskVanished(any()); + verify(organizer2).onTaskAppeared(any()); } @Test @@ -144,12 +143,12 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); stack.setTaskOrganizer(organizer); - verify(organizer).taskAppeared(any()); - assertTrue(stack.isControlledByTaskOrganizer()); + verify(organizer).onTaskAppeared(any()); + assertTrue(stack.isOrganized()); stack.setTaskOrganizer(null); - verify(organizer).taskVanished(any()); - assertFalse(stack.isControlledByTaskOrganizer()); + verify(organizer).onTaskVanished(any()); + assertFalse(stack.isOrganized()); } @Test @@ -159,12 +158,12 @@ public class TaskOrganizerTests extends WindowTestsBase { final ITaskOrganizer organizer = registerMockOrganizer(); stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer).taskAppeared(any()); - assertTrue(stack.isControlledByTaskOrganizer()); + verify(organizer).onTaskAppeared(any()); + assertTrue(stack.isOrganized()); mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer); - verify(organizer).taskVanished(any()); - assertFalse(stack.isControlledByTaskOrganizer()); + verify(organizer).onTaskVanished(any()); + assertFalse(stack.isOrganized()); } @Test @@ -179,23 +178,23 @@ public class TaskOrganizerTests extends WindowTestsBase { // First organizer is registered, verify a task appears when changing windowing mode stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer, times(1)).taskAppeared(any()); - assertTrue(stack.isControlledByTaskOrganizer()); + verify(organizer, times(1)).onTaskAppeared(any()); + assertTrue(stack.isOrganized()); // Now we replace the registration and1 verify the new organizer receives tasks // newly entering the windowing mode. final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer2).taskAppeared(any()); - assertTrue(stack2.isControlledByTaskOrganizer()); + verify(organizer2).onTaskAppeared(any()); + assertTrue(stack2.isOrganized()); // Now we unregister the second one, the first one should automatically be reregistered // so we verify that it's now seeing changes. mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer, times(2)).taskAppeared(any()); - assertTrue(stack3.isControlledByTaskOrganizer()); + verify(organizer, times(2)).onTaskAppeared(any()); + assertTrue(stack3.isOrganized()); } @Test @@ -206,10 +205,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final Task task = createTaskInStack(stack, 0 /* userId */); final Task task2 = createTaskInStack(stack, 0 /* userId */); stack.setWindowingMode(WINDOWING_MODE_PINNED); - verify(organizer, times(1)).taskAppeared(any()); + verify(organizer, times(1)).onTaskAppeared(any()); stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - verify(organizer, times(1)).taskVanished(any()); + verify(organizer, times(1)).onTaskVanished(any()); } @Test @@ -219,7 +218,7 @@ public class TaskOrganizerTests extends WindowTestsBase { stack.setWindowingMode(WINDOWING_MODE_PINNED); final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED); - verify(organizer, times(1)).taskAppeared(any()); + verify(organizer, times(1)).onTaskAppeared(any()); } @Test @@ -396,10 +395,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final boolean[] called = {false}; ITaskOrganizer listener = new ITaskOrganizer.Stub() { @Override - public void taskAppeared(RunningTaskInfo taskInfo) { } + public void onTaskAppeared(RunningTaskInfo taskInfo) { } @Override - public void taskVanished(RunningTaskInfo container) { } + public void onTaskVanished(RunningTaskInfo container) { } @Override public void onTaskInfoChanged(RunningTaskInfo info) throws RemoteException { @@ -447,10 +446,10 @@ public class TaskOrganizerTests extends WindowTestsBase { final ArrayMap<IBinder, RunningTaskInfo> lastReportedTiles = new ArrayMap<>(); ITaskOrganizer listener = new ITaskOrganizer.Stub() { @Override - public void taskAppeared(RunningTaskInfo taskInfo) { } + public void onTaskAppeared(RunningTaskInfo taskInfo) { } @Override - public void taskVanished(RunningTaskInfo container) { } + public void onTaskVanished(RunningTaskInfo container) { } @Override public void onTaskInfoChanged(RunningTaskInfo info) { @@ -667,11 +666,11 @@ public class TaskOrganizerTests extends WindowTestsBase { RunningTaskInfo mInfo; @Override - public void taskAppeared(RunningTaskInfo info) { + public void onTaskAppeared(RunningTaskInfo info) { mInfo = info; } @Override - public void taskVanished(RunningTaskInfo info) { + public void onTaskVanished(RunningTaskInfo info) { } @Override public void onTaskInfoChanged(RunningTaskInfo info) { diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java index a6c5c3ba932b..35464340550b 100644 --- a/telephony/java/android/telephony/SubscriptionInfo.java +++ b/telephony/java/android/telephony/SubscriptionInfo.java @@ -449,13 +449,21 @@ public class SubscriptionInfo implements Parcelable { } /** - * @return the number of this subscription. + * @return the number of this subscription if the calling app has been granted the + * READ_PHONE_NUMBERS permission, or an empty string otherwise */ public String getNumber() { return mNumber; } /** + * @hide + */ + public void clearNumber() { + mNumber = ""; + } + + /** * @return the data roaming state for this subscription, either * {@link SubscriptionManager#DATA_ROAMING_ENABLE} or {@link SubscriptionManager#DATA_ROAMING_DISABLE}. */ diff --git a/tests/BootImageProfileTest/AndroidTest.xml b/tests/BootImageProfileTest/AndroidTest.xml index d7f820411f27..7e97fa3a8ff1 100644 --- a/tests/BootImageProfileTest/AndroidTest.xml +++ b/tests/BootImageProfileTest/AndroidTest.xml @@ -23,6 +23,10 @@ <option name="force-skip-system-props" value="true" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.DeviceCleaner"> + <option name="cleanup-action" value="REBOOT" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > <option name="class" value="com.android.bootimageprofile.BootImageProfileTest" /> </test> diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java index f186ed3b75cf..5afd39ea9de1 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java @@ -150,7 +150,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { }; @Override - public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + public void onTaskAppeared(ActivityManager.RunningTaskInfo ti) { if (!gotFirstTask) { mTaskView1.reparentTask(ti.token); gotFirstTask = true; @@ -158,7 +158,7 @@ public class TaskOrganizerMultiWindowTest extends Activity { mTaskView2.reparentTask(ti.token); } } - public void taskVanished(ActivityManager.RunningTaskInfo ti) { + public void onTaskVanished(ActivityManager.RunningTaskInfo ti) { } @Override public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java index a2f40dc0fd6f..520bc255499b 100644 --- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java +++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java @@ -38,7 +38,7 @@ public class TaskOrganizerPipTest extends Service { TaskView mTaskView; class Organizer extends ITaskOrganizer.Stub { - public void taskAppeared(ActivityManager.RunningTaskInfo ti) { + public void onTaskAppeared(ActivityManager.RunningTaskInfo ti) { mTaskView.reparentTask(ti.token); final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -48,7 +48,7 @@ public class TaskOrganizerPipTest extends Service { } catch (Exception e) { } } - public void taskVanished(ActivityManager.RunningTaskInfo ti) { + public void onTaskVanished(ActivityManager.RunningTaskInfo ti) { } public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) { } diff --git a/wifi/tests/AndroidTest.xml b/wifi/tests/AndroidTest.xml index 34e2e3af9cda..ea5043ae5f65 100644 --- a/wifi/tests/AndroidTest.xml +++ b/wifi/tests/AndroidTest.xml @@ -30,5 +30,9 @@ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> <option name="mainline-module-package-name" value="com.google.android.wifi" /> + <!-- TODO(b/151836001): com.android.wifi doesn't guarantee it is a Mainline module since + it could still be OEM customized. Workaround so that this test will still run on + AOSP builds. --> + <option name="mainline-module-package-name" value="com.android.wifi" /> </object> </configuration> |