diff options
84 files changed, 2260 insertions, 914 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index 755380e93b2a..777cbc540b54 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -126,6 +126,7 @@ package android.app { public class ActivityTaskManager { method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>); + method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks(); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect); diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index e251399776fb..ba4cf11b84f1 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -382,10 +382,6 @@ public: inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } - inline void setResetState(int32_t resetState) { - mResetState = resetState; - } - // Default value = false inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } @@ -398,12 +394,6 @@ public: // Default value = false inline bool isUidField() const { return getValueFromBitmask(UID_POS); } - // If a reset state is not sent in the StatsEvent, returns -1. Note that a - // reset satate is only sent if and only if a reset should be triggered. - inline int32_t getResetState() const { - return mResetState; - } - private: inline void setBitmaskAtPos(int pos, bool value) { mBooleanBitmask &= ~(1 << pos); // clear @@ -417,8 +407,6 @@ private: // This is a bitmask over all annotations stored in boolean form. Because // there are only 4 booleans, just one byte is required. uint8_t mBooleanBitmask = 0; - - int32_t mResetState = -1; }; /** diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index d914ab2436c7..f91c6002bf5e 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -389,15 +389,24 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); + // Tell StatsdStats about new event + const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); + int atomId = event->GetTagId(); + StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC); + if (!event->isValid()) { + StatsdStats::getInstance().noteAtomError(atomId); + return; + } + // Hard-coded logic to update train info on disk and fill in any information // this log event may be missing. - if (event->GetTagId() == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { + if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { onBinaryPushStateChangedEventLocked(event); } // Hard-coded logic to update experiment ids on disk for certain rollback // types and fill the rollback atom with experiment ids - if (event->GetTagId() == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { + if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { onWatchdogRollbackOccurredLocked(event); } @@ -406,16 +415,11 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { ALOGI("%s", event->ToString().c_str()); } #endif - const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); - resetIfConfigTtlExpiredLocked(eventElapsedTimeNs); - StatsdStats::getInstance().noteAtomLogged( - event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC); - // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto - if (event->GetTagId() == android::os::statsd::util::ISOLATED_UID_CHANGED) { + if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) { onIsolatedUidChangedEventLocked(*event); } @@ -432,7 +436,7 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { } - if (event->GetTagId() != android::os::statsd::util::ISOLATED_UID_CHANGED) { + if (atomId != android::os::statsd::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); } diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index 933f48d1714b..3618bb0dd08b 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -67,8 +67,14 @@ bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { lock_guard<mutex> lk(*cv_mutex); for (const StatsEventParcel& parcel: output) { shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1); - event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size()); - sharedData->push_back(event); + bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(), + parcel.buffer.size()); + if (valid) { + sharedData->push_back(event); + } else { + StatsdStats::getInstance().noteAtomError(event->GetTagId(), + /*pull=*/true); + } } *pullSuccess = success; *pullFinish = true; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index db637b15e969..46f5dbda5521 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -54,6 +54,7 @@ const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19; const int FIELD_ID_ATOM_STATS_TAG = 1; const int FIELD_ID_ATOM_STATS_COUNT = 2; +const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3; const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1; @@ -549,6 +550,20 @@ void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayN std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs); } +void StatsdStats::noteAtomError(int atomTag, bool pull) { + lock_guard<std::mutex> lock(mLock); + if (pull) { + mPulledAtomStats[atomTag].atomErrorCount++; + return; + } + + bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end()); + bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize); + if (!full || present) { + mPushedAtomErrorStats[atomTag]++; + } +} + StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { auto atomMetricStatsIter = mAtomMetricStats.find(metricId); if (atomMetricStatsIter != mAtomMetricStats.end()) { @@ -604,9 +619,11 @@ void StatsdStats::resetInternalLocked() { pullStats.second.pullExceedMaxDelay = 0; pullStats.second.registeredCount = 0; pullStats.second.unregisteredCount = 0; + pullStats.second.atomErrorCount = 0; } mAtomMetricStats.clear(); mActivationBroadcastGuardrailStats.clear(); + mPushedAtomErrorStats.clear(); } string buildTimeString(int64_t timeSec) { @@ -617,6 +634,15 @@ string buildTimeString(int64_t timeSec) { return string(timeBuffer); } +int StatsdStats::getPushedAtomErrors(int atomId) const { + const auto& it = mPushedAtomErrorStats.find(atomId); + if (it != mPushedAtomErrorStats.end()) { + return it->second; + } else { + return 0; + } +} + void StatsdStats::dumpStats(int out) const { lock_guard<std::mutex> lock(mLock); time_t t = mStartTimeSec; @@ -721,11 +747,13 @@ void StatsdStats::dumpStats(int out) const { const size_t atomCounts = mPushedAtomStats.size(); for (size_t i = 2; i < atomCounts; i++) { if (mPushedAtomStats[i] > 0) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]); + dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i], + getPushedAtomErrors((int)i)); } } for (const auto& pair : mNonPlatformPushedAtomStats) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second); + dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second, + getPushedAtomErrors(pair.first)); } dprintf(out, "********Pulled Atom stats***********\n"); @@ -737,13 +765,15 @@ void StatsdStats::dumpStats(int out) const { "nanos)%lld, " " (max pull delay nanos)%lld, (data error)%ld\n" " (pull timeout)%ld, (pull exceed max delay)%ld\n" - " (registered count) %ld, (unregistered count) %ld\n", + " (registered count) %ld, (unregistered count) %ld\n" + " (atom error count) %d\n", (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, - pair.second.registeredCount, pair.second.unregisteredCount); + pair.second.registeredCount, pair.second.unregisteredCount, + pair.second.atomErrorCount); } if (mAnomalyAlarmRegisteredStats > 0) { @@ -919,6 +949,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]); + int errors = getPushedAtomErrors(i); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } } @@ -928,6 +962,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second); + int errors = getPushedAtomErrors(pair.first); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index ff31e9e73fd0..21e524a9fadf 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -461,6 +461,16 @@ public: */ void noteActivationBroadcastGuardrailHit(const int uid); + /** + * Reports that an atom is erroneous or cannot be parsed successfully by + * statsd. An atom tag of 0 indicates that the client did not supply the + * atom id within the encoding. + * + * For pushed atoms only, this call should be preceded by a call to + * noteAtomLogged. + */ + void noteAtomError(int atomTag, bool pull=false); + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue @@ -499,6 +509,7 @@ public: long emptyData = 0; long registeredCount = 0; long unregisteredCount = 0; + int32_t atomErrorCount = 0; } PulledAtomStats; typedef struct { @@ -546,6 +557,12 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map<int, PulledAtomStats> mPulledAtomStats; + // Stores the number of times a pushed atom was logged erroneously. The + // corresponding counts for pulled atoms are stored in PulledAtomStats. + // The max size of this map is kMaxAtomErrorsStatsSize. + std::map<int, int> mPushedAtomErrorStats; + int kMaxPushedAtomErrorStatsSize = 100; + // Maps metric ID to its stats. The size is capped by the number of metrics. std::map<int64_t, AtomMetricStats> mAtomMetricStats; @@ -613,6 +630,8 @@ private: void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats); + int getPushedAtomErrors(int atomId) const; + /** * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference * will live as long as `this`. @@ -631,6 +650,7 @@ private: FRIEND_TEST(StatsdStatsTest, TestPullAtomStats); FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); + FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); }; } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 61cd01728ab1..eb830e114b40 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -114,14 +114,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::~LogEvent() { - if (mContext) { - // This is for the case when LogEvent is created using the test interface - // but init() isn't called. - android_log_destroy(&mContext); - } -} - void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue<int32_t>(); addToValues(pos, depth, value, last); @@ -303,8 +295,7 @@ void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { return; } - int32_t resetState = readNextValue<int32_t>(); - mValues[mValues.size() - 1].mAnnotations.setResetState(resetState); + mResetState = readNextValue<int32_t>(); } void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { @@ -386,7 +377,6 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { typeInfo = readNextValue<uint8_t>(); uint8_t typeId = getTypeId(typeInfo); - // TODO(b/144373276): handle errors passed to the socket switch (typeId) { case BOOL_TYPE: parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); @@ -413,8 +403,13 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0]; break; + case ERROR_TYPE: + mErrorBitmask = readNextValue<int32_t>(); + mValid = false; + break; default: mValid = false; + break; } } diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 41fdcc2cbe7a..dedcfaf6cd87 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -70,7 +70,7 @@ public: explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& installTrainInfo); - ~LogEvent(); + ~LogEvent() {} /** * Get the timestamp associated with this event. @@ -184,6 +184,12 @@ public: return mExclusiveStateFieldIndex; } + // If a reset state is not sent in the StatsEvent, returns -1. Note that a + // reset state is sent if and only if a reset should be triggered. + inline int getResetState() const { + return mResetState; + } + inline LogEvent makeCopy() { return LogEvent(*this); } @@ -204,6 +210,14 @@ public: return BAD_INDEX; } + bool isValid() const { + return mValid; + } + + int32_t getErrorBitmask() const { + return mErrorBitmask; + } + private: /** * Only use this if copy is absolutely needed. @@ -230,12 +244,13 @@ private: bool checkPreviousValueType(Type expected); /** - * The below three variables are only valid during the execution of + * The below two variables are only valid during the execution of * parseBuffer. There are no guarantees about the state of these variables * before/after. */ uint8_t* mBuf; uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed + bool mValid = true; // stores whether the event we received from the socket is valid /** @@ -287,19 +302,15 @@ private: // matching. std::vector<FieldValue> mValues; - // This field is used when statsD wants to create log event object and write fields to it. After - // calling init() function, this object would be destroyed to save memory usage. - // When the log event is created from log msg, this field is never initiated. - android_log_context mContext = NULL; - // The timestamp set by the logd. int64_t mLogdTimestampNs; // The elapsed timestamp set by statsd log writer. int64_t mElapsedTimestampNs; - // The atom tag of the event. - int mTagId; + // The atom tag of the event (defaults to 0 if client does not + // appropriately set the atom id). + int mTagId = 0; // The uid of the logging client (defaults to -1). int32_t mLogUid = -1; @@ -307,11 +318,15 @@ private: // The pid of the logging client (defaults to -1). int32_t mLogPid = -1; + // Bitmask of errors sent by StatsEvent/AStatsEvent. + int32_t mErrorBitmask = 0; + // Annotations bool mTruncateTimestamp = false; int mUidFieldIndex = -1; int mAttributionChainIndex = -1; int mExclusiveStateFieldIndex = -1; + int mResetState = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index b7f314a819df..b63713b64c5d 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -51,7 +51,7 @@ void StateTracker::onLogEvent(const LogEvent& event) { return; } - const int32_t resetState = stateValue.mAnnotations.getResetState(); + const int32_t resetState = event.getResetState(); if (resetState != -1) { VLOG("StateTracker new reset state: %d", resetState); handleReset(eventTimeNs, resetState); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index f4247eca6e23..868247bc9d64 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -425,6 +425,7 @@ message StatsdStatsReport { message AtomStats { optional int32 tag = 1; optional int32 count = 2; + optional int32 error_count = 3; } repeated AtomStats atom_stats = 7; @@ -460,6 +461,7 @@ message StatsdStatsReport { optional int64 empty_data = 15; optional int64 registered_count = 16; optional int64 unregistered_count = 17; + optional int32 atom_error_count = 18; } repeated PulledAtomStats pulled_atom_stats = 10; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 563531351fe0..f9fddc8a7c8a 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -80,6 +80,8 @@ const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14; const int FIELD_ID_EMPTY_DATA = 15; const int FIELD_ID_PULL_REGISTERED_COUNT = 16; const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; +const int FIELD_ID_ATOM_ERROR_COUNT = 18; + // for AtomMetricStats proto const int FIELD_ID_ATOM_METRIC_STATS = 17; const int FIELD_ID_METRIC_ID = 1; @@ -492,6 +494,7 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> (long long) pair.second.registeredCount); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, (long long) pair.second.unregisteredCount); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); protoOutput->end(token); } diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index bb4578d9b701..e52e2d024e94 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -352,7 +352,7 @@ TEST(LogEventTest, TestResetStateAnnotation) { const vector<FieldValue>& values = event.getValues(); EXPECT_EQ(values.size(), 1); - EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState); + EXPECT_EQ(event.getResetState(), resetState); } } // namespace statsd diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 129fafa75b41..cdde603f4c0b 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -486,6 +486,41 @@ TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { EXPECT_TRUE(uid2Good); } +TEST(StatsdStatsTest, TestAtomErrorStats) { + StatsdStats stats; + + int pushAtomTag = 100; + int pullAtomTag = 1000; + int numErrors = 10; + + for (int i = 0; i < numErrors; i++) { + // We must call noteAtomLogged as well because only those pushed atoms + // that have been logged will have stats printed about them in the + // proto. + stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0); + stats.noteAtomError(pushAtomTag, /*pull=*/false); + + stats.noteAtomError(pullAtomTag, /*pull=*/true); + } + + vector<uint8_t> output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + + // Check error count = numErrors for push atom + EXPECT_EQ(1, report.atom_stats_size()); + const auto& pushedAtomStats = report.atom_stats(0); + EXPECT_EQ(pushAtomTag, pushedAtomStats.tag()); + EXPECT_EQ(numErrors, pushedAtomStats.error_count()); + + // Check error count = numErrors for pull atom + EXPECT_EQ(1, report.pulled_atom_stats_size()); + const auto& pulledAtomStats = report.pulled_atom_stats(0); + EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id()); + EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 1b941defba42..1cc63da3db0a 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -23,8 +23,10 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -430,4 +432,14 @@ public class ActivityTaskManager { throw e.rethrowFromSystemServer(); } } + + /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */ + public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) { + final Configuration config = context.getResources().getConfiguration(); + int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; + return (modeType != Configuration.UI_MODE_TYPE_CAR + && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER) + && modeType != Configuration.UI_MODE_TYPE_TELEVISION + && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); + } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cc380f32297e..e4dbd63765b6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -514,33 +514,24 @@ public interface WindowManager extends ViewManager { int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3; /** - * Parcel key for the screen shot bitmap sent with messages of type - * {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap} - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap"; - - /** - * Parcel key for the screen bounds of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds"; - - /** - * Parcel key for the task id of the task that the screen shot was taken of, sent with messages - * of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id"; - - /** - * Parcel key for the visible insets of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in - * screen coordinates. + * Enum listing the possible sources from which a screenshot was originated. Used for logging. + * * @hide */ - String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets"; + @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS, + ScreenshotSource.SCREENSHOT_KEY_CHORD, + ScreenshotSource.SCREENSHOT_KEY_OTHER, + ScreenshotSource.SCREENSHOT_OVERVIEW, + ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS, + ScreenshotSource.SCREENSHOT_OTHER}) + @interface ScreenshotSource { + int SCREENSHOT_GLOBAL_ACTIONS = 0; + int SCREENSHOT_KEY_CHORD = 1; + int SCREENSHOT_KEY_OTHER = 2; + int SCREENSHOT_OVERVIEW = 3; + int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4; + int SCREENSHOT_OTHER = 5; + } /** * @hide diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 3eb0923363f2..2c4892561ddd 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -50,6 +50,9 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * This is used in conjunction with @@ -74,11 +77,13 @@ public class IntentForwarderActivity extends Activity { private Injector mInjector; private MetricsLogger mMetricsLogger; + protected ExecutorService mExecutorService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInjector = createInjector(); + mExecutorService = Executors.newSingleThreadExecutor(); Intent intentReceived = getIntent(); String className = intentReceived.getComponent().getClassName(); @@ -118,30 +123,9 @@ public class IntentForwarderActivity extends Activity { mInjector.getIPackageManager(), getContentResolver()); if (newIntent != null) { newIntent.prepareToLeaveUser(callingUserId); - - final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, - targetUserId); - try { - startActivityAsCaller(newIntent, null, null, false, targetUserId); - } catch (RuntimeException e) { - int launchedFromUid = -1; - String launchedFromPackage = "?"; - try { - launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( - getActivityToken()); - launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage( - getActivityToken()); - } catch (RemoteException ignored) { - } - - Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package " - + launchedFromPackage + ", while running in " - + ActivityThread.currentProcessName(), e); - } - - if (shouldShowDisclosure(ri, intentReceived)) { - mInjector.showToast(userMessageId, Toast.LENGTH_LONG); - } + maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId); + CompletableFuture.runAsync(() -> startActivityAsCaller( + newIntent, targetUserId), mExecutorService); } else { Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user " + callingUserId + " to user " + targetUserId); @@ -149,6 +133,44 @@ public class IntentForwarderActivity extends Activity { finish(); } + private void maybeShowDisclosureAsync( + Intent intentReceived, Intent newIntent, int userId, int messageId) { + final CompletableFuture<ResolveInfo> resolveInfoFuture = + mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId); + resolveInfoFuture.thenAcceptAsync(ri -> { + if (shouldShowDisclosure(ri, intentReceived)) { + mInjector.showToast(messageId, Toast.LENGTH_LONG); + } + }, getApplicationContext().getMainExecutor()); + } + + private void startActivityAsCaller(Intent newIntent, int userId) { + try { + startActivityAsCaller( + newIntent, + /* options= */ null, + /* permissionToken= */ null, + /* ignoreTargetSecurity= */ false, + userId); + } catch (RuntimeException e) { + int launchedFromUid = -1; + String launchedFromPackage = "?"; + try { + launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( + getActivityToken()); + launchedFromPackage = ActivityTaskManager.getService() + .getLaunchedFromPackage(getActivityToken()); + } catch (RemoteException ignored) { + } + + Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package " + + launchedFromPackage + ", while running in " + + ActivityThread.currentProcessName(), e); + } finally { + mExecutorService.shutdown(); + } + } + private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) { // When showing the sharesheet, instead of forwarding to the other profile, // we launch the sharesheet in the current user and select the other tab. @@ -322,8 +344,11 @@ public class IntentForwarderActivity extends Activity { } @Override - public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { - return getPackageManager().resolveActivityAsUser(intent, flags, userId); + @Nullable + public CompletableFuture<ResolveInfo> resolveActivityAsUser( + Intent intent, int flags, int userId) { + return CompletableFuture.supplyAsync( + () -> getPackageManager().resolveActivityAsUser(intent, flags, userId)); } @Override @@ -339,7 +364,7 @@ public class IntentForwarderActivity extends Activity { PackageManager getPackageManager(); - ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); + CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId); void showToast(@StringRes int messageId, int duration); } diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index ebfea450af88..56a6db95badc 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -46,6 +46,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver { r.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT), false, this, UserHandle.USER_ALL); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), + false, this, UserHandle.USER_ALL); } public void unregister() { @@ -68,6 +71,11 @@ public class GestureNavigationSettingsObserver extends ContentObserver { return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT); } + public boolean areNavigationButtonForcedVisible() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0; + } + private int getSensitivity(Resources userRes, String side) { final int inset = userRes.getDimensionPixelSize( com.android.internal.R.dimen.config_backGestureInset); diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 7cff90bbf437..adadc5e20549 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -1,5 +1,7 @@ package com.android.internal.util; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -10,11 +12,12 @@ import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -23,6 +26,109 @@ import android.view.WindowManager; import java.util.function.Consumer; public class ScreenshotHelper { + + /** + * Describes a screenshot request (to make it easier to pass data through to the handler). + */ + public static class ScreenshotRequest implements Parcelable { + private int mSource; + private boolean mHasStatusBar; + private boolean mHasNavBar; + private Bitmap mBitmap; + private Rect mBoundsInScreen; + private Insets mInsets; + private int mTaskId; + + ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { + mSource = source; + mHasStatusBar = hasStatus; + mHasNavBar = hasNav; + } + + ScreenshotRequest( + int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + mSource = source; + mBitmap = bitmap; + mBoundsInScreen = boundsInScreen; + mInsets = insets; + mTaskId = taskId; + } + + ScreenshotRequest(Parcel in) { + mSource = in.readInt(); + mHasStatusBar = in.readBoolean(); + mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { + mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); + mInsets = in.readParcelable(Insets.class.getClassLoader()); + mTaskId = in.readInt(); + } + } + + public int getSource() { + return mSource; + } + + public boolean getHasStatusBar() { + return mHasStatusBar; + } + + public boolean getHasNavBar() { + return mHasNavBar; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Rect getBoundsInScreen() { + return mBoundsInScreen; + } + + public Insets getInsets() { + return mInsets; + } + + public int getTaskId() { + return mTaskId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSource); + dest.writeBoolean(mHasStatusBar); + dest.writeBoolean(mHasNavBar); + if (mBitmap == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(mBitmap, 0); + dest.writeParcelable(mBoundsInScreen, 0); + dest.writeParcelable(mInsets, 0); + dest.writeInt(mTaskId); + } + } + + public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR = + new Parcelable.Creator<ScreenshotRequest>() { + + @Override + public ScreenshotRequest createFromParcel(Parcel source) { + return new ScreenshotRequest(source); + } + + @Override + public ScreenshotRequest[] newArray(int size) { + return new ScreenshotRequest[size]; + } + }; + } private static final String TAG = "ScreenshotHelper"; // Time until we give up on the screenshot & show an error instead. @@ -36,8 +142,10 @@ public class ScreenshotHelper { mContext = context; } + + /** - * Request a screenshot be taken with a specific timeout. + * Request a screenshot be taken. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -47,6 +155,32 @@ public class ScreenshotHelper { * or * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} * @param hasStatus {@code true} if the status bar is currently showing. {@code false} + * if not. + * @param hasNav {@code true} if the navigation bar is currently showing. {@code + * false} if not. + * @param source The source of the screenshot request. One of + * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD, + * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER} + * @param handler A handler used in case the screenshot times out + * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the + * screenshot was taken. + */ + public void takeScreenshot(final int screenshotType, final boolean hasStatus, + final boolean hasNav, int source, @NonNull Handler handler, + @Nullable Consumer<Uri> completionConsumer) { + ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav); + takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, + completionConsumer); + } + + /** + * Request a screenshot be taken, with provided reason. + * + * @param screenshotType The type of screenshot, for example either + * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN} + * or + * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param hasStatus {@code true} if the status bar is currently showing. {@code false} * if * not. * @param hasNav {@code true} if the navigation bar is currently showing. {@code @@ -64,7 +198,7 @@ public class ScreenshotHelper { } /** - * Request a screenshot be taken. + * Request a screenshot be taken with a specific timeout. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -89,9 +223,9 @@ public class ScreenshotHelper { public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, long timeoutMs, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { - takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null, - completionConsumer - ); + ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus, + hasNav); + takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer); } /** @@ -106,23 +240,16 @@ public class ScreenshotHelper { * screenshot was taken. */ public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, @NonNull Handler handler, - @Nullable Consumer<Uri> completionConsumer) { - Bundle imageBundle = new Bundle(); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets); - imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId); - - takeScreenshot( - WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, - false, false, // ignored when image bundle is set - SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer); + @NonNull Insets insets, int taskId, int source, + @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { + ScreenshotRequest screenshotRequest = + new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, + handler, screenshotRequest, completionConsumer); } - private void takeScreenshot(final int screenshotType, final boolean hasStatus, - final boolean hasNav, long timeoutMs, @NonNull Handler handler, - @Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) { + private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler, + ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; @@ -157,7 +284,7 @@ public class ScreenshotHelper { return; } Messenger messenger = new Messenger(service); - Message msg = Message.obtain(null, screenshotType); + Message msg = Message.obtain(null, screenshotType, screenshotRequest); final ServiceConnection myConn = this; Handler h = new Handler(handler.getLooper()) { @Override @@ -175,12 +302,6 @@ public class ScreenshotHelper { } }; msg.replyTo = new Messenger(h); - msg.arg1 = hasStatus ? 1 : 0; - msg.arg2 = hasNav ? 1 : 0; - - if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - msg.setData(providedImage); - } try { messenger.send(msg); diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 5424b6f19038..43590bae6770 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class IntentForwarderActivityTest { @@ -633,6 +635,11 @@ public class IntentForwarderActivityTest { public void onCreate(@Nullable Bundle savedInstanceState) { getIntent().setComponent(sComponentName); super.onCreate(savedInstanceState); + try { + mExecutorService.awaitTermination(/* timeout= */ 30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } } @Override @@ -671,7 +678,8 @@ public class IntentForwarderActivityTest { } @Override - public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + public CompletableFuture<ResolveInfo> resolveActivityAsUser( + Intent intent, int flags, int userId) { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = sPackageName; activityInfo.name = sActivityName; @@ -680,7 +688,7 @@ public class IntentForwarderActivityTest { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; - return resolveInfo; + return CompletableFuture.completedFuture(resolveInfo); } @Override diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java index cd6b3af5fa6d..fe33cd80f735 100644 --- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java @@ -36,6 +36,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; +import android.view.WindowManager; import androidx.test.runner.AndroidJUnit4; @@ -91,7 +92,8 @@ public final class ScreenshotHelperTest { public void testProvidedImageScreenshot() { mScreenshotHelper.provideScreenshot( Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(), - Insets.of(0, 0, 0, 0), 1, mHandler, null); + Insets.of(0, 0, 0, 0), 1, + WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null); } @Test diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java index 14249cbe8945..8e8dfaf24b9a 100644 --- a/media/java/android/media/AudioPortEventHandler.java +++ b/media/java/android/media/AudioPortEventHandler.java @@ -78,7 +78,8 @@ class AudioPortEventHandler { listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj); } } else { - listeners = mListeners; + listeners = (ArrayList<AudioManager.OnAudioPortUpdateListener>) + mListeners.clone(); } } // reset audio port cache if the event corresponds to a change coming diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index 97d32d88c7f2..c652628eb425 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -388,7 +388,7 @@ public final class MediaCas implements AutoCloseable { @Override public void onReclaimResources() { synchronized (mSessionMap) { - mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close()); + mSessionMap.forEach((casSession, sessionResourceHandle) -> casSession.close()); } mEventHandler.sendMessage(mEventHandler.obtainMessage( EventHandler.MSG_CAS_RESOURCE_LOST)); @@ -868,7 +868,7 @@ public final class MediaCas implements AutoCloseable { } } - private int getSessionResourceId() throws MediaCasException { + private int getSessionResourceHandle() throws MediaCasException { validateInternalStates(); int[] sessionResourceHandle = new int[1]; @@ -881,14 +881,14 @@ public final class MediaCas implements AutoCloseable { "insufficient resource to Open Session"); } } - return sessionResourceHandle[0]; + return sessionResourceHandle[0]; } - private void addSessionToResourceMap(Session session, int sessionResourceId) { + private void addSessionToResourceMap(Session session, int sessionResourceHandle) { - if (sessionResourceId != -1) { + if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { synchronized (mSessionMap) { - mSessionMap.put(session, sessionResourceId); + mSessionMap.put(session, sessionResourceHandle); } } } @@ -918,13 +918,13 @@ public final class MediaCas implements AutoCloseable { * @throws MediaCasStateException for CAS-specific state exceptions. */ public Session openSession() throws MediaCasException { - int sessionResourceId = getSessionResourceId(); + int sessionResourceHandle = getSessionResourceHandle(); try { OpenSessionCallback cb = new OpenSessionCallback(); mICas.openSession(cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); - addSessionToResourceMap(cb.mSession, sessionResourceId); + addSessionToResourceMap(cb.mSession, sessionResourceHandle); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); @@ -952,7 +952,7 @@ public final class MediaCas implements AutoCloseable { @Nullable public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode) throws MediaCasException { - int sessionResourceId = getSessionResourceId(); + int sessionResourceHandle = getSessionResourceHandle(); if (mICasV12 == null) { Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface"); @@ -963,7 +963,7 @@ public final class MediaCas implements AutoCloseable { OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback(); mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); - addSessionToResourceMap(cb.mSession, sessionResourceId); + addSessionToResourceMap(cb.mSession, sessionResourceHandle); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java index adf4d3dcfa09..022cfeeb4e43 100644 --- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java +++ b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java @@ -80,7 +80,8 @@ public class AudioVolumeGroupChangeHandler { (AudioManager.VolumeGroupCallback) msg.obj); } } else { - listeners = mListeners; + listeners = (ArrayList<AudioManager.VolumeGroupCallback>) + mListeners.clone(); } } if (listeners.isEmpty()) { diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index 7077cd1b76a0..487b444eb627 100644 --- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -218,11 +218,11 @@ interface ITunerResourceManager { * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request. * * @param request {@link CasSessionRequest} information of the current request. - * @param sessionResourceId a one-element array to return the granted cas session id. + * @param casSessionHandle a one-element array to return the granted cas session handle. * * @return true if there is CAS session granted. */ - boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId); + boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle); /* * This API is used by the Tuner framework to request an available Lnb from the TunerHAL. @@ -276,7 +276,7 @@ interface ITunerResourceManager { * * <p>Client must call this whenever it releases a descrambler. * - * @param demuxHandle the handle of the released Tuner Descrambler. + * @param descramblerHandle the handle of the released Tuner Descrambler. * @param clientId the id of the client that is releasing the descrambler. */ void releaseDescrambler(in int descramblerHandle, int clientId); @@ -288,10 +288,10 @@ interface ITunerResourceManager { * * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release. * - * @param sessionResourceId the id of the released CAS session. + * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - void releaseCasSession(in int sessionResourceId, int clientId); + void releaseCasSession(in int casSessionHandle, int clientId); /* * Notifies the TRM that the Lnb with the given handle was released. diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index b4dcc5d8b3df..be102d8acc10 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -362,17 +362,16 @@ public class TunerResourceManager { * request. * * @param request {@link CasSessionRequest} information of the current request. - * @param sessionResourceId a one-element array to return the granted cas session id. - * If no CAS granted, this will return - * {@link #INVALID_CAS_SESSION_RESOURCE_ID}. + * @param casSessionHandle a one-element array to return the granted cas session handel. + * If no CAS granted, this will return {@link #INVALID_RESOURCE_HANDLE}. * * @return true if there is CAS session granted. */ public boolean requestCasSession(@NonNull CasSessionRequest request, - @NonNull int[] sessionResourceId) { + @NonNull int[] casSessionHandle) { boolean result = false; try { - result = mService.requestCasSession(request, sessionResourceId); + result = mService.requestCasSession(request, casSessionHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -471,12 +470,12 @@ public class TunerResourceManager { * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this * release. * - * @param sessionResourceId the id of the released CAS session. + * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - public void releaseCasSession(int sessionResourceId, int clientId) { + public void releaseCasSession(int casSessionHandle, int clientId) { try { - mService.releaseCasSession(sessionResourceId, clientId); + mService.releaseCasSession(casSessionHandle, clientId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml index 7004fb64ba06..8b235e6f246e 100644 --- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -29,8 +29,6 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_height" android:gravity="center"> <include layout="@layout/keyguard_message_area" /> diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index e6fb501d02e1..a5445c56112e 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -86,25 +86,25 @@ <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.systemui.recents.Recents</item> +<!-- <item>com.android.systemui.recents.Recents</item>--> <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.stackdivider.Divider</item> +<!-- <item>com.android.systemui.stackdivider.Divider</item>--> <!-- <item>com.android.systemui.statusbar.phone.StatusBar</item>--> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.pip.PipUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> +<!-- <item>com.android.systemui.keyboard.KeyboardUI</item>--> +<!-- <item>com.android.systemui.pip.PipUI</item>--> +<!-- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>--> <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> +<!-- <item>com.android.systemui.LatencyTester</item>--> +<!-- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>--> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.biometrics.AuthController</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> +<!-- <item>com.android.systemui.SliceBroadcastRelayHandler</item>--> <item>com.android.systemui.SizeCompatModeActivityController</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> +<!-- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>--> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.navigationbar.car.CarNavigationBar</item> <item>com.android.systemui.toast.ToastUI</item> diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 60ee19eec25f..4fde30987e50 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -205,6 +205,9 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void onCancelClicked() { + getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false); + getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false); + mBouncer.hide(/* destroyView= */ true); mKeyguardCancelClickedListener.onCancelClicked(); } @@ -226,7 +229,8 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void setNeedsInput(boolean needsInput) { - getLayout().setFocusable(needsInput); + getOverlayViewGlobalStateController().setWindowFocusable(needsInput); + getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java index 9fdfc0fff307..2c2aec21ea4f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java @@ -52,7 +52,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; @@ -80,7 +79,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks private final Handler mMainHandler; private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy; private final Lazy<NavigationBarController> mNavigationBarControllerLazy; - private final SuperStatusBarViewFactory mSuperStatusBarViewFactory; private final ButtonSelectionStateController mButtonSelectionStateController; private final PhoneStatusBarPolicy mIconPolicy; private final StatusBarIconController mIconController; @@ -127,7 +125,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks @Main Handler mainHandler, Lazy<KeyguardStateController> keyguardStateControllerLazy, Lazy<NavigationBarController> navigationBarControllerLazy, - SuperStatusBarViewFactory superStatusBarViewFactory, ButtonSelectionStateController buttonSelectionStateController, PhoneStatusBarPolicy iconPolicy, StatusBarIconController iconController @@ -143,7 +140,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks mMainHandler = mainHandler; mKeyguardStateControllerLazy = keyguardStateControllerLazy; mNavigationBarControllerLazy = navigationBarControllerLazy; - mSuperStatusBarViewFactory = superStatusBarViewFactory; mButtonSelectionStateController = buttonSelectionStateController; mIconPolicy = iconPolicy; mIconController = iconController; diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java index 402d742cb949..5fe03f17919b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java @@ -123,6 +123,12 @@ public class OverlayViewGlobalStateController { mSystemUIOverlayWindowController.setWindowFocusable(focusable); } + /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the + * sysui overlay window */ + public void setWindowNeedsInput(boolean needsInput) { + mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput); + } + /** Returns {@code true} if the window is focusable. */ public boolean isWindowFocusable() { return mSystemUIOverlayWindowController.isWindowFocusable(); diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java index 04d69eabaeac..5df5d6e98f18 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java @@ -132,6 +132,16 @@ public class SystemUIOverlayWindowController implements updateWindow(); } + /** Sets the window to enable IME. */ + public void setWindowNeedsInput(boolean needsInput) { + if (needsInput) { + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else { + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + updateWindow(); + } + /** Returns {@code true} if the window is visible */ public boolean isWindowVisible() { return mVisible; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java index 3ecb29f95092..6da34d4dddc0 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java @@ -38,7 +38,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -75,8 +74,6 @@ public class CarNavigationBarTest extends SysuiTestCase { @Mock private NavigationBarController mNavigationBarController; @Mock - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; - @Mock private ButtonSelectionStateController mButtonSelectionStateController; @Mock private PhoneStatusBarPolicy mIconPolicy; @@ -92,8 +89,7 @@ public class CarNavigationBarTest extends SysuiTestCase { mCarNavigationBarController, mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener, mHandler, () -> mKeyguardStateController, () -> mNavigationBarController, - mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy, - mIconController); + mButtonSelectionStateController, mIconPolicy, mIconController); } @Test diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index 653c8addba98..deccde593315 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -33,6 +33,7 @@ import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -395,8 +396,12 @@ public class EnableZenModeDialog { button1.setAlpha(button1.isEnabled() ? 1f : .5f); button2.setAlpha(button2.isEnabled() ? 1f : .5f); } else { - button1.setVisibility(View.GONE); - button2.setVisibility(View.GONE); + if (button1 != null) { + ((ViewGroup) row).removeView(button1); + } + if (button2 != null) { + ((ViewGroup) row).removeView(button2); + } } } diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml new file mode 100644 index 000000000000..a793680a037d --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml @@ -0,0 +1,21 @@ +<?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 + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <ripple android:color="#99999999" /> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml new file mode 100644 index 000000000000..129282dae77f --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml @@ -0,0 +1,99 @@ +<?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 + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/rounded_bg_full" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/bubble_manage_menu_dismiss_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_remove_no_shadow" + android:tint="@color/global_actions_text"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" + android:text="@string/bubble_dismiss_text" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_manage_menu_dont_bubble_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_stop_bubble" + android:tint="@color/global_actions_text"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" + android:text="@string/bubbles_dont_bubble_conversation" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_manage_menu_settings_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/bubble_manage_menu_settings_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:src="@drawable/ic_remove_no_shadow"/> + + <TextView + android:id="@+id/bubble_manage_menu_settings_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7cefa0c498d5..179f8b8ea9f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1200,6 +1200,7 @@ snap to the dismiss target. --> <dimen name="bubble_dismiss_target_padding_x">40dp</dimen> <dimen name="bubble_dismiss_target_padding_y">20dp</dimen> + <dimen name="bubble_manage_menu_elevation">4dp</dimen> <dimen name="dismiss_circle_size">52dp</dimen> <dimen name="dismiss_target_x_size">24dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cb20e7a15424..d639ed074240 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2629,6 +2629,8 @@ <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string> <!-- Button text for dismissing the bubble "manage" button tool tip [CHAR LIMIT=20]--> <string name="bubbles_user_education_got_it">Got it</string> + <!-- Label for the button that takes the user to the notification settings for the given app. --> + <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] --> <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string> diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 7262f8caac89..1f27ae238533 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -282,8 +284,8 @@ public class SystemActions extends SystemUI { private void handleTakeScreenshot() { ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext); - screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null); } private void handleAccessibilityMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 71f2bc09b983..38bfffbb75d9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -90,6 +90,7 @@ class Bubble implements BubbleViewProvider { } private FlyoutMessage mFlyoutMessage; + private Drawable mBadgedAppIcon; private Bitmap mBadgedImage; private int mDotColor; private Path mDotPath; @@ -133,6 +134,10 @@ class Bubble implements BubbleViewProvider { return mBadgedImage; } + public Drawable getBadgedAppIcon() { + return mBadgedAppIcon; + } + @Override public int getDotColor() { return mDotColor; @@ -239,6 +244,7 @@ class Bubble implements BubbleViewProvider { mAppName = info.appName; mFlyoutMessage = info.flyoutMessage; + mBadgedAppIcon = info.badgedAppIcon; mBadgedImage = info.badgedBubbleImage; mDotColor = info.dotColor; mDotPath = info.dotPath; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c6883c89bd21..e488cf271fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } + + mStackView.setUnbubbleConversationCallback(notificationEntry -> + onUserChangedBubble(notificationEntry, false /* shouldBubble */)); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 3524696dbc79..bb2365559f74 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -43,7 +43,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; -import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -55,14 +54,13 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; -import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.AlphaOptimizedButton; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and settings icon. */ -public class BubbleExpandedView extends LinearLayout implements View.OnClickListener { +public class BubbleExpandedView extends LinearLayout { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES; private enum ActivityViewStatus { @@ -100,9 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private int mPointerWidth; private int mPointerHeight; private ShapeDrawable mPointerDrawable; - private Rect mTempRect = new Rect(); - private int[] mTempLoc = new int[2]; - private int mExpandedViewTouchSlop; @Nullable private Bubble mBubble; @@ -224,7 +219,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); - mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop); } @Override @@ -239,7 +233,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); - mPointerDrawable = new ShapeDrawable(TriangleShape.create( mPointerWidth, mPointerHeight, true /* pointUp */)); mPointerView.setBackground(mPointerDrawable); @@ -248,7 +241,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mSettingsIconHeight = getContext().getResources().getDimensionPixelSize( R.dimen.bubble_manage_button_height); mSettingsIcon = findViewById(R.id.settings_button); - mSettingsIcon.setOnClickListener(this); mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, true /* singleTaskInstance */); @@ -289,6 +281,19 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return mBubble != null ? mBubble.getEntry() : null; } + void setManageClickListener(OnClickListener manageClickListener) { + findViewById(R.id.settings_button).setOnClickListener(manageClickListener); + } + + /** + * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which + * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful + * if a view has been added or removed from on top of the ActivityView, such as the manage menu. + */ + void updateObscuredTouchableRegion() { + mActivityView.onLocationChanged(); + } + void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] { @@ -473,51 +478,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } /** - * Whether the provided x, y values (in raw coordinates) are in a touchable area of the - * expanded view. - * - * The touchable areas are the ActivityView (plus some slop around it) and the manage button. - */ - boolean intersectingTouchableContent(int rawX, int rawY) { - mTempRect.setEmpty(); - if (mActivityView != null) { - mTempLoc = mActivityView.getLocationOnScreen(); - mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop, - mTempLoc[1] - mExpandedViewTouchSlop, - mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop, - mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop); - } - if (mTempRect.contains(rawX, rawY)) { - return true; - } - mTempLoc = mSettingsIcon.getLocationOnScreen(); - mTempRect.set(mTempLoc[0], - mTempLoc[1], - mTempLoc[0] + mSettingsIcon.getWidth(), - mTempLoc[1] + mSettingsIcon.getHeight()); - if (mTempRect.contains(rawX, rawY)) { - return true; - } - return false; - } - - @Override - public void onClick(View view) { - if (mBubble == null) { - return; - } - int id = view.getId(); - if (id == R.id.settings_button) { - Intent intent = mBubble.getSettingsIntent(); - mStackView.collapseStack(() -> { - mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser()); - logBubbleClickEvent(mBubble, - SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); - }); - } - } - - /** * Update appearance of the expanded view being displayed. */ public void updateView() { @@ -547,10 +507,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList * Position of the manage button displayed in the expanded view. Used for placing user * education about the manage button. */ - public Rect getManageButtonLocationOnScreen() { - mTempLoc = mSettingsIcon.getLocationOnScreen(); - return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(), - mTempLoc[1] + mSettingsIcon.getHeight()); + public void getManageButtonBoundsOnScreen(Rect rect) { + mSettingsIcon.getBoundsOnScreen(rect); } /** @@ -611,26 +569,4 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } return INVALID_DISPLAY; } - - /** - * Logs bubble UI click event. - * - * @param bubble the bubble notification entry that user is interacting with. - * @param action the user interaction enum. - */ - private void logBubbleClickEvent(Bubble bubble, int action) { - StatusBarNotification notification = bubble.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - mStackView.getBubbleIndex(mStackView.getExpandedBubble()), - mStackView.getBubbleCount(), - action, - mStackView.getNormalizedXPosition(), - mStackView.getNormalizedYPosition(), - bubble.showInShade(), - bubble.isOngoing(), - false /* isAppForeground (unused) */); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 0aabdff96e1e..c9069316028c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,12 +33,14 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Notification; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; @@ -47,6 +49,7 @@ import android.graphics.RectF; import android.graphics.Region; import android.os.Bundle; import android.os.Vibrator; +import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.Choreographer; import android.view.DisplayCutout; @@ -55,6 +58,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; @@ -62,6 +66,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.MainThread; @@ -83,6 +88,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -97,6 +103,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. @@ -224,7 +231,7 @@ public class BubbleStackView extends FrameLayout { private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; - private BubbleViewProvider mExpandedBubble; + @Nullable private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded; /** Whether the stack is currently on the left side of the screen, or animating there. */ @@ -244,6 +251,10 @@ public class BubbleStackView extends FrameLayout { } private BubbleController.BubbleExpandListener mExpandListener; + + /** Callback to run when we want to unbubble the given notification's conversation. */ + private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private SysUiState mSysUiState; private boolean mViewUpdatedRequested = false; @@ -255,9 +266,7 @@ public class BubbleStackView extends FrameLayout { private LayoutInflater mInflater; - // Used for determining view / touch intersection - int[] mTempLoc = new int[2]; - RectF mTempRect = new RectF(); + private Rect mTempRect = new Rect(); private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect()); @@ -471,6 +480,11 @@ public class BubbleStackView extends FrameLayout { return true; } + // If the manage menu is visible, just hide it. + if (mShowingManage) { + showManageMenu(false /* show */); + } + if (mBubbleData.isExpanded()) { maybeShowManageEducation(false /* show */); @@ -627,6 +641,13 @@ public class BubbleStackView extends FrameLayout { private BubbleManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; + private ViewGroup mManageMenu; + private ImageView mManageSettingsIcon; + private TextView mManageSettingsText; + private boolean mShowingManage = false; + private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, @@ -689,6 +710,8 @@ public class BubbleStackView extends FrameLayout { mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); + setUpManageMenu(); + setUpFlyout(); mFlyoutTransitionSpring.setSpring(new SpringForce() .setStiffness(SpringForce.STIFFNESS_LOW) @@ -838,7 +861,9 @@ public class BubbleStackView extends FrameLayout { // ActivityViews, etc.) were touched. Collapse the stack if it's expanded. setOnTouchListener((view, ev) -> { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (mBubbleData.isExpanded()) { + if (mShowingManage) { + showManageMenu(false /* show */); + } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); } } @@ -847,6 +872,66 @@ public class BubbleStackView extends FrameLayout { }); } + private void setUpManageMenu() { + if (mManageMenu != null) { + removeView(mManageMenu); + } + + mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate( + R.layout.bubble_manage_menu, this, false); + mManageMenu.setVisibility(View.INVISIBLE); + + PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig); + + final TypedArray ta = mContext.obtainStyledAttributes( + new int[] {android.R.attr.dialogCornerRadius}); + final int menuCornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + + mManageMenu.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius); + } + }); + mManageMenu.setClipToOutline(true); + + mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + dismissBubbleIfExists(mBubbleData.getSelectedBubble()); + }); + + mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + final Bubble bubble = mBubbleData.getSelectedBubble(); + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + mUnbubbleConversationCallback.accept(bubble.getEntry()); + } + }); + + mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + final Bubble bubble = mBubbleData.getSelectedBubble(); + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + final Intent intent = bubble.getSettingsIntent(); + collapseStack(() -> { + mContext.startActivityAsUser( + intent, bubble.getEntry().getSbn().getUser()); + logBubbleClickEvent( + bubble, + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); + }); + } + }); + + mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon); + mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name); + addView(mManageMenu); + } + private void setUpUserEducation() { if (mUserEducationView != null) { removeView(mUserEducationView); @@ -934,6 +1019,7 @@ public class BubbleStackView extends FrameLayout { setUpFlyout(); setUpOverflow(); setUpUserEducation(); + setUpManageMenu(); } /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */ @@ -960,6 +1046,9 @@ public class BubbleStackView extends FrameLayout { Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation)); addOnLayoutChangeListener(mOrientationChangedListener); hideFlyoutImmediate(); + + mManageMenu.setVisibility(View.INVISIBLE); + mShowingManage = false; } @Override @@ -1100,6 +1189,12 @@ public class BubbleStackView extends FrameLayout { mExpandListener = listener; } + /** Sets the function to call to un-bubble the given conversation. */ + public void setUnbubbleConversationCallback( + Consumer<NotificationEntry> unbubbleConversationCallback) { + mUnbubbleConversationCallback = unbubbleConversationCallback; + } + /** * Whether the stack of bubbles is expanded or not. */ @@ -1361,15 +1456,14 @@ public class BubbleStackView extends FrameLayout { mManageEducationView.setAlpha(0); mManageEducationView.setVisibility(VISIBLE); mManageEducationView.post(() -> { - final Rect position = - mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen(); + mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); final int viewHeight = mManageEducationView.getManageViewHeight(); final int inset = getResources().getDimensionPixelSize( R.dimen.bubbles_manage_education_top_inset); mManageEducationView.bringToFront(); - mManageEducationView.setManageViewPosition(position.left, - position.top - viewHeight + inset); - mManageEducationView.setPointerPosition(position.centerX() - position.left); + mManageEducationView.setManageViewPosition(mTempRect.left, + mTempRect.top - viewHeight + inset); + mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left); mManageEducationView.animate() .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION) .setInterpolator(FAST_OUT_SLOW_IN).alpha(1); @@ -1443,6 +1537,9 @@ public class BubbleStackView extends FrameLayout { } private void animateCollapse() { + // Hide the menu if it's visible. + showManageMenu(false); + mIsExpanded = false; final BubbleViewProvider previouslySelected = mExpandedBubble; beforeExpandedViewAnimation(); @@ -1570,9 +1667,9 @@ public class BubbleStackView extends FrameLayout { */ @Override public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { - // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch - // events from any location. - if (mNotificationShadeWindowController.getPanelExpanded()) { + // If the notification shade is expanded, or the manage menu is open, we shouldn't let the + // ActivityView steal any touch events from any location. + if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) { touchableRegion.setEmpty(); } } @@ -1658,17 +1755,20 @@ public class BubbleStackView extends FrameLayout { private void dismissMagnetizedObject() { if (mIsExpanded) { final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject(); - final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView); + dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView)); - if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) { - mBubbleData.notificationEntryRemoved( - draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); - } } else { mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); } } + private void dismissBubbleIfExists(@Nullable Bubble bubble) { + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + mBubbleData.notificationEntryRemoved( + bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); + } + } + /** Prepares and starts the desaturate/darken animation on the bubble stack. */ private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) { mDesaturateAndDarkenTargetView = targetView; @@ -1912,6 +2012,63 @@ public class BubbleStackView extends FrameLayout { invalidate(); } + private void showManageMenu(boolean show) { + mShowingManage = show; + + // This should not happen, since the manage menu is only visible when there's an expanded + // bubble. If we end up in this state, just hide the menu immediately. + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + mManageMenu.setVisibility(View.INVISIBLE); + return; + } + + // If available, update the manage menu's settings option with the expanded bubble's app + // name and icon. + if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) { + final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey()); + mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon()); + mManageSettingsText.setText(getResources().getString( + R.string.bubbles_app_settings, bubble.getAppName())); + } + + mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); + + // When the menu is open, it should be at these coordinates. This will make the menu's + // bottom left corner match up with the button's bottom left corner. + final float targetX = mTempRect.left; + final float targetY = mTempRect.bottom - mManageMenu.getHeight(); + + if (show) { + mManageMenu.setScaleX(0.5f); + mManageMenu.setScaleY(0.5f); + mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4); + mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4); + mManageMenu.setAlpha(0f); + + PhysicsAnimator.getInstance(mManageMenu) + .spring(DynamicAnimation.ALPHA, 1f) + .spring(DynamicAnimation.SCALE_X, 1f) + .spring(DynamicAnimation.SCALE_Y, 1f) + .spring(DynamicAnimation.TRANSLATION_X, targetX) + .spring(DynamicAnimation.TRANSLATION_Y, targetY) + .start(); + + mManageMenu.setVisibility(View.VISIBLE); + } else { + PhysicsAnimator.getInstance(mManageMenu) + .spring(DynamicAnimation.ALPHA, 0f) + .spring(DynamicAnimation.SCALE_X, 0.5f) + .spring(DynamicAnimation.SCALE_Y, 0.5f) + .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4) + .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4) + .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE)) + .start(); + } + + // Update the AV's obscured touchable region for the new menu visibility state. + mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); + } + private void updateExpandedBubble() { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "updateExpandedBubble()"); @@ -1921,6 +2078,7 @@ public class BubbleStackView extends FrameLayout { && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); mExpandedViewContainer.addView(bev); + bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); bev.populateExpandedView(); mExpandedViewContainer.setVisibility(VISIBLE); mExpandedViewContainer.setAlpha(1.0f); @@ -2089,4 +2247,26 @@ public class BubbleStackView extends FrameLayout { } return bubbles; } + + /** + * Logs bubble UI click event. + * + * @param bubble the bubble notification entry that user is interacting with. + * @param action the user interaction enum. + */ + private void logBubbleClickEvent(Bubble bubble, int action) { + StatusBarNotification notification = bubble.getEntry().getSbn(); + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + notification.getPackageName(), + notification.getNotification().getChannelId(), + notification.getId(), + getBubbleIndex(getExpandedBubble()), + getBubbleCount(), + action, + getNormalizedXPosition(), + getNormalizedYPosition(), + bubble.showInShade(), + bubble.isOngoing(), + false /* isAppForeground (unused) */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index c96f9a470ca4..8a57a735f6cb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -116,6 +116,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask ShortcutInfo shortcutInfo; String appName; Bitmap badgedBubbleImage; + Drawable badgedAppIcon; int dotColor; Path dotPath; Bubble.FlyoutMessage flyoutMessage; @@ -176,6 +177,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon); + info.badgedAppIcon = badgedIcon; info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable, badgeBitmapInfo).icon; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java index ef84c73b3145..ca3e2e27fa9a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java @@ -20,15 +20,17 @@ import android.graphics.Bitmap; import android.graphics.Path; import android.view.View; +import androidx.annotation.Nullable; + /** * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow. */ interface BubbleViewProvider { - BubbleExpandedView getExpandedView(); + @Nullable BubbleExpandedView getExpandedView(); void setContentVisibility(boolean visible); - View getIconView(); + @Nullable View getIconView(); void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index); diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index b88350f52678..b211ccc28803 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -16,6 +16,8 @@ package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; @@ -480,7 +482,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, */ @VisibleForTesting protected int getMaxShownPowerItems() { - if (shouldUseControlsLayout()) { + // TODO: Overflow disabled on keyguard while we solve for touch blocking issues. + if (shouldUseControlsLayout() && !mKeyguardShowing) { return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns); } else { return Integer.MAX_VALUE; @@ -830,7 +833,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mHandler.postDelayed(new Runnable() { @Override public void run() { - mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); + mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 66bc177da81d..fecb7b602012 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; @@ -380,7 +381,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, Insets visibleInsets, int taskId) { mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets, - taskId, mHandler, null); + taskId, SCREENSHOT_OVERVIEW, mHandler, null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index e1cc0b0c90c2..1efe663ca6ce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -76,6 +76,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ static class SaveImageInBackgroundData { public Bitmap image; - public Uri imageUri; public Consumer<Uri> finisher; public GlobalScreenshot.ActionsReadyListener mActionsReadyListener; public int errorMsgResId; @@ -112,13 +112,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset void clearImage() { image = null; - imageUri = null; + } + } + + /** + * Structure returned by the SaveImageInBackgroundTask + */ + static class SavedImageData { + public Uri uri; + public Notification.Action shareAction; + public Notification.Action editAction; + public Notification.Action deleteAction; + public List<Notification.Action> smartActions; + + /** + * Used to reset the return data on error + */ + public void reset() { + uri = null; + shareAction = null; + editAction = null; + deleteAction = null; + smartActions = null; } } abstract static class ActionsReadyListener { - abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions, - List<Notification.Action> actions); + abstract void onActionsReady(SavedImageData imageData); } // These strings are used for communicating the action invoked to @@ -147,6 +167,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; + private final UiEventLogger mUiEventLogger; private final Context mContext; private final WindowManager mWindowManager; @@ -185,6 +206,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); GlobalScreenshot.this.clearScreenshot("timeout"); break; default: @@ -199,9 +221,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Inject public GlobalScreenshot( Context context, @Main Resources resources, LayoutInflater layoutInflater, - ScreenshotNotificationsController screenshotNotificationsController) { + ScreenshotNotificationsController screenshotNotificationsController, + UiEventLogger uiEventLogger) { mContext = context; mNotificationsController = screenshotNotificationsController; + mUiEventLogger = uiEventLogger; // Inflate the screenshot layout mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); @@ -222,7 +246,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); - mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); + mDismissButton.setOnClickListener(view -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); + clearScreenshot("dismiss_button"); + }); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); @@ -445,12 +472,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> smartActions, - List<Notification.Action> actions) { - if (uri == null) { + void onActionsReady(SavedImageData imageData) { + finisher.accept(imageData.uri); + if (imageData.uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.addListener( @@ -458,13 +487,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - createScreenshotActionsShadeAnimation( - smartActions, actions).start(); + createScreenshotActionsShadeAnimation(imageData) + .start(); } }); } else { - createScreenshotActionsShadeAnimation(smartActions, - actions).start(); + createScreenshotActionsShadeAnimation(imageData).start(); } }); } @@ -567,8 +595,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return dropInAnimation; } - private ValueAnimator createScreenshotActionsShadeAnimation( - List<Notification.Action> smartActions, List<Notification.Action> actions) { + private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) { LayoutInflater inflater = LayoutInflater.from(mContext); mActionsView.removeAllViews(); mActionsContainer.setScrollX(0); @@ -583,45 +610,63 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } catch (RemoteException e) { } - for (Notification.Action smartAction : smartActions) { + for (Notification.Action smartAction : imageData.smartActions) { ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); actionChip.setText(smartAction.title); actionChip.setIcon(smartAction.getIcon(), false); actionChip.setPendingIntent(smartAction.actionIntent, - () -> clearScreenshot("chip tapped")); + () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); + clearScreenshot("chip tapped"); + }); mActionsView.addView(actionChip); } - for (Notification.Action action : actions) { - ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( - R.layout.global_screenshot_action_chip, mActionsView, false); - actionChip.setText(action.title); - actionChip.setIcon(action.getIcon(), true); - actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped")); - if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) { - mScreenshotView.setOnClickListener(v -> { - try { - action.actionIntent.send(); - clearScreenshot("screenshot preview tapped"); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Intent cancelled", e); - } - }); - mScreenshotView.setContentDescription(action.title); + ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + shareChip.setText(imageData.shareAction.title); + shareChip.setIcon(imageData.shareAction.getIcon(), true); + shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(shareChip); + + ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + editChip.setText(imageData.editAction.title); + editChip.setIcon(imageData.editAction.getIcon(), true); + editChip.setPendingIntent(imageData.editAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(editChip); + + mScreenshotView.setOnClickListener(v -> { + try { + imageData.editAction.actionIntent.send(); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); + clearScreenshot("screenshot preview tapped"); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Intent cancelled", e); } - mActionsView.addView(actionChip); - } + }); + mScreenshotView.setContentDescription(imageData.editAction.title); + if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); Toast scrollNotImplemented = Toast.makeText( mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate + scrollChip.setText("Extend"); // TODO : add resource and translate scrollChip.setIcon( Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true); - scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); + scrollChip.setOnClickListener(v -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED); + scrollNotImplemented.show(); + }); mActionsView.addView(scrollChip); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index f3614ffbdb1b..095c32f4a2ce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -24,7 +24,6 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; -import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -53,7 +52,6 @@ import android.widget.Toast; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; -import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> smartActions, - List<Notification.Action> actions) { - if (uri == null) { + void onActionsReady(GlobalScreenshot.SavedImageData actionData) { + if (actionData.uri == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { mNotificationsController - .showScreenshotActionsNotification(uri, smartActions, actions); + .showScreenshotActionsNotification(actionData); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index c828c4cccce5..170174deaeb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final Context mContext; private final GlobalScreenshot.SaveImageInBackgroundData mParams; + private final GlobalScreenshot.SavedImageData mImageData; private final String mImageFileName; private final long mImageTime; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; @@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { mContext = context; + mImageData = new GlobalScreenshot.SavedImageData(); // Prepare all the output metadata mParams = data; @@ -145,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { values.put(MediaColumns.IS_PENDING, 1); final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + try { // First, write the actual data for our screenshot try (OutputStream out = resolver.openOutputStream(uri)) { @@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { throw e; } - List<Notification.Action> actions = - populateNotificationActions(mContext, r, uri); List<Notification.Action> smartActions = new ArrayList<>(); if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt( @@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mSmartActionsProvider), mContext)); } - mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions); - mParams.imageUri = uri; + + mImageData.uri = uri; + mImageData.smartActions = smartActions; + mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri); + mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri); + mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); + + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.image = null; mParams.errorMsgResId = 0; } catch (Exception e) { @@ -216,29 +223,26 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); } return null; } @Override - protected void onPostExecute(Void params) { - mParams.finisher.accept(mParams.imageUri); - } - - @Override protected void onCancelled(Void params) { // If we are cancelled while the task is running in the background, we may get null // params. The finisher is expected to always be called back, so just use the baked-in // params from the ctor in any case. - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.finisher.accept(null); mParams.clearImage(); } @VisibleForTesting - List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) { + Notification.Action createShareAction(Context context, Resources r, Uri uri) { // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows @@ -263,8 +267,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); - ArrayList<Notification.Action> actions = new ArrayList<>(); - PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); @@ -288,7 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); - actions.add(shareActionBuilder.build()); + + return shareActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createEditAction(Context context, Resources r, Uri uri) { + // Note: Both the share and edit actions are proxied through ActionProxyReceiver in + // order to do some common work like dismissing the keyguard and sending + // closeSystemWindows // Create an edit intent, if a specific package is provided as the editor, then // launch that directly @@ -302,6 +312,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + // Create a edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) @@ -317,24 +331,30 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); - actions.add(editActionBuilder.build()); - - if (mCreateDeleteAction) { - // Create a delete action for the notification - PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) - .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) - .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) - .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, - mSmartActionsEnabled) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_delete), - r.getString(com.android.internal.R.string.delete), deleteAction); - actions.add(deleteActionBuilder.build()); - } - return actions; + + return editActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createDeleteAction(Context context, Resources r, Uri uri) { + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + + // Create a delete action for the notification + PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, + new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) + .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) + .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) + .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, + mSmartActionsEnabled) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( + Icon.createWithResource(r, R.drawable.ic_screenshot_delete), + r.getString(com.android.internal.R.string.delete), deleteAction); + + return deleteActionBuilder.build(); } private int getUserHandleOfForegroundApplication(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java new file mode 100644 index 000000000000..20fa991dcc1f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -0,0 +1,89 @@ +/* + * 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.screenshot; + +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "screenshot requested from global actions") + SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302), + @UiEvent(doc = "screenshot requested from key chord") + SCREENSHOT_REQUESTED_KEY_CHORD(303), + @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)") + SCREENSHOT_REQUESTED_KEY_OTHER(384), + @UiEvent(doc = "screenshot requested from overview") + SCREENSHOT_REQUESTED_OVERVIEW(304), + @UiEvent(doc = "screenshot requested from accessibility actions") + SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382), + @UiEvent(doc = "screenshot requested (other)") + SCREENSHOT_REQUESTED_OTHER(305), + @UiEvent(doc = "screenshot was saved") + SCREENSHOT_SAVED(306), + @UiEvent(doc = "screenshot failed to save") + SCREENSHOT_NOT_SAVED(336), + @UiEvent(doc = "screenshot preview tapped") + SCREENSHOT_PREVIEW_TAPPED(307), + @UiEvent(doc = "screenshot edit button tapped") + SCREENSHOT_EDIT_TAPPED(308), + @UiEvent(doc = "screenshot share button tapped") + SCREENSHOT_SHARE_TAPPED(309), + @UiEvent(doc = "screenshot smart action chip tapped") + SCREENSHOT_SMART_ACTION_TAPPED(374), + @UiEvent(doc = "screenshot scroll tapped") + SCREENSHOT_SCROLL_TAPPED(373), + @UiEvent(doc = "screenshot interaction timed out") + SCREENSHOT_INTERACTION_TIMEOUT(310), + @UiEvent(doc = "screenshot explicitly dismissed") + SCREENSHOT_EXPLICIT_DISMISSAL(311); + + private final int mId; + + ScreenshotEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + + static ScreenshotEvent getScreenshotSource(int source) { + switch (source) { + case SCREENSHOT_GLOBAL_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS; + case SCREENSHOT_KEY_CHORD: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD; + case SCREENSHOT_KEY_OTHER: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER; + case SCREENSHOT_OVERVIEW: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW; + case SCREENSHOT_ACCESSIBILITY_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS; + case SCREENSHOT_OTHER: + default: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 811a8d936b77..fbcd6ba0ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -32,7 +32,6 @@ import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; -import android.net.Uri; import android.os.UserHandle; import android.util.DisplayMetrics; import android.view.WindowManager; @@ -42,8 +41,6 @@ import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.util.NotificationChannels; -import java.util.List; - import javax.inject.Inject; /** @@ -185,23 +182,20 @@ public class ScreenshotNotificationsController { /** * Shows a notification with the saved screenshot and actions that can be taken with it. * - * @param imageUri URI for the saved image - * @param actions a list of notification actions which can be taken + * @param actionData SavedImageData struct with image URI and actions */ public void showScreenshotActionsNotification( - Uri imageUri, - List<Notification.Action> smartActions, - List<Notification.Action> actions) { - for (Notification.Action action : actions) { - mNotificationBuilder.addAction(action); - } - for (Notification.Action smartAction : smartActions) { + GlobalScreenshot.SavedImageData actionData) { + mNotificationBuilder.addAction(actionData.shareAction); + mNotificationBuilder.addAction(actionData.editAction); + mNotificationBuilder.addAction(actionData.deleteAction); + for (Notification.Action smartAction : actionData.smartActions) { mNotificationBuilder.addAction(smartAction); } // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); - launchIntent.setDataAndType(imageUri, "image/png"); + launchIntent.setDataAndType(actionData.uri, "image/png"); launchIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 8b8b6f8071e1..a6e083b04ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -32,6 +32,9 @@ import android.os.UserManager; import android.util.Log; import android.view.WindowManager; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.ScreenshotHelper; + import java.util.function.Consumer; import javax.inject.Inject; @@ -42,6 +45,7 @@ public class TakeScreenshotService extends Service { private final GlobalScreenshot mScreenshot; private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; + private final UiEventLogger mUiEventLogger; private Handler mHandler = new Handler(Looper.myLooper()) { @Override @@ -64,14 +68,22 @@ public class TakeScreenshotService extends Service { return; } - // TODO (mkephart): clean up once notifications flow is fully deprecated + // TODO: clean up once notifications flow is fully deprecated boolean useCornerFlow = true; + + ScreenshotHelper.ScreenshotRequest screenshotRequest = + (ScreenshotHelper.ScreenshotRequest) msg.obj; + + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource())); + switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: if (useCornerFlow) { mScreenshot.takeScreenshot(finisher); } else { - mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + mScreenshotLegacy.takeScreenshot( + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: @@ -79,17 +91,15 @@ public class TakeScreenshotService extends Service { mScreenshot.takeScreenshotPartial(finisher); } else { mScreenshotLegacy.takeScreenshotPartial( - finisher, msg.arg1 > 0, msg.arg2 > 0); + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: - Bitmap screenshot = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP); - Rect screenBounds = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS); - Insets insets = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_INSETS); - int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID); + Bitmap screenshot = screenshotRequest.getBitmap(); + Rect screenBounds = screenshotRequest.getBoundsInScreen(); + Insets insets = screenshotRequest.getInsets(); + int taskId = screenshotRequest.getTaskId(); if (useCornerFlow) { mScreenshot.handleImageAsScreenshot( screenshot, screenBounds, insets, taskId, finisher); @@ -106,10 +116,12 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(GlobalScreenshot globalScreenshot, - GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) { + GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager, + UiEventLogger uiEventLogger) { mScreenshot = globalScreenshot; mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; + mUiEventLogger = uiEventLogger; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 1b7524521d76..8c24c540e743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.NavigationBarFragment; import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.policy.BatteryController; import javax.inject.Inject; @@ -139,7 +140,8 @@ public class NavigationBarController implements Callbacks { ? Dependency.get(LightBarController.class) : new LightBarController(context, Dependency.get(DarkIconDispatcher.class), - Dependency.get(BatteryController.class)); + Dependency.get(BatteryController.class), + Dependency.get(NavigationModeController.class)); navBar.setLightBarController(lightBarController); // TODO(b/118592525): to support multi-display, we start to add something which is diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 7d422e3c15a2..f9119c7a010f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -53,11 +53,13 @@ import android.view.WindowManagerGlobal; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -74,7 +76,7 @@ import java.util.concurrent.Executor; /** * Utility class to handle edge swipes for back gesture */ -public class EdgeBackGestureHandler implements DisplayListener, +public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener, PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { private static final String TAG = "EdgeBackGestureHandler"; @@ -165,6 +167,7 @@ public class EdgeBackGestureHandler implements DisplayListener, private boolean mIsGesturalModeEnabled; private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; + private boolean mIsBackGestureAllowed; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -200,7 +203,7 @@ public class EdgeBackGestureHandler implements DisplayListener, public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager) { - final Resources res = context.getResources(); + super(Dependency.get(BroadcastDispatcher.class)); mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); @@ -216,20 +219,30 @@ public class EdgeBackGestureHandler implements DisplayListener, ViewConfiguration.getLongPressTimeout()); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( - mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res)); + mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources); - updateCurrentUserResources(res); + updateCurrentUserResources(); sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); } - public void updateCurrentUserResources(Resources res) { + public void updateCurrentUserResources() { + Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext() + .getResources(); mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); + mIsBackGestureAllowed = + !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); mBottomGestureHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_gesture_height); } + @Override + public void onUserSwitched(int newUserId) { + updateIsEnabled(); + updateCurrentUserResources(); + } + /** * @see NavigationBarView#onAttachedToWindow() */ @@ -243,6 +256,7 @@ public class EdgeBackGestureHandler implements DisplayListener, Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); updateIsEnabled(); + startTracking(); } /** @@ -255,6 +269,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver); updateIsEnabled(); + stopTracking(); } private void setRotationCallbacks(boolean enable) { @@ -269,10 +284,13 @@ public class EdgeBackGestureHandler implements DisplayListener, } } - public void onNavigationModeChanged(int mode, Context currentUserContext) { + /** + * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged + */ + public void onNavigationModeChanged(int mode) { mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); updateIsEnabled(); - updateCurrentUserResources(currentUserContext.getResources()); + updateCurrentUserResources(); } public void onNavBarTransientStateChanged(boolean isTransient) { @@ -363,6 +381,10 @@ public class EdgeBackGestureHandler implements DisplayListener, updateDisplaySize(); } + public boolean isHandlingGestures() { + return mIsEnabled && mIsBackGestureAllowed; + } + private WindowManager.LayoutParams createLayoutParams() { Resources resources = mContext.getResources(); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( @@ -469,9 +491,9 @@ public class EdgeBackGestureHandler implements DisplayListener, mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; mLogGesture = false; mInRejectedExclusion = false; - mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags) - && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()) - && !mDisabledForQuickstep; + mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) + && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); mEdgeBackPlugin.onMotionEvent(ev); @@ -599,6 +621,7 @@ public class EdgeBackGestureHandler implements DisplayListener, public void dump(PrintWriter pw) { pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); + pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); pw.println(" mAllowGesture=" + mAllowGesture); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index d35e1e1e176a..3e5eb5fba8f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -33,6 +33,7 @@ import com.android.internal.view.AppearanceRegion; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; @@ -58,6 +59,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; private int mStatusBarMode; private int mNavigationBarMode; + private int mNavigationMode; private final Color mDarkModeColor; /** @@ -84,11 +86,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC @Inject public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher, - BatteryController batteryController) { + BatteryController batteryController, NavigationModeController navModeController) { mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone)); mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; mBatteryController = batteryController; mBatteryController.addCallback(this); + mNavigationMode = navModeController.addListener((mode) -> { + mNavigationMode = mode; + }); } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -234,7 +239,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } private void updateNavigation() { - if (mNavigationBarController != null) { + if (mNavigationBarController != null + && !QuickStepContract.isGesturalMode(mNavigationMode)) { mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 84aecd4e0759..2978772cac5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -123,7 +123,7 @@ public class NavigationBarView extends FrameLayout implements private KeyButtonDrawable mRecentIcon; private KeyButtonDrawable mDockedIcon; - private final EdgeBackGestureHandler mEdgeBackGestureHandler; + private EdgeBackGestureHandler mEdgeBackGestureHandler; private final DeadZone mDeadZone; private boolean mDeadZoneConsuming = false; private final NavigationBarTransitions mBarTransitions; @@ -244,7 +244,7 @@ public class NavigationBarView extends FrameLayout implements private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> { // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully // gestural mode, the entire nav bar should be touchable. - if (!isGesturalMode(mNavBarMode) || mImeVisible) { + if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) { info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } @@ -296,8 +296,6 @@ public class NavigationBarView extends FrameLayout implements R.style.RotateButtonCCWStart90, isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton); - final ContextualButton backButton = new ContextualButton(R.id.back, 0); - mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); mConfiguration.updateFrom(context.getResources().getConfiguration()); @@ -305,7 +303,7 @@ public class NavigationBarView extends FrameLayout implements mScreenPinningNotify = new ScreenPinningNotify(mContext); mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class)); - mButtonDispatchers.put(R.id.back, backButton); + mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle)); mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); @@ -659,7 +657,7 @@ public class NavigationBarView extends FrameLayout implements boolean disableHomeHandle = disableRecent && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); - boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode) + boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures() || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)); // When screen pinning, don't hide back and home when connected service or back and @@ -686,9 +684,9 @@ public class NavigationBarView extends FrameLayout implements } } - getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); - getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); - getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); + getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); + getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); + getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE); } @@ -838,10 +836,9 @@ public class NavigationBarView extends FrameLayout implements @Override public void onNavigationModeChanged(int mode) { - Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext(); mNavBarMode = mode; mBarTransitions.onNavigationModeChanged(mNavBarMode); - mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx); + mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode); mRecentsOnboarding.onNavigationModeChanged(mNavBarMode); getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode); @@ -864,6 +861,7 @@ public class NavigationBarView extends FrameLayout implements @Override public void onFinishInflate() { + super.onFinishInflate(); mNavigationInflaterView = findViewById(R.id.navigation_inflater); mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java index d24ccf343a3a..6061b1e73d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java @@ -17,9 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.content.Intent.ACTION_OVERLAY_CHANGED; -import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED; -import static android.os.UserHandle.USER_CURRENT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; @@ -38,17 +35,14 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.util.Log; -import android.util.SparseBooleanArray; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -70,104 +64,34 @@ public class NavigationModeController implements Dumpable { private final Context mContext; private Context mCurrentUserContext; private final IOverlayManager mOverlayManager; - private final DeviceProvisionedController mDeviceProvisionedController; private final Executor mUiBgExecutor; - private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray(); - private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case ACTION_OVERLAY_CHANGED: if (DEBUG) { Log.d(TAG, "ACTION_OVERLAY_CHANGED"); } updateCurrentInteractionMode(true /* notify */); - break; - } } }; - private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback = - new DeviceProvisionedController.DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - if (DEBUG) { - Log.d(TAG, "onDeviceProvisionedChanged: " - + mDeviceProvisionedController.isDeviceProvisioned()); - } - // Once the device has been provisioned, check if we can restore gestural nav - restoreGesturalNavOverlayIfNecessary(); - } - - @Override - public void onUserSetupChanged() { - if (DEBUG) { - Log.d(TAG, "onUserSetupChanged: " - + mDeviceProvisionedController.isCurrentUserSetup()); - } - // Once the user has been setup, check if we can restore gestural nav - restoreGesturalNavOverlayIfNecessary(); - } - - @Override - public void onUserSwitched() { - if (DEBUG) { - Log.d(TAG, "onUserSwitched: " - + ActivityManagerWrapper.getInstance().getCurrentUserId()); - } - - // Update the nav mode for the current user - updateCurrentInteractionMode(true /* notify */); - - // When switching users, defer enabling the gestural nav overlay until the user - // is all set up - deferGesturalNavOverlayIfNecessary(); - } - }; - @Inject - public NavigationModeController(Context context, - DeviceProvisionedController deviceProvisionedController, - @UiBackground Executor uiBgExecutor) { + public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) { mContext = context; mCurrentUserContext = context; mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); mUiBgExecutor = uiBgExecutor; - mDeviceProvisionedController = deviceProvisionedController; - mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); overlayFilter.addDataScheme("package"); overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); - IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED); - mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null, - null); - updateCurrentInteractionMode(false /* notify */); - - // Check if we need to defer enabling gestural nav - deferGesturalNavOverlayIfNecessary(); - } - - private boolean setGestureModeOverlayForMainLauncher() { - if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) { - // Already in gesture mode - return true; - } - - Log.d(TAG, "Switching system navigation to full-gesture mode:" - + " contextUser=" - + mCurrentUserContext.getUserId()); - - setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT); - return true; } public void updateCurrentInteractionMode(boolean notify) { @@ -176,10 +100,9 @@ public class NavigationModeController implements Dumpable { if (mode == NAV_BAR_MODE_GESTURAL) { switchToDefaultGestureNavOverlayIfNecessary(); } - mUiBgExecutor.execute(() -> { + mUiBgExecutor.execute(() -> Settings.Secure.putString(mCurrentUserContext.getContentResolver(), - Secure.NAVIGATION_MODE, String.valueOf(mode)); - }); + Secure.NAVIGATION_MODE, String.valueOf(mode))); if (DEBUG) { Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode); dumpAssetPaths(mCurrentUserContext); @@ -230,61 +153,11 @@ public class NavigationModeController implements Dumpable { } } - private void deferGesturalNavOverlayIfNecessary() { - final int userId = mDeviceProvisionedController.getCurrentUser(); - mRestoreGesturalNavBarMode.put(userId, false); - if (mDeviceProvisionedController.isDeviceProvisioned() - && mDeviceProvisionedController.isCurrentUserSetup()) { - // User is already setup and device is provisioned, nothing to do - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is " - + "setup"); - } - return; - } - - ArrayList<String> defaultOverlays = new ArrayList<>(); - try { - defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages())); - } catch (RemoteException e) { - Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays"); - } - if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) { - // No default gesture nav overlay - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, " - + "default=" + defaultOverlays); - } - return; - } - - // If the default is gestural, force-enable three button mode until the device is - // provisioned - setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT); - mRestoreGesturalNavBarMode.put(userId, true); - - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode"); - } - } - - private void restoreGesturalNavOverlayIfNecessary() { - if (DEBUG) { - Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore=" - + mRestoreGesturalNavBarMode); - } - final int userId = mDeviceProvisionedController.getCurrentUser(); - if (mRestoreGesturalNavBarMode.get(userId)) { - // Restore the gestural state if necessary - setGestureModeOverlayForMainLauncher(); - mRestoreGesturalNavBarMode.put(userId, false); - } - } - private void switchToDefaultGestureNavOverlayIfNecessary() { final int userId = mCurrentUserContext.getUserId(); try { - final IOverlayManager om = mOverlayManager; + final IOverlayManager om = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); if (info != null && !info.isEnabled()) { // Enable the default gesture nav overlay, and move the back gesture inset scale to @@ -309,20 +182,6 @@ public class NavigationModeController implements Dumpable { } } - public void setModeOverlay(String overlayPkg, int userId) { - mUiBgExecutor.execute(() -> { - try { - mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId); - if (DEBUG) { - Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg - + " userId=" + userId); - } - } catch (SecurityException | IllegalStateException | RemoteException e) { - Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId); - } - }); - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NavigationModeController:"); @@ -334,11 +193,6 @@ public class NavigationModeController implements Dumpable { defaultOverlays = "failed_to_fetch"; } pw.println(" defaultOverlays=" + defaultOverlays); - pw.println(" restoreGesturalNavMode:"); - for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) { - pw.println(" userId=" + mRestoreGesturalNavBarMode.keyAt(i) - + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i)); - } dumpAssetPaths(mCurrentUserContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java index 1a6b415f87db..bf52a7ae2bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java @@ -148,11 +148,6 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, updateSamplingRect(); } - private void postUpdateSamplingListener() { - mHandler.removeCallbacks(mUpdateSamplingListener); - mHandler.post(mUpdateSamplingListener); - } - private void updateSamplingListener() { boolean isSamplingEnabled = mSamplingEnabled && !mSamplingRequestBounds.isEmpty() diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt index d65b285adb0c..8880df9959c1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt @@ -115,7 +115,9 @@ abstract class RelativeTouchListener : View.OnTouchListener { performedLongClick = false handler.postDelayed({ - performedLongClick = v.performLongClick() + if (v.isLongClickable) { + performedLongClick = v.performLongClick() + } }, ViewConfiguration.getLongPressTimeout().toLong()) } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index 8625d63a3c7e..db08d64acc10 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -61,14 +61,17 @@ internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>() /** * Default spring configuration to use for animations where stiffness and/or damping ratio - * were not provided. + * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. */ -private val defaultSpring = PhysicsAnimator.SpringConfig( +private val globalDefaultSpring = PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) -/** Default fling configuration to use for animations where friction was not provided. */ -private val defaultFling = PhysicsAnimator.FlingConfig( +/** + * Default fling configuration to use for animations where friction was not provided, and a default + * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig]. + */ +private val globalDefaultFling = PhysicsAnimator.FlingConfig( friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE) /** Whether to log helpful debug information about animations. */ @@ -111,6 +114,12 @@ class PhysicsAnimator<T> private constructor (val target: T) { /** End actions to run when all animations have completed. */ private val endActions = ArrayList<EndAction>() + /** SpringConfig to use by default for properties whose springs were not provided. */ + private var defaultSpring: SpringConfig = globalDefaultSpring + + /** FlingConfig to use by default for properties whose fling configs were not provided. */ + private var defaultFling: FlingConfig = globalDefaultFling + /** * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add @@ -204,6 +213,19 @@ class PhysicsAnimator<T> private constructor (val target: T) { } /** + * Springs a property to a given value using the provided configuration options, and a start + * velocity of 0f. + * + * @see spring + */ + fun spring( + property: FloatPropertyCompat<in T>, + toPosition: Float + ): PhysicsAnimator<T> { + return spring(property, toPosition, 0f) + } + + /** * Flings a property using the given start velocity, using a [FlingAnimation] configured using * the provided configuration settings. * @@ -392,6 +414,14 @@ class PhysicsAnimator<T> private constructor (val target: T) { return this } + fun setDefaultSpringConfig(defaultSpring: SpringConfig) { + this.defaultSpring = defaultSpring + } + + fun setDefaultFlingConfig(defaultFling: FlingConfig) { + this.defaultFling = defaultFling + } + /** Starts the animations! */ fun start() { startAction() @@ -752,7 +782,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { ) { constructor() : - this(defaultSpring.stiffness, defaultSpring.dampingRatio) + this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio) constructor(stiffness: Float, dampingRatio: Float) : this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f) @@ -782,10 +812,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { internal var startVelocity: Float ) { - constructor() : this(defaultFling.friction) + constructor() : this(globalDefaultFling.friction) constructor(friction: Float) : - this(friction, defaultFling.min, defaultFling.max) + this(friction, globalDefaultFling.min, globalDefaultFling.max) constructor(friction: Float, min: Float, max: Float) : this(friction, min, max, startVelocity = 0f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 11649ca34709..ffe3cd5bd504 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -16,6 +16,10 @@ package com.android.systemui.screenshot; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -40,7 +44,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,9 +85,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { CompletableFuture<List<Notification.Action>> smartActionsFuture = ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, smartActionsProvider, true, false); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests any exception thrown in waiting for smart actions future to complete does @@ -99,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { RuntimeException.class); List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions( "", "", smartActionsFuture, timeoutMs, mSmartActionsProvider); - Assert.assertEquals(Collections.emptyList(), actions); + assertEquals(Collections.emptyList(), actions); } // Tests any exception thrown in notifying feedback does not affect regular screenshot flow. @@ -123,9 +126,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { mSmartActionsProvider, true, true); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), eq(false)); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once. @@ -152,14 +155,14 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, actionsProvider, true, true); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(smartActions.size(), 0); + assertEquals(smartActions.size(), 0); } - // Tests for notification action extras. + // Tests for share action extras @Test - public void testNotificationActionExtras() { + public void testShareActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } @@ -169,35 +172,70 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - data.createDeleteAction = true; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); - List<Notification.Action> actions = task.populateNotificationActions( - mContext, mContext.getResources(), + + Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); - Assert.assertEquals(actions.size(), 3); - boolean isShareFound = false; - boolean isEditFound = false; - boolean isDeleteFound = false; - for (Notification.Action action : actions) { - Intent intent = action.actionIntent.getIntent(); - Assert.assertNotNull(intent); - Bundle bundle = intent.getExtras(); - Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); - Assert.assertTrue( - bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); - - if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) { - isDeleteFound = intent.getAction() == null; - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) { - isEditFound = Intent.ACTION_EDIT.equals(intent.getAction()); - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) { - isShareFound = Intent.ACTION_SEND.equals(intent.getAction()); - } + Intent intent = shareAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title); + assertEquals(Intent.ACTION_SEND, intent.getAction()); + } + + // Tests for edit action extras + @Test + public void testEditActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); } - Assert.assertTrue(isEditFound); - Assert.assertTrue(isDeleteFound); - Assert.assertTrue(isShareFound); + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = editAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_EDIT, editAction.title); + assertEquals(Intent.ACTION_EDIT, intent.getAction()); + } + + // Tests for share action extras + @Test + public void testDeleteActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action deleteAction = task.createDeleteAction(mContext, + mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = deleteAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(deleteAction.title, GlobalScreenshot.ACTION_TYPE_DELETE); + assertNull(intent.getAction()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 8ab660c3fca3..3e469073694f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -56,7 +56,7 @@ public class LightBarControllerTest extends SysuiTestCase { when(mStatusBarIconController.getTransitionsController()).thenReturn( mLightBarTransitionsController); mLightBarController = new LightBarController(mContext, mStatusBarIconController, - mock(BatteryController.class)); + mock(BatteryController.class), mock(NavigationModeController.class)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java index 27a50027cfe2..14c6e9f9624d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -50,9 +51,11 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { mDependency.injectMockDependency(IWindowManager.class); mDependency.injectMockDependency(AssistManager.class); mDependency.injectMockDependency(OverviewProxyService.class); - mDependency.injectMockDependency(NavigationModeController.class); mDependency.injectMockDependency(StatusBarStateController.class); mDependency.injectMockDependency(KeyguardStateController.class); + doReturn(mContext) + .when(mDependency.injectMockDependency(NavigationModeController.class)) + .getCurrentUserContext(); NavigationBarView navBar = spy(new NavigationBarView(mContext, null)); when(navBar.getCurrentView()).thenReturn(navBar); diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index b9e3050275ad..a1fc3fa1857d 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -399,7 +401,8 @@ public class SystemActionPerformer { ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null) ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext); screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS, + new Handler(Looper.getMainLooper()), null); return true; } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b27c5d54a6fb..35089d6f5de7 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -117,6 +117,7 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Function; /** * A session for a given activity. @@ -3079,19 +3080,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final boolean isWhitelisted = mService .isWhitelistedForAugmentedAutofillLocked(mComponentName); - final String historyItem = - "aug:id=" + id + " u=" + uid + " m=" + mode - + " a=" + ComponentName.flattenToShortString(mComponentName) - + " f=" + mCurrentViewId - + " s=" + remoteService.getComponentName() - + " w=" + isWhitelisted; - mService.getMaster().logRequestLocked(historyItem); - if (!isWhitelisted) { if (sVerbose) { Slog.v(TAG, "triggerAugmentedAutofillLocked(): " + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); } + logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), + mCurrentViewId, isWhitelisted, /*isInline*/null); return null; } @@ -3116,24 +3111,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = mCurrentViewId; + final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback = + response -> { + synchronized (mLock) { + return mInlineSessionController.onInlineSuggestionsResponseLocked( + focusedId, response); + } + }; final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill = (inlineSuggestionsRequest) -> { - remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, - AutofillId.withoutSession(focusedId), - currentValue, inlineSuggestionsRequest, - /*inlineSuggestionsCallback=*/ - response -> { - synchronized (mLock) { - return mInlineSessionController - .onInlineSuggestionsResponseLocked( - focusedId, response); - } - }, - /*onErrorCallback=*/ () -> { - synchronized (mLock) { - cancelAugmentedAutofillLocked(); - } - }, mService.getRemoteInlineSuggestionRenderServiceLocked()); + synchronized (mLock) { + logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), + focusedId, isWhitelisted, inlineSuggestionsRequest != null); + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, + AutofillId.withoutSession(focusedId), currentValue, + inlineSuggestionsRequest, inlineSuggestionsResponseCallback, + /*onErrorCallback=*/ () -> { + synchronized (mLock) { + cancelAugmentedAutofillLocked(); + } + }, mService.getRemoteInlineSuggestionRenderServiceLocked()); + } }; // When the inline suggestion render service is available, there are 2 cases when @@ -3150,9 +3148,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( (extras) -> { - mInlineSessionController.onCreateInlineSuggestionsRequestLocked( - focusedId, /*requestConsumer=*/ requestAugmentedAutofill, - extras); + synchronized (mLock) { + mInlineSessionController.onCreateInlineSuggestionsRequestLocked( + focusedId, /*requestConsumer=*/ requestAugmentedAutofill, + extras); + } }, mHandler)); } else { requestAugmentedAutofill.accept( @@ -3165,6 +3165,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") + private void logAugmentedAutofillRequestLocked(int mode, + ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, + Boolean isInline) { + final String historyItem = + "aug:id=" + id + " u=" + uid + " m=" + mode + + " a=" + ComponentName.flattenToShortString(mComponentName) + + " f=" + focusedId + + " s=" + augmentedRemoteServiceName + + " w=" + isWhitelisted + + " i=" + isInline; + mService.getMaster().logRequestLocked(historyItem); + } + + @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { final RemoteAugmentedAutofillService remoteService = mService .getRemoteAugmentedAutofillServiceLocked(); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 7c47cf0450d4..d36d038058f1 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -612,7 +612,7 @@ public class AppsFilter { } final int insert = ~loc; System.arraycopy(appIds, insert, buffer, 0, whitelistSize - insert); - appIds[insert] = existingUid; + appIds[insert] = existingAppId; System.arraycopy(buffer, 0, appIds, insert + 1, whitelistSize - insert); whitelistSize++; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index c716fce8a86d..4561d2e7a8b2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -104,6 +104,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; @@ -4516,6 +4517,8 @@ public class UserManagerService extends IUserManager.Stub { switch(cmd) { case "list": return runList(pw, shell); + case "list-missing-system-packages": + return runListMissingSystemPackages(pw, shell); default: return shell.handleDefaultCommands(cmd); } @@ -4582,6 +4585,30 @@ public class UserManagerService extends IUserManager.Stub { } } + private int runListMissingSystemPackages(PrintWriter pw, Shell shell) { + boolean verbose = false; + boolean force = false; + String opt; + while ((opt = shell.getNextOption()) != null) { + switch (opt) { + case "-v": + verbose = true; + break; + case "--force": + force = true; + break; + default: + pw.println("Invalid option: " + opt); + return -1; + } + } + + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose); + } + return 0; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -5154,6 +5181,9 @@ public class UserManagerService extends IUserManager.Stub { pw.println(""); pw.println(" list [-v] [-all]"); pw.println(" Prints all users on the system."); + pw.println(" list-missing-system-packages [-v] [--force]"); + pw.println(" Prints all system packages that were not explicitly configured to be " + + "installed."); } } diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 85c2306f6d89..cd1087f5fcd7 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -22,15 +22,16 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.PackageParser; import android.content.res.Resources; import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -38,7 +39,9 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Set; @@ -263,37 +266,85 @@ class UserSystemPackageInstaller { if (!isLogMode(mode) && !isEnforceMode(mode)) { return; } - Slog.v(TAG, "Checking that all system packages are whitelisted."); + final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); + final int size = warnings.size(); + if (size == 0) { + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings"); + return; + } + + if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { + // Only shows whether all whitelisted packages are indeed on the system. + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final boolean isSevere = pair.first; + if (!isSevere) { + final String msg = pair.second; + Slog.w(TAG, msg); + } + } + return; + } + + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings"); + boolean doWtf = !isImplicitWhitelistMode(mode); + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final boolean isSevere = pair.first; + final String msg = pair.second; + if (isSevere) { + if (doWtf) { + Slog.wtf(TAG, msg); + } else { + Slog.e(TAG, msg); + } + } else { + Slog.w(TAG, msg); + } + } + } + + // TODO: method below was created to refactor the one-time logging logic so it can be used on + // dump / cmd as well. It could to be further refactored (for example, creating a new + // structure for the warnings so it doesn't need a Pair). + /** + * Gets warnings for system user whitelisting. + * + * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF, + * {@code false} for WARN) and {@code Pair.second} the message. + */ + @NonNull + private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings( + @PackageWhitelistMode int mode) { final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages(); - PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + final List<Pair<Boolean, String>> warnings = new ArrayList<>(); + final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); // Check whether all whitelisted packages are indeed on the system. + final String notPresentFmt = "%s is whitelisted but not present."; + final String notSystemFmt = "%s is whitelisted and present but not a system package."; for (String pkgName : allWhitelistedPackages) { - AndroidPackage pkg = pmInt.getPackage(pkgName); + final AndroidPackage pkg = pmInt.getPackage(pkgName); if (pkg == null) { - Slog.w(TAG, pkgName + " is whitelisted but not present."); + warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName))); } else if (!pkg.isSystem()) { - Slog.w(TAG, pkgName + " is whitelisted and present but not a system package."); + warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName))); } } // Check whether all system packages are indeed whitelisted. - if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { - return; - } - final boolean doWtf = isEnforceMode(mode); + final String logMessageFmt = "System package %s is not whitelisted using " + + "'install-in-user-type' in SystemConfig for any user types!"; + final boolean isSevere = isEnforceMode(mode); pmInt.forEachPackage(pkg -> { - if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.getManifestPackageName())) { - final String msg = "System package " + pkg.getManifestPackageName() - + " is not whitelisted using 'install-in-user-type' in SystemConfig " - + "for any user types!"; - if (doWtf) { - Slog.wtf(TAG, msg); - } else { - Slog.e(TAG, msg); - } + if (!pkg.isSystem()) return; + final String pkgName = pkg.getManifestPackageName(); + if (!allWhitelistedPackages.contains(pkgName)) { + warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName))); } }); + + return warnings; } /** Whether to only install system packages in new users for which they are whitelisted. */ @@ -602,32 +653,45 @@ class UserSystemPackageInstaller { } void dump(PrintWriter pw) { - final String prefix = " "; - final String prefix2 = prefix + prefix; + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + dumpIndented(ipw); + } + } + + private void dumpIndented(IndentingPrintWriter pw) { final int mode = getWhitelistMode(); pw.println("Whitelisted packages per user type"); - pw.print(prefix); pw.print("Mode: "); + + pw.increaseIndent(); + pw.print("Mode: "); pw.print(mode); pw.print(isEnforceMode(mode) ? " (enforced)" : ""); pw.print(isLogMode(mode) ? " (logged)" : ""); pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : ""); pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : ""); pw.println(); + pw.decreaseIndent(); - pw.print(prefix); pw.println("Legend"); + pw.increaseIndent(); + pw.println("Legend"); + pw.increaseIndent(); for (int idx = 0; idx < mUserTypes.length; idx++) { - pw.print(prefix2); pw.println(idx + " -> " + mUserTypes[idx]); + pw.println(idx + " -> " + mUserTypes[idx]); } + pw.decreaseIndent(); pw.decreaseIndent(); + pw.increaseIndent(); final int size = mWhitelistedPackagesForUserTypes.size(); if (size == 0) { - pw.print(prefix); pw.println("No packages"); + pw.println("No packages"); + pw.decreaseIndent(); return; } - pw.print(prefix); pw.print(size); pw.println(" packages:"); + pw.print(size); pw.println(" packages:"); + pw.increaseIndent(); for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) { final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx); - pw.print(prefix2); pw.print(pkgName); pw.print(": "); + pw.print(pkgName); pw.print(": "); final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx); for (int idx = 0; idx < mUserTypes.length; idx++) { if ((userTypesBitSet & (1 << idx)) != 0) { @@ -636,5 +700,40 @@ class UserSystemPackageInstaller { } pw.println(); } + pw.decreaseIndent(); pw.decreaseIndent(); + + pw.increaseIndent(); + dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true); + pw.decreaseIndent(); + } + + void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) { + final int mode = getWhitelistMode(); + final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode)); + if (!show) return; + + final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); + final int size = warnings.size(); + + if (size == 0) { + if (verbose) { + pw.println("All system packages are accounted for"); + } + return; + } + + if (verbose) { + pw.print(size); pw.println(" warnings for system user:"); + pw.increaseIndent(); + } + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final String lvl = pair.first ? "WTF" : "WARN"; + final String msg = pair.second; + pw.print(lvl); pw.print(": "); pw.println(msg); + } + if (verbose) { + pw.decreaseIndent(); + } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4624e9ea0209..2f84a99774f4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -62,6 +62,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManagerGlobal.ADD_OKAY; @@ -1328,6 +1330,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } @@ -1411,14 +1414,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; + private int mScreenshotSource = SCREENSHOT_KEY_OTHER; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } + public void setScreenshotSource(int screenshotSource) { + mScreenshotSource = screenshotSource; + } + @Override public void run() { - mDefaultDisplayPolicy.takeScreenshot(mScreenshotType); + mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); } } @@ -2693,6 +2701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION : TAKE_SCREENSHOT_FULLSCREEN; mScreenshotRunnable.setScreenshotType(type); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); return -1; } @@ -2709,6 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); } return -1; diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java index a18b690f08cd..d98ad74a9d6b 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java @@ -23,6 +23,7 @@ import android.hardware.soundtrigger.V2_3.Properties; import android.hardware.soundtrigger.V2_3.RecognitionConfig; import android.os.IHwBinder; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.Log; import java.util.HashMap; @@ -48,83 +49,130 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { @Override public Properties getProperties() { - return mUnderlying.getProperties(); + try { + return mUnderlying.getProperties(); + } catch (RuntimeException e) { + throw handleException(e); + } } @Override public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback, int cookie) { - int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback), cookie); - synchronized (mModelStates) { - mModelStates.put(handle, false); + try { + int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback), + cookie); + synchronized (mModelStates) { + mModelStates.put(handle, false); + } + return handle; + } catch (RuntimeException e) { + throw handleException(e); } - return handle; } @Override public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback, int cookie) { - int handle = mUnderlying.loadPhraseSoundModel(soundModel, new CallbackEnforcer(callback), - cookie); - synchronized (mModelStates) { - mModelStates.put(handle, false); + try { + int handle = mUnderlying.loadPhraseSoundModel(soundModel, + new CallbackEnforcer(callback), + cookie); + synchronized (mModelStates) { + mModelStates.put(handle, false); + } + return handle; + } catch (RuntimeException e) { + throw handleException(e); } - return handle; } @Override public void unloadSoundModel(int modelHandle) { - mUnderlying.unloadSoundModel(modelHandle); - synchronized (mModelStates) { - mModelStates.remove(modelHandle); + try { + mUnderlying.unloadSoundModel(modelHandle); + synchronized (mModelStates) { + mModelStates.remove(modelHandle); + } + } catch (RuntimeException e) { + throw handleException(e); } } @Override public void stopRecognition(int modelHandle) { - mUnderlying.stopRecognition(modelHandle); - synchronized (mModelStates) { - mModelStates.replace(modelHandle, false); + try { + mUnderlying.stopRecognition(modelHandle); + synchronized (mModelStates) { + mModelStates.replace(modelHandle, false); + } + } catch (RuntimeException e) { + throw handleException(e); } } @Override public void stopAllRecognitions() { - mUnderlying.stopAllRecognitions(); - synchronized (mModelStates) { - for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) { - entry.setValue(false); + try { + mUnderlying.stopAllRecognitions(); + synchronized (mModelStates) { + for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) { + entry.setValue(false); + } } + } catch (RuntimeException e) { + throw handleException(e); } } @Override public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback, int cookie) { - mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback), cookie); - synchronized (mModelStates) { - mModelStates.replace(modelHandle, true); + try { + mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback), + cookie); + synchronized (mModelStates) { + mModelStates.replace(modelHandle, true); + } + } catch (RuntimeException e) { + throw handleException(e); } } @Override public void getModelState(int modelHandle) { - mUnderlying.getModelState(modelHandle); + try { + mUnderlying.getModelState(modelHandle); + } catch (RuntimeException e) { + throw handleException(e); + } } @Override public int getModelParameter(int modelHandle, int param) { - return mUnderlying.getModelParameter(modelHandle, param); + try { + return mUnderlying.getModelParameter(modelHandle, param); + } catch (RuntimeException e) { + throw handleException(e); + } } @Override public void setModelParameter(int modelHandle, int param, int value) { - mUnderlying.setModelParameter(modelHandle, param, value); + try { + mUnderlying.setModelParameter(modelHandle, param, value); + } catch (RuntimeException e) { + throw handleException(e); + } } @Override public ModelParameterRange queryParameter(int modelHandle, int param) { - return mUnderlying.queryParameter(modelHandle, param); + try { + return mUnderlying.queryParameter(modelHandle, param); + } catch (RuntimeException e) { + throw handleException(e); + } } @Override @@ -142,6 +190,17 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { return mUnderlying.interfaceDescriptor(); } + private static RuntimeException handleException(RuntimeException e) { + Log.e(TAG, "Exception caught from HAL, rebooting HAL"); + rebootHal(); + throw e; + } + + private static void rebootHal() { + // This property needs to be defined in an init.rc script and trigger a HAL reboot. + SystemProperties.set("sys.audio.restart.hal", "1"); + } + private class CallbackEnforcer implements Callback { private final Callback mUnderlying; @@ -157,6 +216,8 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { synchronized (mModelStates) { if (!mModelStates.getOrDefault(model, false)) { Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + rebootHal(); + return; } if (event.header.status != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { @@ -173,6 +234,8 @@ public class SoundTriggerHw2Enforcer implements ISoundTriggerHw2 { synchronized (mModelStates) { if (!mModelStates.getOrDefault(model, false)) { Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + rebootHal(); + return; } if (event.common.header.status != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) { diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java index 635cb613b934..4b464d2d3e04 100644 --- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java +++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java @@ -27,9 +27,7 @@ import android.media.soundtrigger_middleware.PhraseSoundModel; import android.media.soundtrigger_middleware.RecognitionConfig; import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; -import android.os.Parcel; import android.os.RemoteException; -import android.os.SystemProperties; import android.util.Log; import com.android.server.SystemService; @@ -101,28 +99,6 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic } } - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (InternalServerError e) { - if (e.getCause() instanceof HalException) { - // We recover from any sort of HAL failure by rebooting the HAL process. - // This will likely reboot more than just the sound trigger HAL. - // The rest of the system should be able to tolerate that. - rebootHal(); - } - throw e; - } - } - - private static void rebootHal() { - Log.i(TAG, "Rebooting the sound trigger HAL"); - // This property needs to be defined in an init.rc script and trigger a HAL reboot. - SystemProperties.set("sys.audio.restart.hal", "1"); - } - private final static class ModuleService extends ISoundTriggerModule.Stub { private final ISoundTriggerModule mDelegate; @@ -182,22 +158,6 @@ public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareServic public void detach() throws RemoteException { mDelegate.detach(); } - - @Override - public boolean onTransact(int code, Parcel data, Parcel reply, int flags) - throws RemoteException { - try { - return super.onTransact(code, data, reply, flags); - } catch (InternalServerError e) { - if (e.getCause() instanceof HalException) { - // We recover from any sort of HAL failure by rebooting the HAL process. - // This will likely reboot more than just the sound trigger HAL. - // The rest of the system should be able to tolerate that. - rebootHal(); - } - throw e; - } - } } /** diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java new file mode 100644 index 000000000000..54ad1d268e56 --- /dev/null +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java @@ -0,0 +1,152 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.tv.tunerresourcemanager; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A Cas resource object used by the Tuner Resource Manager to record the cas + * information. + * + * @hide + */ +public final class CasResource { + + private final int mSystemId; + + private int mMaxSessionNum; + + private int mAvailableSessionNum; + + /** + * The owner clients' ids when part of the Cas is occupied. + */ + private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>(); + + private CasResource(Builder builder) { + this.mSystemId = builder.mSystemId; + this.mMaxSessionNum = builder.mMaxSessionNum; + this.mAvailableSessionNum = builder.mMaxSessionNum; + } + + public int getSystemId() { + return mSystemId; + } + + public int getMaxSessionNum() { + return mMaxSessionNum; + } + + public int getUsedSessionNum() { + return (mMaxSessionNum - mAvailableSessionNum); + } + + public boolean isFullyUsed() { + return mAvailableSessionNum == 0; + } + + /** + * Update max session number. + * + * @param maxSessionNum the new max session num. + */ + public void updateMaxSessionNum(int maxSessionNum) { + mAvailableSessionNum = Math.max( + 0, mAvailableSessionNum + (maxSessionNum - mMaxSessionNum)); + mMaxSessionNum = maxSessionNum; + } + + /** + * Set an owner for the cas + * + * @param ownerId the client id of the owner. + */ + public void setOwner(int ownerId) { + int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId) == null + ? 1 : (mOwnerClientIdsToSessionNum.get(ownerId) + 1); + mOwnerClientIdsToSessionNum.put(ownerId, sessionNum); + mAvailableSessionNum--; + } + + /** + * Remove an owner of the Cas. + * + * @param ownerId the removing client id of the owner. + */ + public void removeOwner(int ownerId) { + mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId); + mOwnerClientIdsToSessionNum.remove(ownerId); + } + + public Set<Integer> getOwnerClientIds() { + return mOwnerClientIdsToSessionNum.keySet(); + } + + @Override + public String toString() { + return "CasResource[systemId=" + this.mSystemId + + ", isFullyUsed=" + (this.mAvailableSessionNum == 0) + + ", maxSessionNum=" + this.mMaxSessionNum + + ", ownerClients=" + ownersMapToString() + "]"; + } + + /** + * Builder class for {@link CasResource}. + */ + public static class Builder { + + private int mSystemId; + private int mMaxSessionNum; + + Builder(int systemId) { + this.mSystemId = systemId; + } + + /** + * Builder for {@link CasResource}. + * + * @param maxSessionNum the max session num the current Cas has. + */ + public Builder maxSessionNum(int maxSessionNum) { + this.mMaxSessionNum = maxSessionNum; + return this; + } + + /** + * Build a {@link CasResource}. + * + * @return {@link CasResource}. + */ + public CasResource build() { + CasResource cas = new CasResource(this); + return cas; + } + } + + private String ownersMapToString() { + StringBuilder string = new StringBuilder("{"); + for (int clienId : mOwnerClientIdsToSessionNum.keySet()) { + string.append(" clientId=") + .append(clienId) + .append(", owns session num=") + .append(mOwnerClientIdsToSessionNum.get(clienId)) + .append(","); + } + return string.append("}").toString(); + } +} diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index 4cdc17292ac4..2b0fe8a2602b 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -27,6 +27,7 @@ import java.util.Set; public final class ClientProfile { public static final int INVALID_GROUP_ID = -1; + public static final int INVALID_RESOURCE_ID = -1; /** * Client id sent to the client when registering with @@ -56,7 +57,6 @@ public final class ClientProfile { * also lose their resources. */ private int mGroupId = INVALID_GROUP_ID; - /** * Optional nice value for TRM to reduce client’s priority. */ @@ -73,6 +73,11 @@ public final class ClientProfile { private Set<Integer> mUsingLnbIds = new HashSet<>(); /** + * List of the Cas system ids that are used by the current client. + */ + private int mUsingCasSystemId = INVALID_RESOURCE_ID; + + /** * Optional arbitrary priority value given by the client. * * <p>This value can override the default priorotiy calculated from @@ -172,11 +177,32 @@ public final class ClientProfile { } /** + * Set when the client starts to use a Cas system. + * + * @param casSystemId cas being used. + */ + public void useCas(int casSystemId) { + mUsingCasSystemId = casSystemId; + } + + public int getInUseCasSystemId() { + return mUsingCasSystemId; + } + + /** + * Called when the client released a Cas System. + */ + public void releaseCas() { + mUsingCasSystemId = INVALID_RESOURCE_ID; + } + + /** * Called to reclaim all the resources being used by the current client. */ public void reclaimAllResources() { mUsingFrontendIds.clear(); mUsingLnbIds.clear(); + mUsingCasSystemId = INVALID_RESOURCE_ID; } @Override diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 7231813dd949..2f70840cfc8b 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -65,6 +65,8 @@ public class TunerResourceManagerService extends SystemService { private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); // Map of the current available lnb resources private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); + // Map of the current available Cas resources + private Map<Integer, CasResource> mCasResources = new HashMap<>(); @GuardedBy("mLock") private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>(); @@ -158,10 +160,8 @@ public class TunerResourceManagerService extends SystemService { @Override public void updateCasInfo(int casSystemId, int maxSessionNum) { enforceTrmAccessPermission("updateCasInfo"); - if (DEBUG) { - Slog.d(TAG, - "updateCasInfo(casSystemId=" + casSystemId - + ", maxSessionNum=" + maxSessionNum + ")"); + synchronized (mLock) { + updateCasInfoInternal(casSystemId, maxSessionNum); } } @@ -185,11 +185,11 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("frontendHandle can't be null"); } synchronized (mLock) { - try { - return requestFrontendInternal(request, frontendHandle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request frontend from unregistered client:" + + request.getClientId()); } + return requestFrontendInternal(request, frontendHandle); } } @@ -211,32 +211,45 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("demuxHandle can't be null"); } synchronized (mLock) { + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request demux from unregistered client:" + + request.getClientId()); + } return requestDemuxInternal(request, demuxHandle); } } @Override public boolean requestDescrambler(@NonNull TunerDescramblerRequest request, - @NonNull int[] descrambleHandle) throws RemoteException { + @NonNull int[] descramblerHandle) throws RemoteException { enforceDescramblerAccessPermission("requestDescrambler"); enforceTrmAccessPermission("requestDescrambler"); - if (descrambleHandle == null) { - throw new RemoteException("descrambleHandle can't be null"); + if (descramblerHandle == null) { + throw new RemoteException("descramblerHandle can't be null"); } synchronized (mLock) { - return requestDescramblerInternal(request, descrambleHandle); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request descrambler from unregistered client:" + + request.getClientId()); + } + return requestDescramblerInternal(request, descramblerHandle); } } @Override - public boolean requestCasSession( - @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) { + public boolean requestCasSession(@NonNull CasSessionRequest request, + @NonNull int[] casSessionHandle) throws RemoteException { enforceTrmAccessPermission("requestCasSession"); - if (DEBUG) { - Slog.d(TAG, "requestCasSession(request=" + request + ")"); + if (casSessionHandle == null) { + throw new RemoteException("casSessionHandle can't be null"); + } + synchronized (mLock) { + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request cas from unregistered client:" + + request.getClientId()); + } + return requestCasSessionInternal(request, casSessionHandle); } - - return true; } @Override @@ -248,11 +261,11 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("lnbHandle can't be null"); } synchronized (mLock) { - try { - return requestLnbInternal(request, lnbHandle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request lnb from unregistered client:" + + request.getClientId()); } + return requestLnbInternal(request, lnbHandle); } } @@ -264,15 +277,20 @@ public class TunerResourceManagerService extends SystemService { frontendHandle)) { throw new RemoteException("frontendHandle can't be invalid"); } - int frontendId = getResourceIdFromHandle(frontendHandle); - FrontendResource fe = getFrontendResource(frontendId); - if (fe == null) { - throw new RemoteException("Releasing frontend does not exist."); - } - if (fe.getOwnerClientId() != clientId) { - throw new RemoteException("Client is not the current owner of the releasing fe."); - } synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release frontend from unregistered client:" + + clientId); + } + int frontendId = getResourceIdFromHandle(frontendHandle); + FrontendResource fe = getFrontendResource(frontendId); + if (fe == null) { + throw new RemoteException("Releasing frontend does not exist."); + } + if (fe.getOwnerClientId() != clientId) { + throw new RemoteException( + "Client is not the current owner of the releasing fe."); + } releaseFrontendInternal(fe); } } @@ -296,10 +314,26 @@ public class TunerResourceManagerService extends SystemService { } @Override - public void releaseCasSession(int sessionResourceId, int clientId) { + public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException { enforceTrmAccessPermission("releaseCasSession"); - if (DEBUG) { - Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")"); + if (!validateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) { + throw new RemoteException("casSessionHandle can't be invalid"); + } + synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release cas from unregistered client:" + clientId); + } + int casSystemId = getClientProfile(clientId).getInUseCasSystemId(); + CasResource cas = getCasResource(casSystemId); + if (cas == null) { + throw new RemoteException("Releasing cas does not exist."); + } + if (!cas.getOwnerClientIds().contains(clientId)) { + throw new RemoteException( + "Client is not the current owner of the releasing cas."); + } + releaseCasSessionInternal(cas, clientId); } } @@ -310,6 +344,9 @@ public class TunerResourceManagerService extends SystemService { if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) { throw new RemoteException("lnbHandle can't be invalid"); } + if (!checkClientExists(clientId)) { + throw new RemoteException("Release lnb from unregistered client:" + clientId); + } int lnbId = getResourceIdFromHandle(lnbHandle); LnbResource lnb = getLnbResource(lnbId); if (lnb == null) { @@ -465,17 +502,42 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) - throws RemoteException { + protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) { + if (DEBUG) { + Slog.d(TAG, + "updateCasInfo(casSystemId=" + casSystemId + + ", maxSessionNum=" + maxSessionNum + ")"); + } + // If maxSessionNum is 0, removing the Cas Resource. + if (maxSessionNum == 0) { + removeCasResource(casSystemId); + return; + } + // If the Cas exists, updates the Cas Resource accordingly. + CasResource cas = getCasResource(casSystemId); + if (cas != null) { + if (cas.getUsedSessionNum() > maxSessionNum) { + // Sort and release the short number of Cas resources. + int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum; + releaseLowerPriorityClientCasResources(releasingCasResourceNum); + } + cas.updateMaxSessionNum(maxSessionNum); + return; + } + // Add the new Cas Resource. + cas = new CasResource.Builder(casSystemId) + .maxSessionNum(maxSessionNum) + .build(); + addCasResource(cas); + } + + @VisibleForTesting + protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) { if (DEBUG) { Slog.d(TAG, "requestFrontend(request=" + request + ")"); } frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - if (!checkClientExists(request.getClientId())) { - Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId()); - return false; - } ClientProfile requestClient = getClientProfile(request.getClientId()); int grantingFrontendId = -1; int inUseLowestPriorityFrId = -1; @@ -496,7 +558,7 @@ public class TunerResourceManagerService extends SystemService { } else if (grantingFrontendId < 0) { // Record the frontend id with the lowest client priority among all the // in use frontends when no available frontend has been found. - int priority = getOwnerClientPriority(fr); + int priority = getOwnerClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { inUseLowestPriorityFrId = fr.getId(); currentLowestPriority = priority; @@ -530,17 +592,12 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) - throws RemoteException { + protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) { if (DEBUG) { Slog.d(TAG, "requestLnb(request=" + request + ")"); } lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - if (!checkClientExists(request.getClientId())) { - Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId()); - return false; - } ClientProfile requestClient = getClientProfile(request.getClientId()); int grantingLnbId = -1; int inUseLowestPriorityLnbId = -1; @@ -554,7 +611,7 @@ public class TunerResourceManagerService extends SystemService { } else { // Record the lnb id with the lowest client priority among all the // in use lnb when no available lnb has been found. - int priority = getOwnerClientPriority(lnb); + int priority = getOwnerClientPriority(lnb.getOwnerClientId()); if (currentLowestPriority > priority) { inUseLowestPriorityLnbId = lnb.getId(); currentLowestPriority = priority; @@ -588,7 +645,55 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - void releaseFrontendInternal(FrontendResource fe) { + protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) { + if (DEBUG) { + Slog.d(TAG, "requestCasSession(request=" + request + ")"); + } + CasResource cas = getCasResource(request.getCasSystemId()); + // Unregistered Cas System is treated as having unlimited sessions. + if (cas == null) { + cas = new CasResource.Builder(request.getCasSystemId()) + .maxSessionNum(Integer.MAX_VALUE) + .build(); + addCasResource(cas); + } + casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + ClientProfile requestClient = getClientProfile(request.getClientId()); + int lowestPriorityOwnerId = -1; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + if (!cas.isFullyUsed()) { + casSessionHandle[0] = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); + updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId()); + return true; + } + for (int ownerId : cas.getOwnerClientIds()) { + // Record the client id with lowest priority that is using the current Cas system. + int priority = getOwnerClientPriority(ownerId); + if (currentLowestPriority > priority) { + lowestPriorityOwnerId = ownerId; + currentLowestPriority = priority; + } + } + + // When all the Cas sessions are occupied, reclaim the lowest priority client if the + // request client has higher priority. + if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) { + if (!reclaimResource(lowestPriorityOwnerId, + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { + return false; + } + casSessionHandle[0] = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); + updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId()); + return true; + } + return false; + } + + @VisibleForTesting + protected void releaseFrontendInternal(FrontendResource fe) { if (DEBUG) { Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")"); } @@ -596,7 +701,7 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - void releaseLnbInternal(LnbResource lnb) { + protected void releaseLnbInternal(LnbResource lnb) { if (DEBUG) { Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")"); } @@ -604,7 +709,15 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { + protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) { + if (DEBUG) { + Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")"); + } + updateCasClientMappingOnRelease(cas, ownerClientId); + } + + @VisibleForTesting + protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { if (DEBUG) { Slog.d(TAG, "requestDemux(request=" + request + ")"); } @@ -614,7 +727,8 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) { + protected boolean requestDescramblerInternal( + TunerDescramblerRequest request, int[] descramblerHandle) { if (DEBUG) { Slog.d(TAG, "requestDescrambler(request=" + request + ")"); } @@ -742,14 +856,28 @@ public class TunerResourceManagerService extends SystemService { ownerProfile.releaseLnb(releasingLnb.getId()); } + private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) { + CasResource grantingCas = getCasResource(grantingId); + ClientProfile ownerProfile = getClientProfile(ownerClientId); + grantingCas.setOwner(ownerClientId); + ownerProfile.useCas(grantingId); + } + + private void updateCasClientMappingOnRelease( + @NonNull CasResource releasingCas, int ownerClientId) { + ClientProfile ownerProfile = getClientProfile(ownerClientId); + releasingCas.removeOwner(ownerClientId); + ownerProfile.releaseCas(); + } + /** * Get the owner client's priority from the resource id. * - * @param resource a in use tuner resource. + * @param clientId the owner client id. * @return the priority of the owner client of the resource. */ - private int getOwnerClientPriority(TunerResourceBasic resource) { - return getClientProfile(resource.getOwnerClientId()).getPriority(); + private int getOwnerClientPriority(int clientId) { + return getClientProfile(clientId).getPriority(); } @VisibleForTesting @@ -783,6 +911,9 @@ public class TunerResourceManagerService extends SystemService { private void removeFrontendResource(int removingId) { FrontendResource fe = getFrontendResource(removingId); + if (fe == null) { + return; + } if (fe.isInUse()) { releaseFrontendInternal(fe); } @@ -811,6 +942,9 @@ public class TunerResourceManagerService extends SystemService { private void removeLnbResource(int removingId) { LnbResource lnb = getLnbResource(removingId); + if (lnb == null) { + return; + } if (lnb.isInUse()) { releaseLnbInternal(lnb); } @@ -819,6 +953,39 @@ public class TunerResourceManagerService extends SystemService { @VisibleForTesting @Nullable + protected CasResource getCasResource(int systemId) { + return mCasResources.get(systemId); + } + + @VisibleForTesting + protected Map<Integer, CasResource> getCasResources() { + return mCasResources; + } + + private void addCasResource(CasResource newCas) { + // Update resource list and available id list + mCasResources.put(newCas.getSystemId(), newCas); + } + + private void removeCasResource(int removingId) { + CasResource cas = getCasResource(removingId); + if (cas == null) { + return; + } + for (int ownerId : cas.getOwnerClientIds()) { + getClientProfile(ownerId).releaseCas(); + } + mCasResources.remove(removingId); + } + + private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) { + // TODO: Sort with a treemap + + // select the first num client to release + } + + @VisibleForTesting + @Nullable protected ClientProfile getClientProfile(int clientId) { return mClientProfiles.get(clientId); } @@ -830,12 +997,7 @@ public class TunerResourceManagerService extends SystemService { } private void removeClientProfile(int clientId) { - for (int id : getClientProfile(clientId).getInUseFrontendIds()) { - getFrontendResource(id).removeOwner(); - for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) { - getFrontendResource(groupMemberId).removeOwner(); - } - } + reclaimingResourcesFromClient(getClientProfile(clientId)); mClientProfiles.remove(clientId); mListeners.remove(clientId); } @@ -847,6 +1009,9 @@ public class TunerResourceManagerService extends SystemService { for (Integer lnbId : profile.getInUseLnbIds()) { getLnbResource(lnbId).removeOwner(); } + if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) { + getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId()); + } profile.reclaimAllResources(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 736bc9f8bc22..93a757449564 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5400,14 +5400,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH && config.navigation == Configuration.NAVIGATION_NONAV); - int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; - final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR - && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER) - && modeType != Configuration.UI_MODE_TYPE_TELEVISION - && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(), HIDE_ERROR_DIALOGS, 0) != 0; - mShowDialogs = inputMethodExists && uiModeSupportsDialogs && !hideDialogsSet; + mShowDialogs = inputMethodExists + && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext) + && !hideDialogsSet; } private void updateFontScaleIfNeeded(@UserIdInt int userId) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e9d3d56ee283..8aace212d094 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -261,6 +261,8 @@ public class DisplayPolicy { @Px private int mRightGestureInset; + private boolean mNavButtonForcedVisible; + StatusBarManagerInternal getStatusBarManagerInternal() { synchronized (mServiceAcquireLock) { if (mStatusBarManagerInternal == null) { @@ -1046,12 +1048,14 @@ public class DisplayPolicy { // calculate inset. if (navigationBarPosition(displayFrames.mDisplayWidth, displayFrames.mDisplayHeight, - displayFrames.mRotation) == NAV_BAR_BOTTOM) { + displayFrames.mRotation) == NAV_BAR_BOTTOM + && !mNavButtonForcedVisible) { + sTmpRect.set(displayFrames.mUnrestricted); sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe); inOutFrame.top = sTmpRect.bottom - getNavigationBarHeight(displayFrames.mRotation, - mDisplayContent.getConfiguration().uiMode); + mDisplayContent.getConfiguration().uiMode); } }, @@ -2810,6 +2814,8 @@ public class DisplayPolicy { mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res); mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res); + mNavButtonForcedVisible = + mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); mNavigationBarAlwaysShowOnSideGesture = res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture); @@ -3783,13 +3789,14 @@ public class DisplayPolicy { * @param screenshotType The type of screenshot, for example either * {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource) */ - public void takeScreenshot(int screenshotType) { + public void takeScreenshot(int screenshotType, int source) { if (mScreenshotHelper != null) { mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), - mHandler, null /* completionConsumer */); + source, mHandler, null /* completionConsumer */); } } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index a5014145aa60..51095ee85eea 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2446,17 +2446,15 @@ public class WindowManagerService extends IWindowManager.Stub if (controls != null) { final int length = Math.min(controls.length, outControls.length); for (int i = 0; i < length; i++) { - final InsetsSourceControl control = controls[i]; - - // Check if we are sending invalid leashes. - final SurfaceControl leash = control != null ? control.getLeash() : null; - if (leash != null && !leash.isValid()) { - Slog.wtf(TAG, leash + " is not valid before sending to " + win, - leash.getReleaseStack()); - } - - outControls[i] = win.isClientLocal() && control != null - ? new InsetsSourceControl(control) : control; + // We will leave the critical section before returning the leash to the client, + // so we need to copy the leash to prevent others release the one that we are + // about to return. + // TODO: We will have an extra copy if the client is not local. + // For now, we rely on GC to release it. + // Maybe we can modify InsetsSourceControl.writeToParcel so it can release + // the extra leash as soon as possible. + outControls[i] = controls[i] != null + ? new InsetsSourceControl(controls[i]) : null; } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 22b0b62a819d..7b845557cf73 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -161,8 +161,11 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -171,6 +174,7 @@ import android.content.pm.CrossProfileAppsInternal; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -2704,6 +2708,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { clearDeviceOwnerLocked(doAdmin, doUserId); Slog.i(LOG_TAG, "Removing admin artifacts..."); removeAdminArtifacts(doAdminReceiver, doUserId); + Slog.i(LOG_TAG, "Uninstalling the DO..."); + uninstallOrDisablePackage(doAdminComponent.getPackageName(), doUserId); Slog.i(LOG_TAG, "Migration complete."); // Note: KeyChain keys are not removed and will remain accessible for the apps that have @@ -2715,6 +2721,47 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private void uninstallOrDisablePackage(String packageName, int userHandle) { + final ApplicationInfo appInfo; + try { + appInfo = mIPackageManager.getApplicationInfo( + packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle); + } catch (RemoteException e) { + // Shouldn't happen. + return; + } + if (appInfo == null) { + Slog.wtf(LOG_TAG, "Failed to get package info for " + packageName); + return; + } + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + Slog.i(LOG_TAG, String.format( + "Package %s is pre-installed, marking disabled until used", packageName)); + mContext.getPackageManager().setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */); + return; + } + + final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + final int status = intent.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + Slog.i(LOG_TAG, String.format( + "Package %s uninstalled for user %d", packageName, userHandle)); + } else { + Slog.e(LOG_TAG, String.format( + "Failed to uninstall %s; status: %d", packageName, status)); + } + } + }; + + final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller(); + pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender)); + } + private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { // The following policies can be already controlled via parent instance, skip if so. if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 335217719cc9..064e3486823a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -308,7 +310,7 @@ public class SystemActionPerformerTest { AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); verify(mMockScreenshotHelper).takeScreenshot( eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(), - anyBoolean(), any(Handler.class), any()); + anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any()); } // PendingIntent is a final class and cannot be mocked. So we are using this diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 965304f3c433..21af3563b869 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -26,6 +26,7 @@ import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.media.tv.tuner.frontend.FrontendSettings; +import android.media.tv.tunerresourcemanager.CasSessionRequest; import android.media.tv.tunerresourcemanager.IResourcesReclaimListener; import android.media.tv.tunerresourcemanager.ResourceClientProfile; import android.media.tv.tunerresourcemanager.TunerDemuxRequest; @@ -34,7 +35,6 @@ import android.media.tv.tunerresourcemanager.TunerFrontendInfo; import android.media.tv.tunerresourcemanager.TunerFrontendRequest; import android.media.tv.tunerresourcemanager.TunerLnbRequest; import android.media.tv.tunerresourcemanager.TunerResourceManager; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -236,12 +236,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -264,12 +260,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -296,12 +288,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(0); } @@ -334,23 +322,15 @@ public class TunerResourceManagerServiceTest { int[] frontendHandle = new int[1]; TunerFrontendRequest request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId()) @@ -394,32 +374,20 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isRelaimed()).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); + assertThat(listener.isRelaimed()).isFalse(); request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isRelaimed()).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); + assertThat(listener.isRelaimed()).isFalse(); } @Test @@ -456,12 +424,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) @@ -470,12 +434,8 @@ public class TunerResourceManagerServiceTest { request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) @@ -511,12 +471,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]); assertThat(frontendId).isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService @@ -534,6 +490,99 @@ public class TunerResourceManagerServiceTest { } @Test + public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[2]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + profiles[1] = new ResourceClientProfile("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientPriorities = {100, 500}; + int[] clientId0 = new int[1]; + int[] clientId1 = new int[1]; + TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[0], listener, clientId0); + assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfile(clientId0[0]) + .setPriority(clientPriorities[0]); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[1], new TestResourcesReclaimListener(), clientId1); + assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfile(clientId1[0]) + .setPriority(clientPriorities[1]); + + // Init cas resources. + mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); + + CasSessionRequest request = new CasSessionRequest(clientId0[0], 1 /*casSystemId*/); + int[] casSessionHandle = new int[1]; + // Request for 2 cas sessions. + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue(); + + request = new CasSessionRequest(clientId1[0], 1); + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) + .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + assertThat(listener.isRelaimed()).isTrue(); + } + + @Test + public void releaseCasTest() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[1]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); + mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init cas resources. + mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); + + CasSessionRequest request = new CasSessionRequest(clientId[0], 1 /*casSystemId*/); + int[] casSessionHandle = new int[1]; + // Request for 1 cas sessions. + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + + // Release cas + mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService + .getCasResource(1), clientId[0]); + assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) + .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEmpty(); + } + + @Test public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() { // Register clients ResourceClientProfile[] profiles = new ResourceClientProfile[2]; @@ -562,24 +611,16 @@ public class TunerResourceManagerServiceTest { TunerLnbRequest request = new TunerLnbRequest(clientId0[0]); int[] lnbHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0])) .isEqualTo(lnbIds[0]); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) .getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0]))); request = new TunerLnbRequest(clientId1[0]); - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0])) .isEqualTo(lnbIds[0]); assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0]) @@ -608,12 +649,8 @@ public class TunerResourceManagerServiceTest { TunerLnbRequest request = new TunerLnbRequest(clientId[0]); int[] lnbHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]); assertThat(lnbId).isEqualTo(lnbIds[0]); @@ -647,12 +684,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 9dfa3ac7a5d8..fa9909547fc4 100755 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -734,6 +734,31 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.ORIGINAL_CONNECTION_ID"; /** + * Extra key set on a {@link Connection} when it was created via a remote connection service. + * For example, if a connection manager requests a remote connection service to create a call + * using one of the remote connection service's phone account handle, this extra will be set so + * that Telecom knows that the wrapped remote connection originated in a remote connection + * service. We stash this in the extras since connection managers will typically copy the + * extras from a {@link RemoteConnection} to a {@link Connection} (there is ultimately not + * other way to relate a {@link RemoteConnection} to a {@link Connection}. + * @hide + */ + public static final String EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE = + "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE"; + + /** + * Extra key set from a {@link ConnectionService} when using the remote connection APIs + * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle, + * ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving + * {@link ConnectionService} with a means to know the package name of the requesting + * {@link ConnectionService} so that {@link #EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE} can be set for + * better visibility in Telecom of where a connection ultimately originated. + * @hide + */ + public static final String EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME = + "android.telecom.extra.REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME"; + + /** * Boolean connection extra key set on the extras passed to * {@link Connection#sendConnectionEvent} which indicates that audio is present * on the RTT call when the extra value is true. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 1b60e4820ad0..a716b37f7efd 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1859,9 +1859,25 @@ public abstract class ConnectionService extends Service { new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"), request.getAccountHandle()); } - if (conference.getExtras() != null) { - conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + + Bundle extras = request.getExtras(); + Bundle newExtras = new Bundle(); + newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + if (extras != null) { + // If the request originated from a remote connection service, we will add some + // tracking information that Telecom can use to keep informed of which package + // made the remote request, and which remote connection service was used. + if (extras.containsKey(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) { + newExtras.putString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + extras.getString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + request.getAccountHandle()); + } } + conference.putExtras(newExtras); + mConferenceById.put(callId, conference); mIdByConference.put(conference, callId); conference.addListener(mConferenceListener); @@ -1936,6 +1952,30 @@ public abstract class ConnectionService extends Service { Log.i(this, "createConnection, implementation returned null connection."); connection = Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION")); + } else { + try { + Bundle extras = request.getExtras(); + if (extras != null) { + // If the request originated from a remote connection service, we will add some + // tracking information that Telecom can use to keep informed of which package + // made the remote request, and which remote connection service was used. + if (extras.containsKey( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) { + Bundle newExtras = new Bundle(); + newExtras.putString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + extras.getString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME + )); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + request.getAccountHandle()); + connection.putExtras(newExtras); + } + } + } catch (UnsupportedOperationException ose) { + // Do nothing; if the ConnectionService reported a failure it will be an instance + // of an immutable Connection which we cannot edit, so we're out of luck. + } } boolean isSelfManaged = diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index d82e93fac76d..8d3f4e1df8bc 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -427,7 +427,7 @@ public class Session { StringBuilder methodName = new StringBuilder(); methodName.append(getFullMethodPath(false /*truncatePath*/)); if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) { - methodName.append("(InCall package: "); + methodName.append("("); methodName.append(mOwnerInfo); methodName.append(")"); } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index cad5b707a146..a0833011715d 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -258,6 +258,9 @@ final class RemoteConnectionService { // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. Bundle newExtras = new Bundle(); newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + // Track the fact this request was relayed through the remote connection service. + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + parcel.getPhoneAccount()); conference.putExtras(newExtras); conference.registerCallback(new RemoteConference.Callback() { @@ -383,6 +386,11 @@ final class RemoteConnectionService { RemoteConnection remoteConnection = new RemoteConnection(callId, mOutgoingConnectionServiceRpc, connection, callingPackage, callingTargetSdkVersion); + // Track that it is via a remote connection. + Bundle newExtras = new Bundle(); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + connection.getPhoneAccount()); + remoteConnection.putExtras(newExtras); mConnectionById.put(callId, remoteConnection); remoteConnection.registerCallback(new RemoteConnection.Callback() { @Override @@ -535,10 +543,20 @@ final class RemoteConnectionService { ConnectionRequest request, boolean isIncoming) { final String id = UUID.randomUUID().toString(); + Bundle extras = new Bundle(); + if (request.getExtras() != null) { + extras.putAll(request.getExtras()); + } + // We will set the package name for the originator of the remote request; this lets the + // receiving ConnectionService know that the request originated from a remote connection + // service so that it can provide tracking information for Telecom. + extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + mOurConnectionServiceImpl.getApplicationContext().getOpPackageName()); + final ConnectionRequest newRequest = new ConnectionRequest.Builder() .setAccountHandle(request.getAccountHandle()) .setAddress(request.getAddress()) - .setExtras(request.getExtras()) + .setExtras(extras) .setVideoState(request.getVideoState()) .setRttPipeFromInCall(request.getRttPipeFromInCall()) .setRttPipeToInCall(request.getRttPipeToInCall()) |