diff options
90 files changed, 2700 insertions, 1511 deletions
diff --git a/api/current.txt b/api/current.txt index 8703bf41438b..52b4741c7eec 100644 --- a/api/current.txt +++ b/api/current.txt @@ -41426,6 +41426,7 @@ package android.telecom { method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); method public void cancelMissedCallsNotification(); method public android.content.Intent createManageBlockedNumbersIntent(); + method public boolean endCall(); method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle); method public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); method public java.lang.String getDefaultDialerPackage(); diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp index 297a0711f686..b3bab0cfb4dc 100644 --- a/cmds/incidentd/src/Reporter.cpp +++ b/cmds/incidentd/src/Reporter.cpp @@ -22,6 +22,7 @@ #include "report_directory.h" #include "section_list.h" +#include <android-base/properties.h> #include <android/os/DropBoxManager.h> #include <private/android_filesystem_config.h> #include <utils/SystemClock.h> @@ -31,6 +32,7 @@ #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> +#include <string> /** * The directory where the incident reports are stored. @@ -129,6 +131,8 @@ Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) { int mainDest = -1; HeaderSection headers; MetadataSection metadataSection; + std::string buildType = android::base::GetProperty("ro.build.type", ""); + const bool isUserdebugOrEng = buildType == "userdebug" || buildType == "eng"; // See if we need the main file for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { @@ -175,6 +179,11 @@ Reporter::run_report_status_t Reporter::runReport(size_t* reportByteSize) { // and report to those that care that we're doing it. for (const Section** section = SECTION_LIST; *section; section++) { const int id = (*section)->id; + if ((*section)->userdebugAndEngOnly && !isUserdebugOrEng) { + ALOGD("Skipping incident report section %d '%s' because it's limited to userdebug/eng", + id, (*section)->name.string()); + continue; + } if (this->batch.containsSection(id)) { ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string()); for (ReportRequestSet::iterator it = batch.begin(); it != batch.end(); it++) { diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp index e5bde0d83cf7..4bbe04204a54 100644 --- a/cmds/incidentd/src/Section.cpp +++ b/cmds/incidentd/src/Section.cpp @@ -151,8 +151,11 @@ DONE: } // ================================================================================ -Section::Section(int i, int64_t timeoutMs, bool deviceSpecific) - : id(i), timeoutMs(timeoutMs), deviceSpecific(deviceSpecific) {} +Section::Section(int i, int64_t timeoutMs, bool userdebugAndEngOnly, bool deviceSpecific) + : id(i), + timeoutMs(timeoutMs), + userdebugAndEngOnly(userdebugAndEngOnly), + deviceSpecific(deviceSpecific) {} Section::~Section() {} @@ -239,7 +242,7 @@ static inline bool isSysfs(const char* filename) { return strncmp(filename, "/sy FileSection::FileSection(int id, const char* filename, const bool deviceSpecific, const int64_t timeoutMs) - : Section(id, timeoutMs, deviceSpecific), mFilename(filename) { + : Section(id, timeoutMs, false, deviceSpecific), mFilename(filename) { name = filename; mIsSysfs = isSysfs(filename); } @@ -417,8 +420,8 @@ WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec) WorkerThreadData::~WorkerThreadData() {} // ================================================================================ -WorkerThreadSection::WorkerThreadSection(int id, const int64_t timeoutMs) - : Section(id, timeoutMs) {} +WorkerThreadSection::WorkerThreadSection(int id, const int64_t timeoutMs, bool userdebugAndEngOnly) + : Section(id, timeoutMs, userdebugAndEngOnly) {} WorkerThreadSection::~WorkerThreadSection() {} @@ -615,8 +618,8 @@ status_t CommandSection::Execute(ReportRequestSet* requests) const { } // ================================================================================ -DumpsysSection::DumpsysSection(int id, const char* service, ...) - : WorkerThreadSection(id), mService(service) { +DumpsysSection::DumpsysSection(int id, bool userdebugAndEngOnly, const char* service, ...) + : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS, userdebugAndEngOnly), mService(service) { name = "dumpsys "; name += service; diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h index 577892ef3a38..a031a15fe7c9 100644 --- a/cmds/incidentd/src/Section.h +++ b/cmds/incidentd/src/Section.h @@ -40,10 +40,12 @@ class Section { public: const int id; const int64_t timeoutMs; // each section must have a timeout + const bool userdebugAndEngOnly; const bool deviceSpecific; String8 name; - Section(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, bool deviceSpecific = false); + Section(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, bool userdebugAndEngOnly = false, + bool deviceSpecific = false); virtual ~Section(); virtual status_t Execute(ReportRequestSet* requests) const = 0; @@ -107,7 +109,8 @@ private: */ class WorkerThreadSection : public Section { public: - WorkerThreadSection(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS); + WorkerThreadSection(int id, int64_t timeoutMs = REMOTE_CALL_TIMEOUT_MS, + bool userdebugAndEngOnly = false); virtual ~WorkerThreadSection(); virtual status_t Execute(ReportRequestSet* requests) const; @@ -137,7 +140,7 @@ private: */ class DumpsysSection : public WorkerThreadSection { public: - DumpsysSection(int id, const char* service, ...); + DumpsysSection(int id, bool userdebugAndEngOnly, const char* service, ...); virtual ~DumpsysSection(); virtual status_t BlockingCall(int pipeWriteFd) const; diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp index e6272edf7dcc..50ed18d3e2b0 100644 --- a/cmds/statsd/benchmark/metric_util.cpp +++ b/cmds/statsd/benchmark/metric_util.cpp @@ -366,7 +366,7 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const long timeBaseSec, const Stat sp<AlarmMonitor> periodicAlarmMonitor; sp<StatsLogProcessor> processor = new StatsLogProcessor( uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec * NS_PER_SEC, - [](const ConfigKey&){}); + [](const ConfigKey&){return true;}); processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config); return processor; } diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index ed07acc6317b..4d279481dc76 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -75,7 +75,7 @@ StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& periodicAlarmMonitor, const int64_t timeBaseNs, - const std::function<void(const ConfigKey&)>& sendBroadcast) + const std::function<bool(const ConfigKey&)>& sendBroadcast) : mUidMap(uidMap), mAnomalyAlarmMonitor(anomalyAlarmMonitor), mPeriodicAlarmMonitor(periodicAlarmMonitor), @@ -465,12 +465,21 @@ void StatsLogProcessor::flushIfNecessaryLocked( // We suspect that the byteSize() computation is expensive, so we set a rate limit. size_t totalBytes = metricsManager.byteSize(); mLastByteSizeTimes[key] = timestampNs; + bool requestDump = false; if (totalBytes > StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data. metricsManager.dropData(timestampNs); StatsdStats::getInstance().noteDataDropped(key); VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); - } else if (totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) { + } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) || + (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) { + // Request to send a broadcast if: + // 1. in memory data > threshold OR + // 2. config has old data report on disk. + requestDump = true; + } + + if (requestDump) { // Send broadcast so that receivers can pull data. auto lastBroadcastTime = mLastBroadcastTimes.find(key); if (lastBroadcastTime != mLastBroadcastTimes.end()) { @@ -479,10 +488,12 @@ void StatsLogProcessor::flushIfNecessaryLocked( return; } } - mLastBroadcastTimes[key] = timestampNs; - VLOG("StatsD requesting broadcast for %s", key.ToString().c_str()); - mSendBroadcast(key); - StatsdStats::getInstance().noteBroadcastSent(key); + if (mSendBroadcast(key)) { + mOnDiskDataConfigs.erase(key); + VLOG("StatsD triggered data fetch for %s", key.ToString().c_str()); + mLastBroadcastTimes[key] = timestampNs; + StatsdStats::getInstance().noteBroadcastSent(key); + } } } @@ -505,6 +516,8 @@ void StatsLogProcessor::WriteDataToDiskLocked(const ConfigKey& key, return; } proto.flush(fd.get()); + // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP. + mOnDiskDataConfigs.insert(key); } void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason) { @@ -533,6 +546,11 @@ int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { } } +void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + mOnDiskDataConfigs.insert(key); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 8de0f4124841..d6fb8de801bf 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -48,7 +48,7 @@ public: StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor, const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor, const int64_t timeBaseNs, - const std::function<void(const ConfigKey&)>& sendBroadcast); + const std::function<bool(const ConfigKey&)>& sendBroadcast); virtual ~StatsLogProcessor(); void OnLogEvent(LogEvent* event, bool reconnectionStarts); @@ -99,6 +99,9 @@ public: #endif } + // Add a specific config key to the possible configs to dump ASAP. + void noteOnDiskData(const ConfigKey& key); + private: // For testing only. inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const { @@ -118,6 +121,9 @@ private: // Tracks when we last checked the bytes consumed for each config key. std::unordered_map<ConfigKey, long> mLastByteSizeTimes; + // Tracks which config keys has metric reports on disk + std::set<ConfigKey> mOnDiskDataConfigs; + sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid. StatsPullerManager mStatsPullerManager; @@ -159,7 +165,7 @@ private: // Function used to send a broadcast so that receiver for the config key can call getData // to retrieve the stored data. - std::function<void(const ConfigKey& key)> mSendBroadcast; + std::function<bool(const ConfigKey& key)> mSendBroadcast; const int64_t mTimeBaseNs; diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index e823f6884b29..acf3ad25ba1e 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -158,11 +158,14 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) auto receiver = mConfigManager->GetConfigReceiver(key); if (sc == nullptr) { VLOG("Could not find StatsCompanionService"); + return false; } else if (receiver == nullptr) { VLOG("Statscompanion could not find a broadcast receiver for %s", key.ToString().c_str()); + return false; } else { sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key)); + return true; } } ); @@ -948,6 +951,11 @@ Status StatsService::setDataFetchOperation(int64_t key, IPCThreadState* ipc = IPCThreadState::self(); ConfigKey configKey(ipc->getCallingUid(), key); mConfigManager->SetConfigReceiver(configKey, intentSender); + if (StorageManager::hasConfigMetricsReport(configKey)) { + VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk", + configKey.ToString().c_str()); + mProcessor->noteOnDiskData(configKey); + } return Status::ok(); } @@ -988,6 +996,15 @@ Status StatsService::unsetBroadcastSubscriber(int64_t configId, return Status::ok(); } +Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) { + // Permission check not necessary as it's meant for applications to write to + // statsd. + android::util::stats_write(util::APP_BREADCRUMB_REPORTED, + IPCThreadState::self()->getCallingUid(), label, + state); + return Status::ok(); +} + void StatsService::binderDied(const wp <IBinder>& who) { ALOGW("statscompanion service died"); StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec()); diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 67fc7709103a..b3a477645b73 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -139,6 +139,11 @@ public: /** Inform statsCompanion that statsd is ready. */ virtual void sayHiToStatsCompanion(); + /** + * Binder call to get AppBreadcrumbReported atom. + */ + virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override; + /** IBinder::DeathRecipient */ virtual void binderDied(const wp<IBinder>& who) override; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index bf0f7206174b..2d14b05c8f5b 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -62,7 +62,7 @@ MetricsManager::MetricsManager(const ConfigKey& key, const StatsdConfig& config, : mConfigKey(key), mUidMap(uidMap), mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1), mTtlEndNs(-1), - mLastReportTimeNs(timeBaseNs), + mLastReportTimeNs(currentTimeNs), mLastReportWallClockNs(getWallClockNs()) { // Init the ttl end timestamp. refreshTtl(timeBaseNs); @@ -247,16 +247,6 @@ void MetricsManager::onLogEvent(const LogEvent& event) { return; } - // Label is 2nd from last field and must be from [0, 15]. - long appHookLabel = event.GetLong(event.size()-1, &err); - if (err != NO_ERROR ) { - VLOG("APP_BREADCRUMB_REPORTED had error when parsing the label field"); - return; - } else if (appHookLabel < 0 || appHookLabel > 15) { - VLOG("APP_BREADCRUMB_REPORTED does not have valid label %ld", appHookLabel); - return; - } - // The state must be from 0,3. This part of code must be manually updated. long appHookState = event.GetLong(event.size(), &err); if (err != NO_ERROR ) { diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 170d6a7eacae..4f39df9e7ad2 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -79,7 +79,8 @@ public: } }; - // Returns the elapsed realtime when this metric manager last reported metrics. + // Returns the elapsed realtime when this metric manager last reported metrics. If this config + // has not yet dumped any reports, this is the time the metricsmanager was initialized. inline int64_t getLastReportTimeNs() const { return mLastReportTimeNs; }; diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp index ea8da1408017..1f8181266b65 100644 --- a/cmds/statsd/src/storage/StorageManager.cpp +++ b/cmds/statsd/src/storage/StorageManager.cpp @@ -160,6 +160,34 @@ void StorageManager::sendBroadcast(const char* path, } } +bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) { + unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir); + if (dir == NULL) { + VLOG("Path %s does not exist", STATS_DATA_DIR); + return false; + } + + string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId()); + + dirent* de; + while ((de = readdir(dir.get()))) { + char* name = de->d_name; + if (name[0] == '.') continue; + + size_t nameLen = strlen(name); + size_t suffixLen = suffix.length(); + if (suffixLen <= nameLen && + strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) { + // Check again that the file name is parseable. + int64_t result[3]; + parseFileName(name, result); + if (result[0] == -1) continue; + return true; + } + } + return false; +} + void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto) { unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir); if (dir == NULL) { diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h index 8953be97aeaf..4840f3c4fa34 100644 --- a/cmds/statsd/src/storage/StorageManager.h +++ b/cmds/statsd/src/storage/StorageManager.h @@ -63,6 +63,11 @@ public: const std::function<void(const ConfigKey&)>& sendBroadcast); /** + * Returns true if there's at least one report on disk. + */ + static bool hasConfigMetricsReport(const ConfigKey& key); + + /** * Appends ConfigMetricsReport found on disk to the specific proto and * delete it. */ diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp index 9fdf7a33a7d0..de6e6e542c60 100644 --- a/cmds/statsd/tests/StatsLogProcessor_test.cpp +++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp @@ -62,7 +62,7 @@ TEST(StatsLogProcessorTest, TestRateLimitByteSize) { sp<AlarmMonitor> periodicAlarmMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. StatsLogProcessor p(m, anomalyAlarmMonitor, periodicAlarmMonitor, 0, - [](const ConfigKey& key) {}); + [](const ConfigKey& key) {return true;}); MockMetricsManager mockMetricsManager; @@ -81,7 +81,7 @@ TEST(StatsLogProcessorTest, TestRateLimitBroadcast) { sp<AlarmMonitor> subscriberAlarmMonitor; int broadcastCount = 0; StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); + [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;}); MockMetricsManager mockMetricsManager; @@ -107,7 +107,7 @@ TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge) { sp<AlarmMonitor> subscriberAlarmMonitor; int broadcastCount = 0; StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); + [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;}); MockMetricsManager mockMetricsManager; @@ -131,7 +131,7 @@ TEST(StatsLogProcessorTest, TestUidMapHasSnapshot) { sp<AlarmMonitor> subscriberAlarmMonitor; int broadcastCount = 0; StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); + [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;}); ConfigKey key(3, 4); StatsdConfig config; config.add_allowed_log_source("AID_ROOT"); @@ -156,7 +156,7 @@ TEST(StatsLogProcessorTest, TestReportIncludesSubConfig) { sp<AlarmMonitor> subscriberAlarmMonitor; int broadcastCount = 0; StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); + [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;}); ConfigKey key(3, 4); StatsdConfig config; auto annotation = config.add_annotation(); @@ -185,7 +185,7 @@ TEST(StatsLogProcessorTest, TestOutOfOrderLogs) { sp<AlarmMonitor> subscriberAlarmMonitor; int broadcastCount = 0; StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [&broadcastCount](const ConfigKey& key) { broadcastCount++; }); + [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;}); LogEvent event1(0, 1 /*logd timestamp*/, 1001 /*elapsedRealtime*/); event1.init(); diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp index dde50c2597da..e23131d7b45d 100644 --- a/cmds/statsd/tests/UidMap_test.cpp +++ b/cmds/statsd/tests/UidMap_test.cpp @@ -44,7 +44,7 @@ TEST(UidMapTest, TestIsolatedUID) { sp<AlarmMonitor> subscriberAlarmMonitor; // Construct the processor with a dummy sendBroadcast function that does nothing. StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0, - [](const ConfigKey& key) {}); + [](const ConfigKey& key) {return true;}); LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1); addEvent.write(100); // parent UID addEvent.write(101); // isolated UID diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp index 590399312b88..e0c98cb9735b 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -459,7 +459,7 @@ sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const in new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){}, [](const sp<IStatsCompanionService>&){}); sp<StatsLogProcessor> processor = new StatsLogProcessor( - uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){}); + uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){return true;}); processor->OnConfigUpdated(currentTimeNs, key, config); return processor; } diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index 29d7a21b8b77..9b695f9020be 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -366,8 +366,12 @@ Landroid/app/job/IJobService$Stub;-><init>()V Landroid/app/job/IJobService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/app/job/IJobService; Landroid/app/job/IJobService;->startJob(Landroid/app/job/JobParameters;)V Landroid/app/job/IJobService;->stopJob(Landroid/app/job/JobParameters;)V +Landroid/app/job/JobInfo$Builder;->setFlags(I)Landroid/app/job/JobInfo$Builder; +Landroid/app/job/JobInfo$Builder;->setPriority(I)Landroid/app/job/JobInfo$Builder; Landroid/app/job/JobInfo;->flags:I +Landroid/app/job/JobInfo;->FLAG_WILL_BE_FOREGROUND:I Landroid/app/job/JobInfo;->jobId:I +Landroid/app/job/JobInfo;->PRIORITY_FOREGROUND_APP:I Landroid/app/job/JobInfo;->service:Landroid/content/ComponentName; Landroid/app/job/JobParameters;->callback:Landroid/os/IBinder; Landroid/app/job/JobParameters;->jobId:I @@ -571,8 +575,8 @@ Landroid/bluetooth/BluetoothHeadset;->connectAudio()Z Landroid/bluetooth/BluetoothHeadset;->disconnectAudio()Z Landroid/bluetooth/BluetoothHeadset;->getActiveDevice()Landroid/bluetooth/BluetoothDevice; Landroid/bluetooth/BluetoothHeadset;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z -Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z +Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall()Z +Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall()Z Landroid/bluetooth/BluetoothHearingAid;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String; Landroid/bluetooth/BluetoothHearingAid;->getActiveDevices()Ljava/util/List; Landroid/bluetooth/BluetoothHearingAid;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index a68f485f4374..0c91a2054b8b 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.Manifest; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; @@ -633,8 +634,9 @@ public final class BluetoothHeadset implements BluetoothProfile { * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Bluetooth headset - * @return false if there is no headset connected of if the connected headset doesn't support - * voice recognition or on error, true otherwise + * @return false if there is no headset connected, or the connected headset doesn't support + * voice recognition, or voice recognition is already started, or audio channel is occupied, + * or on error, true otherwise */ public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); @@ -654,10 +656,15 @@ public final class BluetoothHeadset implements BluetoothProfile { * Stop Bluetooth Voice Recognition mode, and shut down the * Bluetooth audio path. * + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * If this function returns true, this intent will be broadcasted with + * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. + * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Bluetooth headset - * @return false if there is no headset connected or on error, true otherwise + * @return false if there is no headset connected, or voice recognition has not started, + * or voice recognition has ended on this headset, or on error, true otherwise */ public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); @@ -798,11 +805,12 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Check if Bluetooth SCO audio is connected. + * Check if at least one headset's SCO audio is connected or connecting * * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * - * @return true if SCO is connected, false otherwise or on error + * @return true if at least one device's SCO audio is connected or connecting, false otherwise + * or on error * @hide */ public boolean isAudioOn() { @@ -821,11 +829,21 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Initiates a connection of headset audio. - * It setup SCO channel with remote connected headset device. + * Initiates a connection of headset audio to the current active device + * + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * If this function returns true, this intent will be broadcasted with + * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. * - * @return true if successful false if there was some error such as there is no connected - * headset + * <p> {@link #EXTRA_STATE} will transition from + * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when + * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} + * in case of failure to establish the audio connection. + * + * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true + * before calling this method + * + * @return false if there was some error such as there is no active headset * @hide */ public boolean connectAudio() { @@ -844,11 +862,14 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Initiates a disconnection of headset audio. - * It tears down the SCO channel from remote headset device. + * Initiates a disconnection of HFP SCO audio. + * Tear down voice recognition or virtual voice call if any. + * + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * If this function returns true, this intent will be broadcasted with + * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. * - * @return true if successful false if there was some error such as there is no connected SCO - * channel + * @return false if audio is not connected, or on error, true otherwise * @hide */ public boolean disconnectAudio() { @@ -867,22 +888,33 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Initiates a SCO channel connection with the headset (if connected). - * Also initiates a virtual voice call for Handsfree devices as many devices - * do not accept SCO audio without a call. - * This API allows the handsfree device to be used for routing non-cellular - * call audio. + * Initiates a SCO channel connection as a virtual voice call to the current active device + * Active handsfree device will be notified of incoming call and connected call. * - * @param device Remote Bluetooth Device - * @return true if successful, false if there was some error. + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * If this function returns true, this intent will be broadcasted with + * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}. + * + * <p> {@link #EXTRA_STATE} will transition from + * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when + * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} + * in case of failure to establish the audio connection. + * + * @return true if successful, false if one of the following case applies + * - SCO audio is not idle (connecting or connected) + * - virtual call has already started + * - there is no active device + * - a Telecom managed call is going on + * - binder is dead or Bluetooth is disabled or other error * @hide */ - public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) { + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean startScoUsingVirtualVoiceCall() { if (DBG) log("startScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; - if (service != null && isEnabled() && isValidDevice(device)) { + if (service != null && isEnabled()) { try { - return service.startScoUsingVirtualVoiceCall(device); + return service.startScoUsingVirtualVoiceCall(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -894,19 +926,24 @@ public final class BluetoothHeadset implements BluetoothProfile { } /** - * Terminates an ongoing SCO connection and the associated virtual - * call. + * Terminates an ongoing SCO connection and the associated virtual call. * - * @param device Remote Bluetooth Device - * @return true if successful, false if there was some error. + * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}. + * If this function returns true, this intent will be broadcasted with + * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. + * + * @return true if successful, false if one of the following case applies + * - virtual voice call is not started or has ended + * - binder is dead or Bluetooth is disabled or other error * @hide */ - public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) { + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean stopScoUsingVirtualVoiceCall() { if (DBG) log("stopScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; - if (service != null && isEnabled() && isValidDevice(device)) { + if (service != null && isEnabled()) { try { - return service.stopScoUsingVirtualVoiceCall(device); + return service.stopScoUsingVirtualVoiceCall(); } catch (RemoteException e) { Log.e(TAG, e.toString()); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 0b4b92107e42..619ec235913e 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -21,6 +21,7 @@ import android.app.job.JobParameters; import android.content.Context; import android.content.pm.ApplicationInfo; import android.server.ServerProtoEnums; +import android.service.batterystats.BatteryStatsServiceDumpHistoryProto; import android.service.batterystats.BatteryStatsServiceDumpProto; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -6030,51 +6031,51 @@ public abstract class BatteryStats implements Parcelable { } } - static void printBitDescriptions(PrintWriter pw, int oldval, int newval, HistoryTag wakelockTag, - BitDescription[] descriptions, boolean longNames) { + static void printBitDescriptions(StringBuilder sb, int oldval, int newval, + HistoryTag wakelockTag, BitDescription[] descriptions, boolean longNames) { int diff = oldval ^ newval; if (diff == 0) return; boolean didWake = false; for (int i=0; i<descriptions.length; i++) { BitDescription bd = descriptions[i]; if ((diff&bd.mask) != 0) { - pw.print(longNames ? " " : ","); + sb.append(longNames ? " " : ","); if (bd.shift < 0) { - pw.print((newval&bd.mask) != 0 ? "+" : "-"); - pw.print(longNames ? bd.name : bd.shortName); + sb.append((newval & bd.mask) != 0 ? "+" : "-"); + sb.append(longNames ? bd.name : bd.shortName); if (bd.mask == HistoryItem.STATE_WAKE_LOCK_FLAG && wakelockTag != null) { didWake = true; - pw.print("="); + sb.append("="); if (longNames) { - UserHandle.formatUid(pw, wakelockTag.uid); - pw.print(":\""); - pw.print(wakelockTag.string); - pw.print("\""); + UserHandle.formatUid(sb, wakelockTag.uid); + sb.append(":\""); + sb.append(wakelockTag.string); + sb.append("\""); } else { - pw.print(wakelockTag.poolIdx); + sb.append(wakelockTag.poolIdx); } } } else { - pw.print(longNames ? bd.name : bd.shortName); - pw.print("="); + sb.append(longNames ? bd.name : bd.shortName); + sb.append("="); int val = (newval&bd.mask)>>bd.shift; if (bd.values != null && val >= 0 && val < bd.values.length) { - pw.print(longNames? bd.values[val] : bd.shortValues[val]); + sb.append(longNames ? bd.values[val] : bd.shortValues[val]); } else { - pw.print(val); + sb.append(val); } } } } if (!didWake && wakelockTag != null) { - pw.print(longNames ? " wake_lock=" : ",w="); + sb.append(longNames ? " wake_lock=" : ",w="); if (longNames) { - UserHandle.formatUid(pw, wakelockTag.uid); - pw.print(":\""); - pw.print(wakelockTag.string); - pw.print("\""); + UserHandle.formatUid(sb, wakelockTag.uid); + sb.append(":\""); + sb.append(wakelockTag.string); + sb.append("\""); } else { - pw.print(wakelockTag.poolIdx); + sb.append(wakelockTag.poolIdx); } } } @@ -6108,339 +6109,360 @@ public abstract class BatteryStats implements Parcelable { public void printNextItem(PrintWriter pw, HistoryItem rec, long baseTime, boolean checkin, boolean verbose) { + pw.print(printNextItem(rec, baseTime, checkin, verbose)); + } + + /** Print the next history item to proto. */ + public void printNextItem(ProtoOutputStream proto, HistoryItem rec, long baseTime, + boolean verbose) { + String item = printNextItem(rec, baseTime, true, verbose); + for (String line : item.split("\n")) { + proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES, line); + } + } + + private String printNextItem(HistoryItem rec, long baseTime, boolean checkin, + boolean verbose) { + StringBuilder item = new StringBuilder(); if (!checkin) { - pw.print(" "); - TimeUtils.formatDuration(rec.time - baseTime, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); - pw.print(" ("); - pw.print(rec.numReadInts); - pw.print(") "); + item.append(" "); + TimeUtils.formatDuration( + rec.time - baseTime, item, TimeUtils.HUNDRED_DAY_FIELD_LEN); + item.append(" ("); + item.append(rec.numReadInts); + item.append(") "); } else { - pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); - pw.print(HISTORY_DATA); pw.print(','); + item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); + item.append(HISTORY_DATA); item.append(','); if (lastTime < 0) { - pw.print(rec.time - baseTime); + item.append(rec.time - baseTime); } else { - pw.print(rec.time - lastTime); + item.append(rec.time - lastTime); } lastTime = rec.time; } if (rec.cmd == HistoryItem.CMD_START) { if (checkin) { - pw.print(":"); + item.append(":"); } - pw.println("START"); + item.append("START\n"); reset(); } else if (rec.cmd == HistoryItem.CMD_CURRENT_TIME || rec.cmd == HistoryItem.CMD_RESET) { if (checkin) { - pw.print(":"); + item.append(":"); } if (rec.cmd == HistoryItem.CMD_RESET) { - pw.print("RESET:"); + item.append("RESET:"); reset(); } - pw.print("TIME:"); + item.append("TIME:"); if (checkin) { - pw.println(rec.currentTime); + item.append(rec.currentTime); + item.append("\n"); } else { - pw.print(" "); - pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", + item.append(" "); + item.append(DateFormat.format("yyyy-MM-dd-HH-mm-ss", rec.currentTime).toString()); + item.append("\n"); } } else if (rec.cmd == HistoryItem.CMD_SHUTDOWN) { if (checkin) { - pw.print(":"); + item.append(":"); } - pw.println("SHUTDOWN"); + item.append("SHUTDOWN\n"); } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { if (checkin) { - pw.print(":"); + item.append(":"); } - pw.println("*OVERFLOW*"); + item.append("*OVERFLOW*\n"); } else { if (!checkin) { - if (rec.batteryLevel < 10) pw.print("00"); - else if (rec.batteryLevel < 100) pw.print("0"); - pw.print(rec.batteryLevel); + if (rec.batteryLevel < 10) item.append("00"); + else if (rec.batteryLevel < 100) item.append("0"); + item.append(rec.batteryLevel); if (verbose) { - pw.print(" "); + item.append(" "); if (rec.states < 0) ; - else if (rec.states < 0x10) pw.print("0000000"); - else if (rec.states < 0x100) pw.print("000000"); - else if (rec.states < 0x1000) pw.print("00000"); - else if (rec.states < 0x10000) pw.print("0000"); - else if (rec.states < 0x100000) pw.print("000"); - else if (rec.states < 0x1000000) pw.print("00"); - else if (rec.states < 0x10000000) pw.print("0"); - pw.print(Integer.toHexString(rec.states)); + else if (rec.states < 0x10) item.append("0000000"); + else if (rec.states < 0x100) item.append("000000"); + else if (rec.states < 0x1000) item.append("00000"); + else if (rec.states < 0x10000) item.append("0000"); + else if (rec.states < 0x100000) item.append("000"); + else if (rec.states < 0x1000000) item.append("00"); + else if (rec.states < 0x10000000) item.append("0"); + item.append(Integer.toHexString(rec.states)); } } else { if (oldLevel != rec.batteryLevel) { oldLevel = rec.batteryLevel; - pw.print(",Bl="); pw.print(rec.batteryLevel); + item.append(",Bl="); item.append(rec.batteryLevel); } } if (oldStatus != rec.batteryStatus) { oldStatus = rec.batteryStatus; - pw.print(checkin ? ",Bs=" : " status="); + item.append(checkin ? ",Bs=" : " status="); switch (oldStatus) { case BatteryManager.BATTERY_STATUS_UNKNOWN: - pw.print(checkin ? "?" : "unknown"); + item.append(checkin ? "?" : "unknown"); break; case BatteryManager.BATTERY_STATUS_CHARGING: - pw.print(checkin ? "c" : "charging"); + item.append(checkin ? "c" : "charging"); break; case BatteryManager.BATTERY_STATUS_DISCHARGING: - pw.print(checkin ? "d" : "discharging"); + item.append(checkin ? "d" : "discharging"); break; case BatteryManager.BATTERY_STATUS_NOT_CHARGING: - pw.print(checkin ? "n" : "not-charging"); + item.append(checkin ? "n" : "not-charging"); break; case BatteryManager.BATTERY_STATUS_FULL: - pw.print(checkin ? "f" : "full"); + item.append(checkin ? "f" : "full"); break; default: - pw.print(oldStatus); + item.append(oldStatus); break; } } if (oldHealth != rec.batteryHealth) { oldHealth = rec.batteryHealth; - pw.print(checkin ? ",Bh=" : " health="); + item.append(checkin ? ",Bh=" : " health="); switch (oldHealth) { case BatteryManager.BATTERY_HEALTH_UNKNOWN: - pw.print(checkin ? "?" : "unknown"); + item.append(checkin ? "?" : "unknown"); break; case BatteryManager.BATTERY_HEALTH_GOOD: - pw.print(checkin ? "g" : "good"); + item.append(checkin ? "g" : "good"); break; case BatteryManager.BATTERY_HEALTH_OVERHEAT: - pw.print(checkin ? "h" : "overheat"); + item.append(checkin ? "h" : "overheat"); break; case BatteryManager.BATTERY_HEALTH_DEAD: - pw.print(checkin ? "d" : "dead"); + item.append(checkin ? "d" : "dead"); break; case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: - pw.print(checkin ? "v" : "over-voltage"); + item.append(checkin ? "v" : "over-voltage"); break; case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: - pw.print(checkin ? "f" : "failure"); + item.append(checkin ? "f" : "failure"); break; case BatteryManager.BATTERY_HEALTH_COLD: - pw.print(checkin ? "c" : "cold"); + item.append(checkin ? "c" : "cold"); break; default: - pw.print(oldHealth); + item.append(oldHealth); break; } } if (oldPlug != rec.batteryPlugType) { oldPlug = rec.batteryPlugType; - pw.print(checkin ? ",Bp=" : " plug="); + item.append(checkin ? ",Bp=" : " plug="); switch (oldPlug) { case 0: - pw.print(checkin ? "n" : "none"); + item.append(checkin ? "n" : "none"); break; case BatteryManager.BATTERY_PLUGGED_AC: - pw.print(checkin ? "a" : "ac"); + item.append(checkin ? "a" : "ac"); break; case BatteryManager.BATTERY_PLUGGED_USB: - pw.print(checkin ? "u" : "usb"); + item.append(checkin ? "u" : "usb"); break; case BatteryManager.BATTERY_PLUGGED_WIRELESS: - pw.print(checkin ? "w" : "wireless"); + item.append(checkin ? "w" : "wireless"); break; default: - pw.print(oldPlug); + item.append(oldPlug); break; } } if (oldTemp != rec.batteryTemperature) { oldTemp = rec.batteryTemperature; - pw.print(checkin ? ",Bt=" : " temp="); - pw.print(oldTemp); + item.append(checkin ? ",Bt=" : " temp="); + item.append(oldTemp); } if (oldVolt != rec.batteryVoltage) { oldVolt = rec.batteryVoltage; - pw.print(checkin ? ",Bv=" : " volt="); - pw.print(oldVolt); + item.append(checkin ? ",Bv=" : " volt="); + item.append(oldVolt); } final int chargeMAh = rec.batteryChargeUAh / 1000; if (oldChargeMAh != chargeMAh) { oldChargeMAh = chargeMAh; - pw.print(checkin ? ",Bcc=" : " charge="); - pw.print(oldChargeMAh); + item.append(checkin ? ",Bcc=" : " charge="); + item.append(oldChargeMAh); } - printBitDescriptions(pw, oldState, rec.states, rec.wakelockTag, + printBitDescriptions(item, oldState, rec.states, rec.wakelockTag, HISTORY_STATE_DESCRIPTIONS, !checkin); - printBitDescriptions(pw, oldState2, rec.states2, null, + printBitDescriptions(item, oldState2, rec.states2, null, HISTORY_STATE2_DESCRIPTIONS, !checkin); if (rec.wakeReasonTag != null) { if (checkin) { - pw.print(",wr="); - pw.print(rec.wakeReasonTag.poolIdx); + item.append(",wr="); + item.append(rec.wakeReasonTag.poolIdx); } else { - pw.print(" wake_reason="); - pw.print(rec.wakeReasonTag.uid); - pw.print(":\""); - pw.print(rec.wakeReasonTag.string); - pw.print("\""); + item.append(" wake_reason="); + item.append(rec.wakeReasonTag.uid); + item.append(":\""); + item.append(rec.wakeReasonTag.string); + item.append("\""); } } if (rec.eventCode != HistoryItem.EVENT_NONE) { - pw.print(checkin ? "," : " "); + item.append(checkin ? "," : " "); if ((rec.eventCode&HistoryItem.EVENT_FLAG_START) != 0) { - pw.print("+"); + item.append("+"); } else if ((rec.eventCode&HistoryItem.EVENT_FLAG_FINISH) != 0) { - pw.print("-"); + item.append("-"); } String[] eventNames = checkin ? HISTORY_EVENT_CHECKIN_NAMES : HISTORY_EVENT_NAMES; int idx = rec.eventCode & ~(HistoryItem.EVENT_FLAG_START | HistoryItem.EVENT_FLAG_FINISH); if (idx >= 0 && idx < eventNames.length) { - pw.print(eventNames[idx]); + item.append(eventNames[idx]); } else { - pw.print(checkin ? "Ev" : "event"); - pw.print(idx); + item.append(checkin ? "Ev" : "event"); + item.append(idx); } - pw.print("="); + item.append("="); if (checkin) { - pw.print(rec.eventTag.poolIdx); + item.append(rec.eventTag.poolIdx); } else { - pw.append(HISTORY_EVENT_INT_FORMATTERS[idx] + item.append(HISTORY_EVENT_INT_FORMATTERS[idx] .applyAsString(rec.eventTag.uid)); - pw.print(":\""); - pw.print(rec.eventTag.string); - pw.print("\""); + item.append(":\""); + item.append(rec.eventTag.string); + item.append("\""); } } - pw.println(); + item.append("\n"); if (rec.stepDetails != null) { if (!checkin) { - pw.print(" Details: cpu="); - pw.print(rec.stepDetails.userTime); - pw.print("u+"); - pw.print(rec.stepDetails.systemTime); - pw.print("s"); + item.append(" Details: cpu="); + item.append(rec.stepDetails.userTime); + item.append("u+"); + item.append(rec.stepDetails.systemTime); + item.append("s"); if (rec.stepDetails.appCpuUid1 >= 0) { - pw.print(" ("); - printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid1, + item.append(" ("); + printStepCpuUidDetails(item, rec.stepDetails.appCpuUid1, rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1); if (rec.stepDetails.appCpuUid2 >= 0) { - pw.print(", "); - printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid2, + item.append(", "); + printStepCpuUidDetails(item, rec.stepDetails.appCpuUid2, rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2); } if (rec.stepDetails.appCpuUid3 >= 0) { - pw.print(", "); - printStepCpuUidDetails(pw, rec.stepDetails.appCpuUid3, + item.append(", "); + printStepCpuUidDetails(item, rec.stepDetails.appCpuUid3, rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3); } - pw.print(')'); + item.append(')'); } - pw.println(); - pw.print(" /proc/stat="); - pw.print(rec.stepDetails.statUserTime); - pw.print(" usr, "); - pw.print(rec.stepDetails.statSystemTime); - pw.print(" sys, "); - pw.print(rec.stepDetails.statIOWaitTime); - pw.print(" io, "); - pw.print(rec.stepDetails.statIrqTime); - pw.print(" irq, "); - pw.print(rec.stepDetails.statSoftIrqTime); - pw.print(" sirq, "); - pw.print(rec.stepDetails.statIdlTime); - pw.print(" idle"); + item.append("\n"); + item.append(" /proc/stat="); + item.append(rec.stepDetails.statUserTime); + item.append(" usr, "); + item.append(rec.stepDetails.statSystemTime); + item.append(" sys, "); + item.append(rec.stepDetails.statIOWaitTime); + item.append(" io, "); + item.append(rec.stepDetails.statIrqTime); + item.append(" irq, "); + item.append(rec.stepDetails.statSoftIrqTime); + item.append(" sirq, "); + item.append(rec.stepDetails.statIdlTime); + item.append(" idle"); int totalRun = rec.stepDetails.statUserTime + rec.stepDetails.statSystemTime + rec.stepDetails.statIOWaitTime + rec.stepDetails.statIrqTime + rec.stepDetails.statSoftIrqTime; int total = totalRun + rec.stepDetails.statIdlTime; if (total > 0) { - pw.print(" ("); + item.append(" ("); float perc = ((float)totalRun) / ((float)total) * 100; - pw.print(String.format("%.1f%%", perc)); - pw.print(" of "); + item.append(String.format("%.1f%%", perc)); + item.append(" of "); StringBuilder sb = new StringBuilder(64); formatTimeMsNoSpace(sb, total*10); - pw.print(sb); - pw.print(")"); + item.append(sb); + item.append(")"); } - pw.print(", PlatformIdleStat "); - pw.print(rec.stepDetails.statPlatformIdleState); - pw.println(); + item.append(", PlatformIdleStat "); + item.append(rec.stepDetails.statPlatformIdleState); + item.append("\n"); - pw.print(", SubsystemPowerState "); - pw.print(rec.stepDetails.statSubsystemPowerState); - pw.println(); + item.append(", SubsystemPowerState "); + item.append(rec.stepDetails.statSubsystemPowerState); + item.append("\n"); } else { - pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); - pw.print(HISTORY_DATA); pw.print(",0,Dcpu="); - pw.print(rec.stepDetails.userTime); - pw.print(":"); - pw.print(rec.stepDetails.systemTime); + item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); + item.append(HISTORY_DATA); item.append(",0,Dcpu="); + item.append(rec.stepDetails.userTime); + item.append(":"); + item.append(rec.stepDetails.systemTime); if (rec.stepDetails.appCpuUid1 >= 0) { - printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid1, + printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid1, rec.stepDetails.appCpuUTime1, rec.stepDetails.appCpuSTime1); if (rec.stepDetails.appCpuUid2 >= 0) { - printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid2, + printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid2, rec.stepDetails.appCpuUTime2, rec.stepDetails.appCpuSTime2); } if (rec.stepDetails.appCpuUid3 >= 0) { - printStepCpuUidCheckinDetails(pw, rec.stepDetails.appCpuUid3, + printStepCpuUidCheckinDetails(item, rec.stepDetails.appCpuUid3, rec.stepDetails.appCpuUTime3, rec.stepDetails.appCpuSTime3); } } - pw.println(); - pw.print(BATTERY_STATS_CHECKIN_VERSION); pw.print(','); - pw.print(HISTORY_DATA); pw.print(",0,Dpst="); - pw.print(rec.stepDetails.statUserTime); - pw.print(','); - pw.print(rec.stepDetails.statSystemTime); - pw.print(','); - pw.print(rec.stepDetails.statIOWaitTime); - pw.print(','); - pw.print(rec.stepDetails.statIrqTime); - pw.print(','); - pw.print(rec.stepDetails.statSoftIrqTime); - pw.print(','); - pw.print(rec.stepDetails.statIdlTime); - pw.print(','); + item.append("\n"); + item.append(BATTERY_STATS_CHECKIN_VERSION); item.append(','); + item.append(HISTORY_DATA); item.append(",0,Dpst="); + item.append(rec.stepDetails.statUserTime); + item.append(','); + item.append(rec.stepDetails.statSystemTime); + item.append(','); + item.append(rec.stepDetails.statIOWaitTime); + item.append(','); + item.append(rec.stepDetails.statIrqTime); + item.append(','); + item.append(rec.stepDetails.statSoftIrqTime); + item.append(','); + item.append(rec.stepDetails.statIdlTime); + item.append(','); if (rec.stepDetails.statPlatformIdleState != null) { - pw.print(rec.stepDetails.statPlatformIdleState); + item.append(rec.stepDetails.statPlatformIdleState); if (rec.stepDetails.statSubsystemPowerState != null) { - pw.print(','); + item.append(','); } } if (rec.stepDetails.statSubsystemPowerState != null) { - pw.print(rec.stepDetails.statSubsystemPowerState); + item.append(rec.stepDetails.statSubsystemPowerState); } - pw.println(); + item.append("\n"); } } oldState = rec.states; oldState2 = rec.states2; } + + return item.toString(); } - private void printStepCpuUidDetails(PrintWriter pw, int uid, int utime, int stime) { - UserHandle.formatUid(pw, uid); - pw.print("="); - pw.print(utime); - pw.print("u+"); - pw.print(stime); - pw.print("s"); + private void printStepCpuUidDetails(StringBuilder sb, int uid, int utime, int stime) { + UserHandle.formatUid(sb, uid); + sb.append("="); + sb.append(utime); + sb.append("u+"); + sb.append(stime); + sb.append("s"); } - private void printStepCpuUidCheckinDetails(PrintWriter pw, int uid, int utime, int stime) { - pw.print('/'); - pw.print(uid); - pw.print(":"); - pw.print(utime); - pw.print(":"); - pw.print(stime); + private void printStepCpuUidCheckinDetails(StringBuilder sb, int uid, int utime, + int stime) { + sb.append('/'); + sb.append(uid); + sb.append(":"); + sb.append(utime); + sb.append(":"); + sb.append(stime); } } @@ -7046,21 +7068,30 @@ public abstract class BatteryStats implements Parcelable { } } - /** Dump #STATS_SINCE_CHARGED batterystats data to a proto. @hide */ + /** + * Dump #STATS_SINCE_CHARGED batterystats data to a proto. If the flags include + * DUMP_INCLUDE_HISTORY or DUMP_HISTORY_ONLY, only the history will be dumped. + * @hide + */ public void dumpProtoLocked(Context context, FileDescriptor fd, List<ApplicationInfo> apps, - int flags) { + int flags, long histStart) { final ProtoOutputStream proto = new ProtoOutputStream(fd); - final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS); prepareForDumpLocked(); + if ((flags & (DUMP_INCLUDE_HISTORY | DUMP_HISTORY_ONLY)) != 0) { + dumpProtoHistoryLocked(proto, flags, histStart); + proto.flush(); + return; + } + + final long bToken = proto.start(BatteryStatsServiceDumpProto.BATTERYSTATS); + proto.write(BatteryStatsProto.REPORT_VERSION, CHECKIN_VERSION); proto.write(BatteryStatsProto.PARCEL_VERSION, getParcelVersion()); proto.write(BatteryStatsProto.START_PLATFORM_VERSION, getStartPlatformVersion()); proto.write(BatteryStatsProto.END_PLATFORM_VERSION, getEndPlatformVersion()); - // History intentionally not included in proto dump. - - if ((flags & (DUMP_HISTORY_ONLY | DUMP_DAILY_ONLY)) == 0) { + if ((flags & DUMP_DAILY_ONLY) == 0) { final BatteryStatsHelper helper = new BatteryStatsHelper(context, false, (flags & DUMP_DEVICE_WIFI_ONLY) != 0); helper.create(this); @@ -7530,6 +7561,108 @@ public abstract class BatteryStats implements Parcelable { } } + private void dumpProtoHistoryLocked(ProtoOutputStream proto, int flags, long histStart) { + if (!startIteratingHistoryLocked()) { + return; + } + + proto.write(BatteryStatsServiceDumpHistoryProto.REPORT_VERSION, CHECKIN_VERSION); + proto.write(BatteryStatsServiceDumpHistoryProto.PARCEL_VERSION, getParcelVersion()); + proto.write(BatteryStatsServiceDumpHistoryProto.START_PLATFORM_VERSION, + getStartPlatformVersion()); + proto.write(BatteryStatsServiceDumpHistoryProto.END_PLATFORM_VERSION, + getEndPlatformVersion()); + try { + long token; + // History string pool (HISTORY_STRING_POOL) + for (int i = 0; i < getHistoryStringPoolSize(); ++i) { + token = proto.start(BatteryStatsServiceDumpHistoryProto.KEYS); + proto.write(BatteryStatsServiceDumpHistoryProto.Key.INDEX, i); + proto.write(BatteryStatsServiceDumpHistoryProto.Key.UID, getHistoryTagPoolUid(i)); + proto.write(BatteryStatsServiceDumpHistoryProto.Key.TAG, + getHistoryTagPoolString(i)); + proto.end(token); + } + + // History data (HISTORY_DATA) + final HistoryPrinter hprinter = new HistoryPrinter(); + final HistoryItem rec = new HistoryItem(); + long lastTime = -1; + long baseTime = -1; + boolean printed = false; + HistoryEventTracker tracker = null; + while (getNextHistoryLocked(rec)) { + lastTime = rec.time; + if (baseTime < 0) { + baseTime = lastTime; + } + if (rec.time >= histStart) { + if (histStart >= 0 && !printed) { + if (rec.cmd == HistoryItem.CMD_CURRENT_TIME + || rec.cmd == HistoryItem.CMD_RESET + || rec.cmd == HistoryItem.CMD_START + || rec.cmd == HistoryItem.CMD_SHUTDOWN) { + printed = true; + hprinter.printNextItem(proto, rec, baseTime, + (flags & DUMP_VERBOSE) != 0); + rec.cmd = HistoryItem.CMD_UPDATE; + } else if (rec.currentTime != 0) { + printed = true; + byte cmd = rec.cmd; + rec.cmd = HistoryItem.CMD_CURRENT_TIME; + hprinter.printNextItem(proto, rec, baseTime, + (flags & DUMP_VERBOSE) != 0); + rec.cmd = cmd; + } + if (tracker != null) { + if (rec.cmd != HistoryItem.CMD_UPDATE) { + hprinter.printNextItem(proto, rec, baseTime, + (flags & DUMP_VERBOSE) != 0); + rec.cmd = HistoryItem.CMD_UPDATE; + } + int oldEventCode = rec.eventCode; + HistoryTag oldEventTag = rec.eventTag; + rec.eventTag = new HistoryTag(); + for (int i = 0; i < HistoryItem.EVENT_COUNT; i++) { + HashMap<String, SparseIntArray> active = + tracker.getStateForEvent(i); + if (active == null) { + continue; + } + for (HashMap.Entry<String, SparseIntArray> ent + : active.entrySet()) { + SparseIntArray uids = ent.getValue(); + for (int j = 0; j < uids.size(); j++) { + rec.eventCode = i; + rec.eventTag.string = ent.getKey(); + rec.eventTag.uid = uids.keyAt(j); + rec.eventTag.poolIdx = uids.valueAt(j); + hprinter.printNextItem(proto, rec, baseTime, + (flags & DUMP_VERBOSE) != 0); + rec.wakeReasonTag = null; + rec.wakelockTag = null; + } + } + } + rec.eventCode = oldEventCode; + rec.eventTag = oldEventTag; + tracker = null; + } + } + hprinter.printNextItem(proto, rec, baseTime, + (flags & DUMP_VERBOSE) != 0); + } + } + if (histStart >= 0) { + commitCurrentHistoryBatchLocked(); + proto.write(BatteryStatsServiceDumpHistoryProto.CSV_LINES, + "NEXT: " + (lastTime + 1)); + } + } finally { + finishIteratingHistoryLocked(); + } + } + private void dumpProtoSystemLocked(ProtoOutputStream proto, BatteryStatsHelper helper) { final long sToken = proto.start(BatteryStatsProto.SYSTEM); final long rawUptimeUs = SystemClock.uptimeMillis() * 1000; diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl index 8c256be20479..124f2072933e 100644 --- a/core/java/android/os/IStatsManager.aidl +++ b/core/java/android/os/IStatsManager.aidl @@ -152,4 +152,10 @@ interface IStatsManager { * Requires Manifest.permission.DUMP. */ void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName); + + /** + * Apps can send an atom via this application breadcrumb with the specified label and state for + * this label. This allows building custom metrics and predicates. + */ + void sendAppBreadcrumbAtom(int label, int state); } diff --git a/core/java/android/service/notification/ScheduleCalendar.java b/core/java/android/service/notification/ScheduleCalendar.java index 01287104f9b2..8b7946ce7651 100644 --- a/core/java/android/service/notification/ScheduleCalendar.java +++ b/core/java/android/service/notification/ScheduleCalendar.java @@ -135,6 +135,24 @@ public class ScheduleCalendar { } /** + * @param alarm milliseconds since Epoch + * @param now milliseconds since Epoch + * @return true if alarm and now is within the schedule, else false + */ + public boolean isAlarmInSchedule(long alarm, long now) { + if (mSchedule == null || mDays.size() == 0) return false; + final long start = getTime(alarm, mSchedule.startHour, mSchedule.startMinute); + long end = getTime(alarm, mSchedule.endHour, mSchedule.endMinute); + if (end <= start) { + end = addDays(end, 1); + } + return (isInSchedule(-1, alarm, start, end) + && isInSchedule(-1, now, start, end)) + || (isInSchedule(0, alarm, start, end) + && isInSchedule(0, now, start, end)); + } + + /** * @param time milliseconds since Epoch * @return true if should exit at time for next alarm, else false */ @@ -145,7 +163,7 @@ public class ScheduleCalendar { return mSchedule.exitAtAlarm && mSchedule.nextAlarm != 0 && time >= mSchedule.nextAlarm - && isInSchedule(mSchedule.nextAlarm); + && isAlarmInSchedule(mSchedule.nextAlarm, time); } private boolean isInSchedule(int daysOffset, long time, long start, long end) { diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java index e8b4197259ce..e3de30713652 100644 --- a/core/java/android/util/StatsLog.java +++ b/core/java/android/util/StatsLog.java @@ -16,59 +16,101 @@ package android.util; -import android.os.Process; +import android.os.IStatsManager; +import android.os.RemoteException; +import android.os.ServiceManager; /** * StatsLog provides an API for developers to send events to statsd. The events can be used to * define custom metrics inside statsd. */ public final class StatsLog extends StatsLogInternal { - private static final String TAG = "StatsManager"; + private static final String TAG = "StatsLog"; + private static final boolean DEBUG = false; + + private static IStatsManager sService; private StatsLog() {} /** * Logs a start event. * - * @param label developer-chosen label that is from [0, 16). + * @param label developer-chosen label. * @return True if the log request was sent to statsd. */ public static boolean logStart(int label) { - if (label >= 0 && label < 16) { - StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(), - label, APP_BREADCRUMB_REPORTED__STATE__START); - return true; + synchronized (StatsLog.class) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging start"); + return false; + } + service.sendAppBreadcrumbAtom(label, + StatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging start"); + return false; + } } - return false; } /** * Logs a stop event. * - * @param label developer-chosen label that is from [0, 16). + * @param label developer-chosen label. * @return True if the log request was sent to statsd. */ public static boolean logStop(int label) { - if (label >= 0 && label < 16) { - StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(), - label, APP_BREADCRUMB_REPORTED__STATE__STOP); - return true; + synchronized (StatsLog.class) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging stop"); + return false; + } + service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging stop"); + return false; + } } - return false; } /** * Logs an event that does not represent a start or stop boundary. * - * @param label developer-chosen label that is from [0, 16). + * @param label developer-chosen label. * @return True if the log request was sent to statsd. */ public static boolean logEvent(int label) { - if (label >= 0 && label < 16) { - StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(), label, - APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); - return true; + synchronized (StatsLog.class) { + try { + IStatsManager service = getIStatsManagerLocked(); + if (service == null) { + if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging event"); + return false; + } + service.sendAppBreadcrumbAtom( + label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging event"); + return false; + } + } + } + + private static IStatsManager getIStatsManagerLocked() throws RemoteException { + if (sService != null) { + return sService; } - return false; + sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats")); + return sService; } } diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 84ae20b92f3c..05b613c3c7c7 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -240,6 +240,14 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, StringBuilder builder, int fieldLen) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, fieldLen); + builder.append(sFormatStr, 0, len); + } + } + + /** @hide Just for debugging; not internationalized. */ public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { synchronized (sFormatSync) { int len = formatDurationLocked(duration, fieldLen); diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 88300dbda1c4..4c7dc116d167 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1132,8 +1132,7 @@ public final class AutofillManager { if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) { if (sDebug) Log.d(TAG, "triggering commit by click of " + id); commitLocked(); - mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED, - mContext.getPackageName()); + mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED)); } } } @@ -1893,14 +1892,19 @@ public final class AutofillManager { } } - final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED) - .setPackageName(mContext.getPackageName()) + mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied); - mMetricsLogger.write(log); + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied)); } } + private LogMaker newLog(int category) { + return new LogMaker(category) + .setPackageName(mContext.getPackageName()) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, + isCompatibilityModeEnabledLocked() ? 1 : 0); + } + /** * Set the tracked views. * diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 15b2718bea42..b9a8864600c7 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -147,7 +147,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou setAvatar(sender.getIcon()); } mAvatarView.setVisibility(VISIBLE); - mSenderName.setVisibility(VISIBLE); + mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE); mTextColor = getNormalTextColor(); mSendingTextColor = calculateSendingTextColor(); } diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java index af9aae3c318d..538ea11b6aa9 100644 --- a/core/java/com/android/internal/widget/MessagingLayout.java +++ b/core/java/com/android/internal/widget/MessagingLayout.java @@ -436,10 +436,29 @@ public class MessagingLayout extends FrameLayout { } private void updateHistoricMessageVisibility() { - for (int i = 0; i < mHistoricMessages.size(); i++) { + int numHistoric = mHistoricMessages.size(); + for (int i = 0; i < numHistoric; i++) { MessagingMessage existing = mHistoricMessages.get(i); existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE); } + int numGroups = mGroups.size(); + for (int i = 0; i < numGroups; i++) { + MessagingGroup group = mGroups.get(i); + int visibleChildren = 0; + List<MessagingMessage> messages = group.getMessages(); + int numGroupMessages = messages.size(); + for (int j = 0; j < numGroupMessages; j++) { + MessagingMessage message = messages.get(j); + if (message.getVisibility() != GONE) { + visibleChildren++; + } + } + if (visibleChildren > 0 && group.getVisibility() == GONE) { + group.setVisibility(VISIBLE); + } else if (visibleChildren == 0 && group.getVisibility() != GONE) { + group.setVisibility(GONE); + } + } } @Override diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java index d2b670fa2d66..ffcb50309feb 100644 --- a/core/java/com/android/internal/widget/MessagingMessage.java +++ b/core/java/com/android/internal/widget/MessagingMessage.java @@ -145,4 +145,6 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild { MessagingMessageState getState(); void setVisibility(int visibility); + + int getVisibility(); } diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto index 64e1239f36f6..3aea3a767f46 100644 --- a/core/proto/android/os/incident.proto +++ b/core/proto/android/os/incident.proto @@ -18,6 +18,7 @@ syntax = "proto2"; option java_multiple_files = true; import "frameworks/base/core/proto/android/os/backtrace.proto"; +import "frameworks/base/core/proto/android/os/batterystats.proto"; import "frameworks/base/core/proto/android/os/batterytype.proto"; import "frameworks/base/core/proto/android/os/cpufreq.proto"; import "frameworks/base/core/proto/android/os/cpuinfo.proto"; @@ -289,6 +290,14 @@ message IncidentProto { (section).args = "usb --proto" ]; + // The history can be large and may cause issues in consumers, so put the + // history in a separate section to compensate. + optional android.service.batterystats.BatteryStatsServiceDumpHistoryProto battery_history = 3022 [ + (section).type = SECTION_DUMPSYS, + (section).args = "batterystats --proto --history", + (section).userdebug_and_eng_only = true + ]; + // Reserved for OEMs. extensions 50000 to 100000; } diff --git a/core/proto/android/service/batterystats.proto b/core/proto/android/service/batterystats.proto index 5586263d0fac..25b47d3f88f2 100644 --- a/core/proto/android/service/batterystats.proto +++ b/core/proto/android/service/batterystats.proto @@ -23,8 +23,34 @@ option java_outer_classname = "BatteryStatsServiceProto"; import "frameworks/base/core/proto/android/os/batterystats.proto"; import "frameworks/base/libs/incident/proto/android/privacy.proto"; +// Dump of batterystats aggregate data (dumpsys batterystats --proto). message BatteryStatsServiceDumpProto { option (android.msg_privacy).dest = DEST_AUTOMATIC; optional android.os.BatteryStatsProto batterystats = 1; } + +// Dump of batterystats history data (dumpsys batterystats --proto --history). +message BatteryStatsServiceDumpHistoryProto { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 report_version = 1; + optional int64 parcel_version = 2; + optional string start_platform_version = 3; + optional string end_platform_version = 4; + + // HistoryStringPool data + message Key { + option (android.msg_privacy).dest = DEST_AUTOMATIC; + + optional int32 index = 1; + // Not valid for all keys. + optional int32 uid = 2; + optional string tag = 3; + } + repeated Key keys = 5; + + // Dump of battery history in csv format (equivalent of + // 'batterystats -c --history', with the hsp lines extracted). + repeated string csv_lines = 6; +} diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index b3a3f455bac0..298b699055a5 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -7367,222 +7367,238 @@ void ResTable::print(bool inclValues) const printf("\n"); } - int packageId = pg->id; - size_t pkgCount = pg->packages.size(); - for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { - const Package* pkg = pg->packages[pkgIndex]; - // Use a package's real ID, since the ID may have been assigned - // if this package is a shared library. - packageId = pkg->package->id; - char16_t tmpName[sizeof(pkg->package->name)/sizeof(pkg->package->name[0])]; - strcpy16_dtoh(tmpName, pkg->package->name, sizeof(pkg->package->name)/sizeof(pkg->package->name[0])); - printf(" Package %d id=0x%02x name=%s\n", (int)pkgIndex, - pkg->package->id, String8(tmpName).string()); - } - - for (size_t typeIndex=0; typeIndex < pg->types.size(); typeIndex++) { - const TypeList& typeList = pg->types[typeIndex]; - if (typeList.isEmpty()) { - continue; - } - const Type* typeConfigs = typeList[0]; - const size_t NTC = typeConfigs->configs.size(); - printf(" type %d configCount=%d entryCount=%d\n", - (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); - if (typeConfigs->typeSpecFlags != NULL) { - for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { - uint32_t resID = (0xff000000 & ((packageId)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryIndex)); - // Since we are creating resID without actually - // iterating over them, we have no idea which is a - // dynamic reference. We must check. - if (packageId == 0) { - pg->dynamicRefTable.lookupResourceId(&resID); - } - - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); - } else { - name8 = String8(resName.name, resName.nameLen); - } - printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", - resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string(), - dtohl(typeConfigs->typeSpecFlags[entryIndex])); - } else { - printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); - } - } - } - for (size_t configIndex=0; configIndex<NTC; configIndex++) { - const ResTable_type* type = typeConfigs->configs[configIndex]; - if ((((uint64_t)type)&0x3) != 0) { - printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); - continue; - } - - // Always copy the config, as fields get added and we need to - // set the defaults. - ResTable_config thisConfig; - thisConfig.copyFromDtoH(type->config); - - String8 configStr = thisConfig.toString(); - printf(" config %s", configStr.size() > 0 - ? configStr.string() : "(default)"); - if (type->flags != 0u) { - printf(" flags=0x%02x", type->flags); - if (type->flags & ResTable_type::FLAG_SPARSE) { - printf(" [sparse]"); - } - } - - printf(":\n"); + // Determine the number of resource splits for the resource types in this package. + // It needs to be done outside of the loop below so all of the information for a + // is displayed in a single block. Otherwise, a resource split's resource types + // would be interleaved with other splits. + size_t splitCount = 0; + for (size_t typeIndex = 0; typeIndex < pg->types.size(); typeIndex++) { + splitCount = max(splitCount, pg->types[typeIndex].size()); + } - size_t entryCount = dtohl(type->entryCount); - uint32_t entriesStart = dtohl(type->entriesStart); - if ((entriesStart&0x3) != 0) { - printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", entriesStart); - continue; - } - uint32_t typeSize = dtohl(type->header.size); - if ((typeSize&0x3) != 0) { - printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); + int packageId = pg->id; + for (size_t splitIndex = 0; splitIndex < splitCount; splitIndex++) { + size_t pkgCount = pg->packages.size(); + for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + // Use a package's real ID, since the ID may have been assigned + // if this package is a shared library. + packageId = pkg->package->id; + char16_t tmpName[sizeof(pkg->package->name)/sizeof(pkg->package->name[0])]; + strcpy16_dtoh(tmpName, pkg->package->name, + sizeof(pkg->package->name)/sizeof(pkg->package->name[0])); + printf(" Package %d id=0x%02x name=%s\n", (int)pkgIndex, + pkg->package->id, String8(tmpName).string()); + } + + for (size_t typeIndex = 0; typeIndex < pg->types.size(); typeIndex++) { + const TypeList& typeList = pg->types[typeIndex]; + if (splitIndex >= typeList.size() || typeList.isEmpty()) { + // Only dump if the split exists and contains entries for this type continue; } - - const uint32_t* const eindex = (const uint32_t*) - (((const uint8_t*)type) + dtohs(type->header.headerSize)); - for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { - size_t entryId; - uint32_t thisOffset; - if (type->flags & ResTable_type::FLAG_SPARSE) { - const ResTable_sparseTypeEntry* entry = - reinterpret_cast<const ResTable_sparseTypeEntry*>( - eindex + entryIndex); - entryId = dtohs(entry->idx); - // Offsets are encoded as divided by 4. - thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; - } else { - entryId = entryIndex; - thisOffset = dtohl(eindex[entryIndex]); - if (thisOffset == ResTable_type::NO_ENTRY) { - continue; + const Type* typeConfigs = typeList[splitIndex]; + const size_t NTC = typeConfigs->configs.size(); + printf(" type %d configCount=%d entryCount=%d\n", + (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); + if (typeConfigs->typeSpecFlags != NULL) { + for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount; entryIndex++) { + uint32_t resID = (0xff000000 & ((packageId)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + // Since we are creating resID without actually + // iterating over them, we have no idea which is a + // dynamic reference. We must check. + if (packageId == 0) { + pg->dynamicRefTable.lookupResourceId(&resID); } - } - uint32_t resID = (0xff000000 & ((packageId)<<24)) - | (0x00ff0000 & ((typeIndex+1)<<16)) - | (0x0000ffff & (entryId)); - if (packageId == 0) { - pg->dynamicRefTable.lookupResourceId(&resID); - } - resource_name resName; - if (this->getResourceName(resID, true, &resName)) { - String8 type8; - String8 name8; - if (resName.type8 != NULL) { - type8 = String8(resName.type8, resName.typeLen); - } else { - type8 = String8(resName.type, resName.typeLen); - } - if (resName.name8 != NULL) { - name8 = String8(resName.name8, resName.nameLen); + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); + } + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); + } else { + name8 = String8(resName.name, resName.nameLen); + } + printf(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", + resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string(), + dtohl(typeConfigs->typeSpecFlags[entryIndex])); } else { - name8 = String8(resName.name, resName.nameLen); + printf(" INVALID TYPE CONFIG FOR RESOURCE 0x%08x\n", resID); } - printf(" resource 0x%08x %s:%s/%s: ", resID, - CHAR16_TO_CSTR(resName.package, resName.packageLen), - type8.string(), name8.string()); - } else { - printf(" INVALID RESOURCE 0x%08x: ", resID); - } - if ((thisOffset&0x3) != 0) { - printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); - continue; } - if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { - printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", - entriesStart, thisOffset, typeSize); + } + for (size_t configIndex=0; configIndex<NTC; configIndex++) { + const ResTable_type* type = typeConfigs->configs[configIndex]; + if ((((uint64_t)type)&0x3) != 0) { + printf(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); continue; } - const ResTable_entry* ent = (const ResTable_entry*) - (((const uint8_t*)type) + entriesStart + thisOffset); - if (((entriesStart + thisOffset)&0x3) != 0) { - printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", - (entriesStart + thisOffset)); - continue; + // Always copy the config, as fields get added and we need to + // set the defaults. + ResTable_config thisConfig; + thisConfig.copyFromDtoH(type->config); + + String8 configStr = thisConfig.toString(); + printf(" config %s", configStr.size() > 0 + ? configStr.string() : "(default)"); + if (type->flags != 0u) { + printf(" flags=0x%02x", type->flags); + if (type->flags & ResTable_type::FLAG_SPARSE) { + printf(" [sparse]"); + } } - uintptr_t esize = dtohs(ent->size); - if ((esize&0x3) != 0) { - printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); + printf(":\n"); + + size_t entryCount = dtohl(type->entryCount); + uint32_t entriesStart = dtohl(type->entriesStart); + if ((entriesStart&0x3) != 0) { + printf(" NON-INTEGER ResTable_type entriesStart OFFSET: 0x%x\n", + entriesStart); continue; } - if ((thisOffset+esize) > typeSize) { - printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", - entriesStart, thisOffset, (void *)esize, typeSize); + uint32_t typeSize = dtohl(type->header.size); + if ((typeSize&0x3) != 0) { + printf(" NON-INTEGER ResTable_type header.size: 0x%x\n", typeSize); continue; } - const Res_value* valuePtr = NULL; - const ResTable_map_entry* bagPtr = NULL; - Res_value value; - if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { - printf("<bag>"); - bagPtr = (const ResTable_map_entry*)ent; - } else { - valuePtr = (const Res_value*) - (((const uint8_t*)ent) + esize); - value.copyFrom_dtoh(*valuePtr); - printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", - (int)value.dataType, (int)value.data, - (int)value.size, (int)value.res0); - } + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)type) + dtohs(type->header.headerSize)); + for (size_t entryIndex=0; entryIndex<entryCount; entryIndex++) { + size_t entryId; + uint32_t thisOffset; + if (type->flags & ResTable_type::FLAG_SPARSE) { + const ResTable_sparseTypeEntry* entry = + reinterpret_cast<const ResTable_sparseTypeEntry*>( + eindex + entryIndex); + entryId = dtohs(entry->idx); + // Offsets are encoded as divided by 4. + thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; + } else { + entryId = entryIndex; + thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + continue; + } + } - if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { - printf(" (PUBLIC)"); - } - printf("\n"); - - if (inclValues) { - if (valuePtr != NULL) { - printf(" "); - print_value(typeConfigs->package, value); - } else if (bagPtr != NULL) { - const int N = dtohl(bagPtr->count); - const uint8_t* baseMapPtr = (const uint8_t*)ent; - size_t mapOffset = esize; - const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - const uint32_t parent = dtohl(bagPtr->parent.ident); - uint32_t resolvedParent = parent; - if (Res_GETPACKAGE(resolvedParent) + 1 == 0) { - status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); - if (err != NO_ERROR) { - resolvedParent = 0; - } + uint32_t resID = (0xff000000 & ((packageId)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryId)); + if (packageId == 0) { + pg->dynamicRefTable.lookupResourceId(&resID); + } + resource_name resName; + if (this->getResourceName(resID, true, &resName)) { + String8 type8; + String8 name8; + if (resName.type8 != NULL) { + type8 = String8(resName.type8, resName.typeLen); + } else { + type8 = String8(resName.type, resName.typeLen); } - printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", - parent, resolvedParent, N); - for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { - printf(" #%i (Key=0x%08x): ", - i, dtohl(mapPtr->name.ident)); - value.copyFrom_dtoh(mapPtr->value); + if (resName.name8 != NULL) { + name8 = String8(resName.name8, resName.nameLen); + } else { + name8 = String8(resName.name, resName.nameLen); + } + printf(" resource 0x%08x %s:%s/%s: ", resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + type8.string(), name8.string()); + } else { + printf(" INVALID RESOURCE 0x%08x: ", resID); + } + if ((thisOffset&0x3) != 0) { + printf("NON-INTEGER OFFSET: 0x%x\n", thisOffset); + continue; + } + if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { + printf("OFFSET OUT OF BOUNDS: 0x%x+0x%x (size is 0x%x)\n", + entriesStart, thisOffset, typeSize); + continue; + } + + const ResTable_entry* ent = (const ResTable_entry*) + (((const uint8_t*)type) + entriesStart + thisOffset); + if (((entriesStart + thisOffset)&0x3) != 0) { + printf("NON-INTEGER ResTable_entry OFFSET: 0x%x\n", + (entriesStart + thisOffset)); + continue; + } + + uintptr_t esize = dtohs(ent->size); + if ((esize&0x3) != 0) { + printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize); + continue; + } + if ((thisOffset+esize) > typeSize) { + printf("ResTable_entry OUT OF BOUNDS: 0x%x+0x%x+%p (size is 0x%x)\n", + entriesStart, thisOffset, (void *)esize, typeSize); + continue; + } + + const Res_value* valuePtr = NULL; + const ResTable_map_entry* bagPtr = NULL; + Res_value value; + if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + printf("<bag>"); + bagPtr = (const ResTable_map_entry*)ent; + } else { + valuePtr = (const Res_value*) + (((const uint8_t*)ent) + esize); + value.copyFrom_dtoh(*valuePtr); + printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", + (int)value.dataType, (int)value.data, + (int)value.size, (int)value.res0); + } + + if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + printf(" (PUBLIC)"); + } + printf("\n"); + + if (inclValues) { + if (valuePtr != NULL) { + printf(" "); print_value(typeConfigs->package, value); - const size_t size = dtohs(mapPtr->value.size); - mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); - mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + } else if (bagPtr != NULL) { + const int N = dtohl(bagPtr->count); + const uint8_t* baseMapPtr = (const uint8_t*)ent; + size_t mapOffset = esize; + const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + const uint32_t parent = dtohl(bagPtr->parent.ident); + uint32_t resolvedParent = parent; + if (Res_GETPACKAGE(resolvedParent) + 1 == 0) { + status_t err = + pg->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + resolvedParent = 0; + } + } + printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", + parent, resolvedParent, N); + for (int i=0; + i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { + printf(" #%i (Key=0x%08x): ", + i, dtohl(mapPtr->name.ident)); + value.copyFrom_dtoh(mapPtr->value); + print_value(typeConfigs->package, value); + const size_t size = dtohs(mapPtr->value.size); + mapOffset += size + sizeof(*mapPtr)-sizeof(mapPtr->value); + mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); + } } } } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 79bb534a99be..270527d551a9 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -297,6 +297,26 @@ private: GLuint mTexture = 0; }; +static bool isFP16Supported(const sk_sp<GrContext>& grContext) { + static std::once_flag sOnceFlag; + static bool supported = false; + + std::call_once(sOnceFlag, [](const sk_sp<GrContext>& grContext) { + if (!grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) { + supported = false; + return; + } + + sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16, + GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER | + GraphicBuffer::USAGE_SW_READ_NEVER, "tempFp16Buffer"); + status_t error = buffer->initCheck(); + supported = !error; + }, grContext); + + return supported; +} + sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread, SkBitmap& skBitmap) { renderThread.eglManager().initialize(); @@ -318,7 +338,7 @@ sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThr type = GL_UNSIGNED_BYTE; break; case kRGBA_F16_SkColorType: - isSupported = grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig); + isSupported = isFP16Supported(grContext); if (isSupported) { type = GL_HALF_FLOAT; pixelFormat = PIXEL_FORMAT_RGBA_FP16; diff --git a/libs/incident/proto/android/section.proto b/libs/incident/proto/android/section.proto index e8280ed690f6..45f3c91850e7 100644 --- a/libs/incident/proto/android/section.proto +++ b/libs/incident/proto/android/section.proto @@ -52,6 +52,9 @@ message SectionFlags { optional SectionType type = 1 [default = SECTION_NONE]; optional string args = 2; optional bool device_specific = 3 [default = false]; + // If true, then the section will only be generated for userdebug and eng + // builds. + optional bool userdebug_and_eng_only = 4 [default = false]; } extend google.protobuf.FieldOptions { diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java index fe89b89dcfec..b9731d111aa3 100644 --- a/media/java/android/media/AudioFocusRequest.java +++ b/media/java/android/media/AudioFocusRequest.java @@ -414,7 +414,9 @@ public final class AudioFocusRequest { * with {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}. * Note that only focus changes (gains and losses) affecting the focus owner are reported, * not gains and losses of other focus requesters in the system.<br> - * Notifications are delivered on the main {@link Looper}. + * Notifications are delivered on the {@link Looper} associated with the one of + * the creation of the {@link AudioManager} used to request focus + * (see {@link AudioManager#requestAudioFocus(AudioFocusRequest)}). * @param listener the listener receiving the focus change notifications. * @return this {@code Builder} instance. * @throws NullPointerException thrown when a null focus listener is used. diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 68463e11bbfe..566db94690cb 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4558,8 +4558,7 @@ public class AudioManager { /** * The list of {@link AudioDeviceCallback} objects to receive add/remove notifications. */ - private ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> - mDeviceCallbacks = + private final ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> mDeviceCallbacks = new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>(); /** @@ -4859,22 +4858,21 @@ public class AudioManager { calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL); AudioDeviceInfo[] removed_devices = calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL); - if (added_devices.length != 0 || removed_devices.length != 0) { synchronized (mDeviceCallbacks) { for (int i = 0; i < mDeviceCallbacks.size(); i++) { handler = mDeviceCallbacks.valueAt(i).getHandler(); if (handler != null) { - if (added_devices.length != 0) { - handler.sendMessage(Message.obtain(handler, - MSG_DEVICES_DEVICES_ADDED, - added_devices)); - } if (removed_devices.length != 0) { handler.sendMessage(Message.obtain(handler, MSG_DEVICES_DEVICES_REMOVED, removed_devices)); } + if (added_devices.length != 0) { + handler.sendMessage(Message.obtain(handler, + MSG_DEVICES_DEVICES_ADDED, + added_devices)); + } } } } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index d53f1a02b514..88edd12ab2f4 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -208,6 +208,9 @@ <!-- to read and change hvac values in a car --> <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" /> + <!-- Permission necessary to change car audio volume through CarAudioManager --> + <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" /> + <application android:name=".SystemUIApplication" android:persistent="true" @@ -419,7 +422,8 @@ android:theme="@style/Theme.AlertDialogHost" android:finishOnCloseSystemDialogs="true" android:launchMode="singleTop" - android:excludeFromRecents="true" /> + android:excludeFromRecents="true" + android:visibleToInstantApps="true"/> <!-- started from PipUI --> <activity diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml index 9fdb00eebe10..11bd98f549ca 100644 --- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -49,6 +49,7 @@ <EditText android:id="@+id/passwordEntry" android:layout_width="match_parent" android:layout_height="wrap_content" + android:contentDescription="@string/keyguard_accessibility_password" android:gravity="center_horizontal" android:singleLine="true" android:textStyle="normal" diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index ed63089f22dd..513d8488a0d4 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -120,6 +120,9 @@ <!-- Accessibility description of the PIN password view. [CHAR_LIMIT=none] --> <string name="keyguard_accessibility_pin_area">PIN area</string> + <!-- Accessibility description of the normal password view. [CHAR_LIMIT=none] --> + <string name="keyguard_accessibility_password">Device password</string> + <!-- Accessibility description of the SIM PIN password view. [CHAR_LIMIT=none] --> <string name="keyguard_accessibility_sim_pin_area">SIM PIN area</string> <!-- Accessibility description of the SIM PUK password view. [CHAR_LIMIT=none] --> diff --git a/packages/SystemUI/res/drawable/car_ic_notification_2.xml b/packages/SystemUI/res/drawable/car_ic_notification_2.xml deleted file mode 100644 index c74ae153cf09..000000000000 --- a/packages/SystemUI/res/drawable/car_ic_notification_2.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2018 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License - --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="32dp" - android:height="38dp" - android:viewportWidth="32" - android:viewportHeight="38" > - <group - android:translateX="-6" - android:translateY="-3"> - <path - android:pathData="M26.6195649,6.98115478 C31.5083629,8.85235985 34.9817444,13.6069337 34.9817444,19.1767606 L34.9817444,27.9542254 L38,27.9542254 L38,34.2161972 L6,34.2161972 L6,27.9542254 L9.01825558,27.9542254 L9.01825558,19.1767606 C9.01825558,13.6069337 12.4916371,8.85235985 17.3804351,6.98115478 C17.723241,4.726863 19.6609451,3 22,3 C24.3390549,3 26.276759,4.726863 26.6195649,6.98115478 Z M17.326572,36.3035211 L26.673428,36.3035211 C26.673428,38.8973148 24.581063,41 22,41 C19.418937,41 17.326572,38.8973148 17.326572,36.3035211 Z" - android:strokeColor="#00000000" - android:fillType="evenOdd" - android:fillColor="@color/car_grey_50" /> - </group> -</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml index f6c2eebbab66..c70e8296340e 100644 --- a/packages/SystemUI/res/layout/volume_dialog.xml +++ b/packages/SystemUI/res/layout/volume_dialog.xml @@ -53,7 +53,11 @@ android:layout_gravity="center" android:soundEffectsEnabled="false" /> - <include layout="@layout/volume_dnd_icon"/> + <include layout="@layout/volume_dnd_icon" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginRight="@dimen/volume_dialog_stream_padding" + android:layout_marginTop="6dp"/> </FrameLayout> <LinearLayout @@ -71,7 +75,9 @@ android:layout_height="wrap_content" android:minWidth="@dimen/volume_dialog_panel_width" android:gravity="center" - android:orientation="horizontal" > + android:orientation="horizontal" + android:paddingRight="@dimen/volume_dialog_stream_padding" + android:paddingLeft="@dimen/volume_dialog_stream_padding"> <!-- volume rows added and removed here! :-) --> </LinearLayout> <FrameLayout diff --git a/packages/SystemUI/res/layout/volume_dnd_icon.xml b/packages/SystemUI/res/layout/volume_dnd_icon.xml index ac235b7090c5..037d143fa69e 100644 --- a/packages/SystemUI/res/layout/volume_dnd_icon.xml +++ b/packages/SystemUI/res/layout/volume_dnd_icon.xml @@ -17,14 +17,13 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/dnd_icon" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:layout_marginTop="6dp"> <ImageView android:layout_width="14dp" android:layout_height="14dp" - android:layout_marginTop="6dp" - android:layout_marginRight="6dp" android:layout_gravity="right|top" android:src="@drawable/ic_dnd" android:tint="?android:attr/textColorTertiary"/> -</FrameLayout> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml index 99d242591e92..41e078699647 100644 --- a/packages/SystemUI/res/values/attrs_car.xml +++ b/packages/SystemUI/res/values/attrs_car.xml @@ -63,4 +63,32 @@ <attr name="hvacPropertyId" format="integer"/> <attr name="hvacTempFormat" format="string"/> </declare-styleable> + + <declare-styleable name="carVolumeItems"/> + <declare-styleable name="carVolumeItems_item"> + <!-- Align with AudioAttributes.USAGE_* --> + <attr name="usage"> + <enum name="unknown" value="0"/> + <enum name="media" value="1"/> + <enum name="voice_communication" value="2"/> + <enum name="voice_communication_signalling" value="3"/> + <enum name="alarm" value="4"/> + <enum name="notification" value="5"/> + <enum name="notification_ringtone" value="6"/> + <enum name="notification_communication_request" value="7"/> + <enum name="notification_communication_instant" value="8"/> + <enum name="notification_communication_delayed" value="9"/> + <enum name="notification_event" value="10"/> + <enum name="assistance_accessibility" value="11"/> + <enum name="assistance_navigation_guidance" value="12"/> + <enum name="assistance_sonification" value="13"/> + <enum name="game" value="14"/> + <!-- hidden, do not use --> + <!-- enum name="virtual_source" value="15"/ --> + <enum name="assistant" value="16"/> + </attr> + + <!-- Icon resource ids to render on UI --> + <attr name="icon" format="reference"/> + </declare-styleable> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ad74725aa19d..f3fe29794bac 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -296,6 +296,8 @@ <dimen name="volume_dialog_panel_transparent_padding">20dp</dimen> + <dimen name="volume_dialog_stream_padding">8dp</dimen> + <!-- the amount the volume panel should be offset at the end from the view next to it (or the screen edge, in portrait--> <dimen name="volume_dialog_base_margin">8dp</dimen> diff --git a/packages/SystemUI/res/xml/car_volume_items.xml b/packages/SystemUI/res/xml/car_volume_items.xml new file mode 100644 index 000000000000..742dfdda73c8 --- /dev/null +++ b/packages/SystemUI/res/xml/car_volume_items.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + * + * Copyright 2018, 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. +*/ +--> + +<!-- + Defines all possible items on car volume settings UI, keyed by usage. + + This enables the CarSettings UI to associate VolumeGroups surfaced by + CarAudioManager.getVolumeGroupCount with renderable assets (ie: title, icon) + for presentation. + + Order matters in this configuration. If one volume group contains multiple + audio usages, the first one appears in this file would be picked to be + presented on UI. + + When overriding this configuration, please consult also the + car_volume_groups.xml, which is read by car audio service. +--> +<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto"> + <item car:usage="unknown" + car:icon="@drawable/car_ic_music"/> + <item car:usage="media" + car:icon="@drawable/car_ic_music"/> + <item car:usage="voice_communication" + car:icon="@*android:drawable/ic_audio_ring_notif"/> + <item car:usage="voice_communication_signalling" + car:icon="@*android:drawable/ic_audio_ring_notif"/> + <item car:usage="alarm" + car:icon="@*android:drawable/ic_audio_alarm"/> + <item car:usage="notification" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="notification_ringtone" + car:icon="@*android:drawable/ic_audio_ring_notif"/> + <item car:usage="notification_communication_request" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="notification_communication_instant" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="notification_communication_delayed" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="notification_event" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="assistance_accessibility" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="assistance_navigation_guidance" + car:icon="@drawable/car_ic_navigation"/> + <item car:usage="assistance_sonification" + car:icon="@drawable/car_ic_notification"/> + <item car:usage="game" + car:icon="@drawable/car_ic_music"/> + <item car:usage="assistant" + car:icon="@drawable/car_ic_music"/> +</carVolumeItems> + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java index 30a17a129e41..f066e3401752 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java @@ -57,6 +57,7 @@ import java.util.function.Consumer; import androidx.slice.Slice; import androidx.slice.SliceItem; +import androidx.slice.SliceManager; import androidx.slice.core.SliceQuery; import androidx.slice.widget.ListContent; import androidx.slice.widget.RowContent; @@ -390,6 +391,11 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe } } + public void refresh() { + Slice slice = SliceManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri); + onChanged(slice); + } + public static class Row extends LinearLayout { /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index 454528e4f5f6..f6b5d69b99d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -287,7 +287,12 @@ public class KeyguardStatusView extends GridLayout implements } } - public void refreshTime() { + public void dozeTimeTick() { + refreshTime(); + mKeyguardSlice.refresh(); + } + + private void refreshTime() { mClockView.refresh(); } diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index 8a8bafaf9853..38a90cfd96a5 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -207,8 +207,12 @@ public class AssistManager implements ConfigurationChangedReceiver { boolean structureEnabled = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.ASSIST_STRUCTURE_ENABLED, 1, UserHandle.USER_CURRENT) != 0; - final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) - .getAssistIntent(structureEnabled); + final SearchManager searchManager = + (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE); + if (searchManager == null) { + return; + } + final Intent intent = searchManager.getAssistIntent(structureEnabled); if (intent == null) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index f867b342c8de..c5e66f9f4f99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -79,7 +79,6 @@ public class KeyguardSliceProvider extends SliceProvider implements private DateFormat mDateFormat; private String mLastText; private boolean mRegistered; - private boolean mRegisteredEveryMinute; private String mNextAlarm; private NextAlarmController mNextAlarmController; protected AlarmManager mAlarmManager; @@ -175,7 +174,7 @@ public class KeyguardSliceProvider extends SliceProvider implements mZenModeController = new ZenModeControllerImpl(getContext(), mHandler); mZenModeController.addCallback(this); mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern); - registerClockUpdate(false /* everyMinute */); + registerClockUpdate(); updateClock(); return true; } @@ -214,22 +213,13 @@ public class KeyguardSliceProvider extends SliceProvider implements /** * Registers a broadcast receiver for clock updates, include date, time zone and manually * changing the date/time via the settings app. - * - * @param everyMinute {@code true} if you also want updates every minute. */ - protected void registerClockUpdate(boolean everyMinute) { + private void registerClockUpdate() { if (mRegistered) { - if (mRegisteredEveryMinute == everyMinute) { - return; - } else { - unregisterClockUpdate(); - } + return; } IntentFilter filter = new IntentFilter(); - if (everyMinute) { - filter.addAction(Intent.ACTION_TIME_TICK); - } filter.addAction(Intent.ACTION_DATE_CHANGED); filter.addAction(Intent.ACTION_TIME_CHANGED); filter.addAction(Intent.ACTION_TIMEZONE_CHANGED); @@ -237,15 +227,6 @@ public class KeyguardSliceProvider extends SliceProvider implements getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/, null /* scheduler */); mRegistered = true; - mRegisteredEveryMinute = everyMinute; - } - - protected void unregisterClockUpdate() { - if (!mRegistered) { - return; - } - getContext().unregisterReceiver(mIntentReceiver); - mRegistered = false; } @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c6bb17c2d133..065e9cc2d1d3 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -198,7 +198,13 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { showWarningNotification(); mShowing = SHOWING_WARNING; } else if (mShowAutoSaverSuggestion) { - showAutoSaverSuggestionNotification(); + // Once we showed the notification, don't show it again until it goes SHOWING_NOTHING. + // This shouldn't be needed, because we have a delete intent on this notification + // so when it's dismissed we should notice it and clear mShowAutoSaverSuggestion, + // However we double check here just in case the dismiss intent broadcast is delayed. + if (mShowing != SHOWING_AUTO_SAVER_SUGGESTION) { + showAutoSaverSuggestionNotification(); + } mShowing = SHOWING_AUTO_SAVER_SUGGESTION; } else { mNoMan.cancelAsUser(TAG_BATTERY, SystemMessage.NOTE_BAD_CHARGER, UserHandle.ALL); @@ -303,7 +309,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { private PendingIntent pendingBroadcast(String action) { return PendingIntent.getBroadcastAsUser(mContext, 0, - new Intent(action).setPackage(mContext.getPackageName()), 0, UserHandle.CURRENT); + new Intent(action).setPackage(mContext.getPackageName()) + .setFlags(Intent.FLAG_RECEIVER_FOREGROUND), + 0, UserHandle.CURRENT); } private static Intent settings(String action) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 227f2d2be0a9..8a0d7e38f875 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -129,13 +129,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final int mImageWidth; private final int mImageHeight; - // WORKAROUND: We want the same notification across screenshots that we update so that we don't - // spam a user's notification drawer. However, we only show the ticker for the saving state - // and if the ticker text is the same as the previous notification, then it will not show. So - // for now, we just add and remove a space from the ticker text to trigger the animation when - // necessary. - private static boolean mTickerAddSpace; - SaveImageInBackgroundTask(Context context, SaveImageInBackgroundData data, NotificationManager nManager) { Resources r = context.getResources(); @@ -176,8 +169,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Bitmap icon = generateAdjustedHwBitmap(data.image, iconSize, iconSize, matrix, paint, overlayColor); - // Show the intermediate notification - mTickerAddSpace = !mTickerAddSpace; mNotificationManager = nManager; final long now = System.currentTimeMillis(); @@ -199,8 +190,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mNotificationBuilder = new Notification.Builder(context, NotificationChannels.SCREENSHOTS_HEADSUP) - .setTicker(r.getString(R.string.screenshot_saving_ticker) - + (mTickerAddSpace ? " " : "")) .setContentTitle(r.getString(R.string.screenshot_saving_title)) .setSmallIcon(R.drawable.stat_notify_image) .setWhen(now) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index 27fa48aee216..8adf4bc3d5c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -2599,6 +2599,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView @Override protected boolean disallowSingleClick(MotionEvent event) { + if (areGutsExposed()) { + return false; + } float x = event.getX(); float y = event.getY(); NotificationHeaderView header = getVisibleNotificationHeader(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java index 3ed8cce21ac5..3975ec2d2776 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java @@ -37,10 +37,10 @@ public class HybridGroupManager { private final NotificationDozeHelper mDozer; private final ViewGroup mParent; - private final float mOverflowNumberSizeDark; - private final int mOverflowNumberPaddingDark; - private final float mOverflowNumberSize; - private final int mOverflowNumberPadding; + private float mOverflowNumberSizeDark; + private int mOverflowNumberPaddingDark; + private float mOverflowNumberSize; + private int mOverflowNumberPadding; private int mOverflowNumberColor; private int mOverflowNumberColorDark; @@ -50,7 +50,10 @@ public class HybridGroupManager { mContext = ctx; mParent = parent; mDozer = new NotificationDozeHelper(); + initDimens(); + } + public void initDimens() { Resources res = mContext.getResources(); mOverflowNumberSize = res.getDimensionPixelSize( R.dimen.group_overflow_number_size); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java index 9fcb0905263c..ee8325003870 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java @@ -212,7 +212,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } public void hideClock(boolean animate) { - animateHide(mClockView, animate); + animateHiddenState(mClockView, View.GONE, animate); } public void showClock(boolean animate) { @@ -240,21 +240,29 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } /** - * Hides a view. + * Animate a view to INVISIBLE or GONE */ - private void animateHide(final View v, boolean animate) { + private void animateHiddenState(final View v, int state, boolean animate) { v.animate().cancel(); if (!animate) { v.setAlpha(0f); - v.setVisibility(View.INVISIBLE); + v.setVisibility(state); return; } + v.animate() .alpha(0f) .setDuration(160) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction(() -> v.setVisibility(View.INVISIBLE)); + .withEndAction(() -> v.setVisibility(state)); + } + + /** + * Hides a view. + */ + private void animateHide(final View v, boolean animate) { + animateHiddenState(v, View.INVISIBLE, animate); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b650944403cb..b475b64eb201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -2267,7 +2267,7 @@ public class NotificationPanelView extends PanelView implements } public void onScreenTurningOn() { - mKeyguardStatusView.refreshTime(); + mKeyguardStatusView.dozeTimeTick(); } @Override @@ -2690,7 +2690,7 @@ public class NotificationPanelView extends PanelView implements } public void dozeTimeTick() { - mKeyguardStatusView.refreshTime(); + mKeyguardStatusView.dozeTimeTick(); mKeyguardBottomArea.dozeTimeTick(); if (mDarkAmount > 0) { positionClockAndNotifications(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java index e5ab712e9bdd..81152971645c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java @@ -121,8 +121,8 @@ public class NotificationChildrenContainer extends ViewGroup { public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - initDimens(); mHybridGroupManager = new HybridGroupManager(getContext(), this); + initDimens(); setClipChildren(false); } @@ -148,6 +148,7 @@ public class NotificationChildrenContainer extends ViewGroup { mTranslationForHeader = res.getDimensionPixelSize( com.android.internal.R.dimen.notification_content_margin) - mNotificationHeaderMargin; + mHybridGroupManager.initDimens(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 7370c4c1c0d1..5deebffc4778 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -3331,8 +3331,16 @@ public class NotificationStackScrollLayout extends ViewGroup private void generateTopPaddingEvent() { if (mTopPaddingNeedsAnimation) { - mAnimationEvents.add( - new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED)); + AnimationEvent event; + if (mAmbientState.isDark()) { + event = new AnimationEvent(null /* view */, + AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED, + KeyguardSliceView.DEFAULT_ANIM_DURATION); + } else { + event = new AnimationEvent(null /* view */, + AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED); + } + mAnimationEvents.add(event); } mTopPaddingNeedsAnimation = false; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java index f1a7183e1602..8034345c37a7 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java @@ -19,23 +19,34 @@ package com.android.systemui.volume; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorSet; +import android.annotation.DrawableRes; import android.annotation.Nullable; import android.app.Dialog; import android.app.KeyguardManager; +import android.car.Car; +import android.car.CarNotConnectedException; +import android.car.media.CarAudioManager; +import android.car.media.ICarVolumeCallback; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; +import android.content.ServiceConnection; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; -import android.media.AudioManager; -import android.media.AudioSystem; +import android.media.AudioAttributes; import android.os.Debug; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.util.AttributeSet; import android.util.Log; -import android.util.SparseBooleanArray; +import android.util.SparseArray; +import android.util.Xml; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.MotionEvent; @@ -53,626 +64,533 @@ import androidx.car.widget.ListItemProvider.ListProvider; import androidx.car.widget.PagedListView; import androidx.car.widget.SeekbarListItem; +import java.util.Iterator; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.plugins.VolumeDialog; -import com.android.systemui.plugins.VolumeDialogController; -import com.android.systemui.plugins.VolumeDialogController.State; -import com.android.systemui.plugins.VolumeDialogController.StreamState; /** * Car version of the volume dialog. * - * A client of VolumeDialogControllerImpl and its state model. - * * Methods ending in "H" must be called on the (ui) handler. */ public class CarVolumeDialogImpl implements VolumeDialog { - private static final String TAG = Util.logTag(CarVolumeDialogImpl.class); - - private static final long USER_ATTEMPT_GRACE_PERIOD = 1000; - - private final Context mContext; - private final H mHandler = new H(); - private final VolumeDialogController mController; - private final AudioManager mAudioManager; - - private Window mWindow; - private CustomDialog mDialog; - private PagedListView mListView; - private ListItemAdapter mPagedListAdapter; - private final List<ListItem> mVolumeLineItems = new ArrayList<>(); - private final List<VolumeRow> mRows = new ArrayList<>(); - private ConfigurableTexts mConfigurableTexts; - private final SparseBooleanArray mDynamic = new SparseBooleanArray(); - private final KeyguardManager mKeyguard; - private final Object mSafetyWarningLock = new Object(); - - private boolean mShowing; - - private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE; - private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE; - private State mState; - private SafetyWarningDialog mSafetyWarning; - private boolean mHovering = false; - private boolean mExpanded; - - public CarVolumeDialogImpl(Context context) { - mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); - mController = Dependency.get(VolumeDialogController.class); - mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + private static final String TAG = Util.logTag(CarVolumeDialogImpl.class); + + private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems"; + private static final String XML_TAG_VOLUME_ITEM = "item"; + private static final int HOVERING_TIMEOUT = 16000; + private static final int NORMAL_TIMEOUT = 3000; + private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250; + private static final int DISMISS_DELAY_IN_MILLIS = 50; + private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100; + + private final Context mContext; + private final H mHandler = new H(); + + private Window mWindow; + private CustomDialog mDialog; + private PagedListView mListView; + private ListItemAdapter mPagedListAdapter; + // All the volume items. + private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>(); + // Available volume items in car audio manager. + private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>(); + // Volume items in the PagedListView. + private final List<ListItem> mVolumeLineItems = new ArrayList<>(); + private final KeyguardManager mKeyguard; + + private Car mCar; + private CarAudioManager mCarAudioManager; + + private boolean mHovering; + private boolean mShowing; + private boolean mExpanded; + + public CarVolumeDialogImpl(Context context) { + mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme); + mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mCar = Car.createCar(mContext, mServiceConnection); + } + + public void init(int windowType, Callback callback) { + initDialog(); + + mCar.connect(); + } + + @Override + public void destroy() { + mHandler.removeCallbacksAndMessages(null); + + cleanupAudioManager(); + // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup + // audio manager beforehand. + mCar.disconnect(); + } + + private void initDialog() { + loadAudioUsageItems(); + mVolumeLineItems.clear(); + mDialog = new CustomDialog(mContext); + + mHovering = false; + mShowing = false; + mExpanded = false; + mWindow = mDialog.getWindow(); + mWindow.requestFeature(Window.FEATURE_NO_TITLE); + mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED + | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH + | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); + mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); + final WindowManager.LayoutParams lp = mWindow.getAttributes(); + lp.format = PixelFormat.TRANSLUCENT; + lp.setTitle(VolumeDialogImpl.class.getSimpleName()); + lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + lp.windowAnimations = -1; + mWindow.setAttributes(lp); + mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + mDialog.setCanceledOnTouchOutside(true); + mDialog.setContentView(R.layout.car_volume_dialog); + mDialog.setOnShowListener(dialog -> { + mListView.setTranslationY(-mListView.getHeight()); + mListView.setAlpha(0); + mListView.animate() + .alpha(1) + .translationY(0) + .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS) + .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) + .start(); + }); + mListView = (PagedListView) mWindow.findViewById(R.id.volume_list); + mListView.setOnHoverListener((v, event) -> { + int action = event.getActionMasked(); + mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) + || (action == MotionEvent.ACTION_HOVER_MOVE); + rescheduleTimeoutH(); + return true; + }); + + mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems), + BackgroundStyle.PANEL); + mListView.setAdapter(mPagedListAdapter); + mListView.setMaxPages(PagedListView.UNLIMITED_PAGES); + } + + public void show(int reason) { + mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); + } + + public void dismiss(int reason) { + mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); + } + + private void showH(int reason) { + if (D.BUG) { + Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]); } - public void init(int windowType, Callback callback) { - initDialog(); - - mController.addCallback(mControllerCallbackH, mHandler); - mController.getState(); + mHandler.removeMessages(H.SHOW); + mHandler.removeMessages(H.DISMISS); + rescheduleTimeoutH(); + // Refresh the data set before showing. + mPagedListAdapter.notifyDataSetChanged(); + if (mShowing) { + return; } + mShowing = true; - @Override - public void destroy() { - mController.removeCallback(mControllerCallbackH); - mHandler.removeCallbacksAndMessages(null); - } + mDialog.show(); + Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); + } - private void initDialog() { - mRows.clear(); - mVolumeLineItems.clear(); - mDialog = new CustomDialog(mContext); + protected void rescheduleTimeoutH() { + mHandler.removeMessages(H.DISMISS); + final int timeout = computeTimeoutH(); + mHandler.sendMessageDelayed(mHandler + .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout); - mConfigurableTexts = new ConfigurableTexts(mContext); - mHovering = false; - mShowing = false; - mExpanded = false; - mWindow = mDialog.getWindow(); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); - mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast); - final WindowManager.LayoutParams lp = mWindow.getAttributes(); - lp.format = PixelFormat.TRANSLUCENT; - lp.setTitle(VolumeDialogImpl.class.getSimpleName()); - lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; - lp.windowAnimations = -1; - mWindow.setAttributes(lp); - mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(R.layout.car_volume_dialog); - mDialog.setOnShowListener(dialog -> { - mListView.setTranslationY(-mListView.getHeight()); - mListView.setAlpha(0); - mListView.animate() - .alpha(1) - .translationY(0) - .setDuration(300) - .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator()) - .start(); - }); - mListView = (PagedListView) mWindow.findViewById(R.id.volume_list); - mListView.setOnHoverListener((v, event) -> { - int action = event.getActionMasked(); - mHovering = (action == MotionEvent.ACTION_HOVER_ENTER) - || (action == MotionEvent.ACTION_HOVER_MOVE); - rescheduleTimeoutH(); - return true; - }); - - addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music, - R.drawable.car_ic_keyboard_arrow_down, true, true), - new ExpandIconListener()); - // We map AudioManager.STREAM_RING to a navigation icon for demo. - addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0, - true, false); - addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0, - true, false); - - mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems), - BackgroundStyle.PANEL); - mListView.setAdapter(mPagedListAdapter); - mListView.setMaxPages(PagedListView.UNLIMITED_PAGES); + if (D.BUG) { + Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller()); } + } - public void setStreamImportant(int stream, boolean important) { - mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget(); - } + private int computeTimeoutH() { + return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT; + } - public void setAutomute(boolean automute) { - if (mAutomute == automute) { - return; - } - mAutomute = automute; - mHandler.sendEmptyMessage(H.RECHECK_ALL); + protected void dismissH(int reason) { + if (D.BUG) { + Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]); } - public void setSilentMode(boolean silentMode) { - if (mSilentMode == silentMode) { - return; - } - mSilentMode = silentMode; - mHandler.sendEmptyMessage(H.RECHECK_ALL); + mHandler.removeMessages(H.DISMISS); + mHandler.removeMessages(H.SHOW); + if (!mShowing) { + return; } - private VolumeRow addVolumeRow(int stream, int primaryActionIcon, int supplementalIcon, - boolean important, boolean defaultStream) { - VolumeRow volumeRow = new VolumeRow(); - volumeRow.stream = stream; - volumeRow.primaryActionIcon = primaryActionIcon; - volumeRow.supplementalIcon = supplementalIcon; - volumeRow.important = important; - volumeRow.defaultStream = defaultStream; - volumeRow.listItem = null; - mRows.add(volumeRow); - return volumeRow; + mListView.animate().cancel(); + mShowing = false; + + mListView.setTranslationY(0); + mListView.setAlpha(1); + mListView.animate() + .alpha(0) + .translationY(-mListView.getHeight()) + .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS) + .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) + .withEndAction(() -> mHandler.postDelayed(() -> { + if (D.BUG) { + Log.d(TAG, "mDialog.dismiss()"); + } + mDialog.dismiss(); + }, DISMISS_DELAY_IN_MILLIS)) + .start(); + + Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason); + } + + public void dump(PrintWriter writer) { + writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); + writer.print(" mShowing: "); writer.println(mShowing); + } + + private void loadAudioUsageItems() { + try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) { + AttributeSet attrs = Xml.asAttributeSet(parser); + int type; + // Traverse to the first start tag + while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT + && type != XmlResourceParser.START_TAG) { + } + + if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) { + throw new RuntimeException("Meta-data does not start with carVolumeItems tag"); + } + int outerDepth = parser.getDepth(); + int rank = 0; + while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT + && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlResourceParser.END_TAG) { + continue; + } + if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) { + TypedArray item = mContext.getResources().obtainAttributes( + attrs, R.styleable.carVolumeItems_item); + int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1); + if (usage >= 0) { + VolumeItem volumeItem = new VolumeItem(); + volumeItem.usage = usage; + volumeItem.rank = rank; + volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon, 0); + mVolumeItems.put(usage, volumeItem); + rank++; + } + item.recycle(); + } + } + } catch (XmlPullParserException | IOException e) { + Log.e(TAG, "Error parsing volume groups configuration", e); } - - private SeekbarListItem addSeekbarListItem( - VolumeRow volumeRow, @Nullable View.OnClickListener supplementalIconOnClickListener) { - int volumeMax = mAudioManager.getStreamMaxVolume(volumeRow.stream); - int currentVolume = mAudioManager.getStreamVolume(volumeRow.stream); - SeekbarListItem listItem = - new SeekbarListItem(mContext, volumeMax, currentVolume, - new VolumeSeekBarChangeListener(volumeRow), null); - Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon); - listItem.setPrimaryActionIcon(primaryIcon); - if (volumeRow.supplementalIcon != 0) { - Drawable supplementalIcon = mContext.getResources() - .getDrawable(volumeRow.supplementalIcon); - listItem.setSupplementalIcon(supplementalIcon, true, - supplementalIconOnClickListener); - } else { - listItem.setSupplementalEmptyIcon(true); - } - - mVolumeLineItems.add(listItem); - volumeRow.listItem = listItem; - - return listItem; + } + + private VolumeItem getVolumeItemForUsages(int[] usages) { + int rank = Integer.MAX_VALUE; + VolumeItem result = null; + for (int usage : usages) { + VolumeItem volumeItem = mVolumeItems.get(usage); + if (volumeItem.rank < rank) { + rank = volumeItem.rank; + result = volumeItem; + } } - - private static int getImpliedLevel(SeekBar seekBar, int progress) { - final int m = seekBar.getMax(); - final int n = m / 100 - 1; - final int level = progress == 0 ? 0 - : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n)); - return level; + return result; + } + + private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) { + try { + return carAudioManager.getGroupVolume(volumeGroupId); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); } - - public void show(int reason) { - mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget(); + return 0; + } + + private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) { + try { + return carAudioManager.getGroupMaxVolume(volumeGroupId); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); } - - public void dismiss(int reason) { - mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget(); + return 0; + } + + private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem, int volumeGroupId, + int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener) { + SeekbarListItem listItem = new SeekbarListItem(mContext); + listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId)); + int progress = getSeekbarValue(mCarAudioManager, volumeGroupId); + listItem.setProgress(progress); + listItem.setOnSeekBarChangeListener( + new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager)); + listItem.setPrimaryActionIcon(mContext.getResources().getDrawable(volumeItem.icon)); + if (supplementalIconId != 0) { + Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId); + listItem.setSupplementalIcon(supplementalIcon, true, + supplementalIconOnClickListener); + } else { + listItem.setSupplementalEmptyIcon(true); } - private void showH(int reason) { - if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]); - mHandler.removeMessages(H.SHOW); - mHandler.removeMessages(H.DISMISS); - rescheduleTimeoutH(); - if (mShowing) return; - mShowing = true; - - mDialog.show(); - Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked()); - mController.notifyVisible(true); + mVolumeLineItems.add(listItem); + volumeItem.listItem = listItem; + volumeItem.progress = progress; + return listItem; + } + + private VolumeItem findVolumeItem(SeekbarListItem targetItem) { + for (int i = 0; i < mVolumeItems.size(); ++i) { + VolumeItem volumeItem = mVolumeItems.valueAt(i); + if (volumeItem.listItem == targetItem) { + return volumeItem; + } } - - protected void rescheduleTimeoutH() { - mHandler.removeMessages(H.DISMISS); - final int timeout = computeTimeoutH(); - mHandler.sendMessageDelayed(mHandler - .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout); - if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller()); - mController.userActivity(); + return null; + } + + private void cleanupAudioManager() { + try { + mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder()); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); } + mVolumeLineItems.clear(); + mCarAudioManager = null; + } - private int computeTimeoutH() { - if (mHovering) return 16000; - if (mSafetyWarning != null) return 5000; - return 3000; - } + private final class H extends Handler { + private static final int SHOW = 1; + private static final int DISMISS = 2; - protected void dismissH(int reason) { - mHandler.removeMessages(H.DISMISS); - mHandler.removeMessages(H.SHOW); - if (!mShowing) return; - mListView.animate().cancel(); - mShowing = false; - - mListView.setTranslationY(0); - mListView.setAlpha(1); - mListView.animate() - .alpha(0) - .translationY(-mListView.getHeight()) - .setDuration(250) - .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator()) - .withEndAction(() -> mHandler.postDelayed(() -> { - if (D.BUG) Log.d(TAG, "mDialog.dismiss()"); - mDialog.dismiss(); - }, 50)) - .start(); - - Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason); - mController.notifyVisible(false); - synchronized (mSafetyWarningLock) { - if (mSafetyWarning != null) { - if (D.BUG) Log.d(TAG, "SafetyWarning dismissed"); - mSafetyWarning.dismiss(); - } - } + public H() { + super(Looper.getMainLooper()); } - private void trimObsoleteH() { - int initialVolumeItemSize = mVolumeLineItems.size(); - for (int i = mRows.size() - 1; i >= 0; i--) { - final VolumeRow row = mRows.get(i); - if (row.ss == null || !row.ss.dynamic) continue; - if (!mDynamic.get(row.stream)) { - mRows.remove(i); - mVolumeLineItems.remove(row.listItem); - } - } - - if (mVolumeLineItems.size() != initialVolumeItemSize) { - mPagedListAdapter.notifyDataSetChanged(); - } + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case SHOW: + showH(msg.arg1); + break; + case DISMISS: + dismissH(msg.arg1); + break; + default: + } } + } - private void onStateChangedH(State state) { - mState = state; - mDynamic.clear(); - // add any new dynamic rows - for (int i = 0; i < state.states.size(); i++) { - final int stream = state.states.keyAt(i); - final StreamState ss = state.states.valueAt(i); - if (!ss.dynamic) { - continue; - } - mDynamic.put(stream, true); - if (findRow(stream) == null) { - VolumeRow row = addVolumeRow(stream, R.drawable.ic_volume_remote, - 0, true,false); - if (mExpanded) { - addSeekbarListItem(row, null); - } - } - } - - for (VolumeRow row : mRows) { - updateVolumeRowH(row); - } + private final class CustomDialog extends Dialog implements DialogInterface { + public CustomDialog(Context context) { + super(context, com.android.systemui.R.style.qs_theme); } - private void updateVolumeRowH(VolumeRow row) { - if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream); - if (mState == null) { - return; - } - final StreamState ss = mState.states.get(row.stream); - if (ss == null) { - return; - } - row.ss = ss; - if (ss.level == row.requestedLevel) { - row.requestedLevel = -1; - } - // TODO: update Seekbar progress and change the mute icon if necessary. + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + rescheduleTimeoutH(); + return super.dispatchTouchEvent(ev); } - private VolumeRow findRow(int stream) { - for (VolumeRow row : mRows) { - if (row.stream == stream) { - return row; - } - } - return null; + @Override + protected void onStart() { + super.setCanceledOnTouchOutside(true); + super.onStart(); } - private VolumeRow findRow(SeekbarListItem targetItem) { - for (VolumeRow row : mRows) { - if (row.listItem == targetItem) { - return row; - } - } - return null; + @Override + protected void onStop() { + super.onStop(); } - public void dump(PrintWriter writer) { - writer.println(VolumeDialogImpl.class.getSimpleName() + " state:"); - writer.print(" mShowing: "); writer.println(mShowing); - writer.print(" mDynamic: "); writer.println(mDynamic); - writer.print(" mAutomute: "); writer.println(mAutomute); - writer.print(" mSilentMode: "); writer.println(mSilentMode); + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isShowing()) { + if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { + dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); + return true; + } + } + return false; } + } - private void recheckH(VolumeRow row) { - if (row == null) { - if (D.BUG) Log.d(TAG, "recheckH ALL"); - trimObsoleteH(); - for (VolumeRow r : mRows) { - updateVolumeRowH(r); - } - } else { - if (D.BUG) Log.d(TAG, "recheckH " + row.stream); - updateVolumeRowH(row); - } + private final class ExpandIconListener implements View.OnClickListener { + @Override + public void onClick(final View v) { + mExpanded = !mExpanded; + Animator inAnimator; + if (mExpanded) { + for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) { + // Adding the items which are not coming from the default item. + VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); + if (volumeItem.defaultItem) { + // Set progress here due to the progress of seekbar may not be updated. + volumeItem.listItem.setProgress(volumeItem.progress); + } else { + addSeekbarListItem(volumeItem, groupId, 0, null); + } + } + inAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_in_rotate_up); + } else { + // Only keeping the default stream if it is not expended. + Iterator itr = mVolumeLineItems.iterator(); + while (itr.hasNext()) { + SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next(); + VolumeItem volumeItem = findVolumeItem(seekbarListItem); + if (!volumeItem.defaultItem) { + itr.remove(); + } else { + // Set progress here due to the progress of seekbar may not be updated. + seekbarListItem.setProgress(volumeItem.progress); + } + } + inAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_in_rotate_down); + } + + Animator outAnimator = AnimatorInflater.loadAnimator( + mContext, R.anim.car_arrow_fade_out); + inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS); + AnimatorSet animators = new AnimatorSet(); + animators.playTogether(outAnimator, inAnimator); + animators.setTarget(v); + animators.start(); + mPagedListAdapter.notifyDataSetChanged(); } + } - private void setStreamImportantH(int stream, boolean important) { - for (VolumeRow row : mRows) { - if (row.stream == stream) { - row.important = important; - return; - } - } - } + private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { + private final int mVolumeGroupId; + private final CarAudioManager mCarAudioManager; - private void showSafetyWarningH(int flags) { - if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0 - || mShowing) { - synchronized (mSafetyWarningLock) { - if (mSafetyWarning != null) { - return; - } - mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) { - @Override - protected void cleanUp() { - synchronized (mSafetyWarningLock) { - mSafetyWarning = null; - } - recheckH(null); - } - }; - mSafetyWarning.show(); - } - recheckH(null); - } - rescheduleTimeoutH(); + private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) { + mVolumeGroupId = volumeGroupId; + mCarAudioManager = carAudioManager; } - private final VolumeDialogController.Callbacks mControllerCallbackH - = new VolumeDialogController.Callbacks() { - @Override - public void onShowRequested(int reason) { - showH(reason); - } - - @Override - public void onDismissRequested(int reason) { - dismissH(reason); - } - - @Override - public void onScreenOff() { - dismissH(Events.DISMISS_REASON_SCREEN_OFF); - } - - @Override - public void onStateChanged(State state) { - onStateChangedH(state); - } - - @Override - public void onLayoutDirectionChanged(int layoutDirection) { - mListView.setLayoutDirection(layoutDirection); - } - - @Override - public void onConfigurationChanged() { - mDialog.dismiss(); - initDialog(); - mConfigurableTexts.update(); - } - - @Override - public void onShowVibrateHint() { - if (mSilentMode) { - mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false); - } - } - - @Override - public void onShowSilentHint() { - if (mSilentMode) { - mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false); - } - } - - @Override - public void onShowSafetyWarning(int flags) { - showSafetyWarningH(flags); - } - - @Override - public void onAccessibilityModeChanged(Boolean showA11yStream) { - } - }; - - private final class H extends Handler { - private static final int SHOW = 1; - private static final int DISMISS = 2; - private static final int RECHECK = 3; - private static final int RECHECK_ALL = 4; - private static final int SET_STREAM_IMPORTANT = 5; - private static final int RESCHEDULE_TIMEOUT = 6; - private static final int STATE_CHANGED = 7; - - public H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case SHOW: showH(msg.arg1); break; - case DISMISS: dismissH(msg.arg1); break; - case RECHECK: recheckH((VolumeRow) msg.obj); break; - case RECHECK_ALL: recheckH(null); break; - case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break; - case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break; - case STATE_CHANGED: onStateChangedH(mState); break; - } - } + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (!fromUser) { + // For instance, if this event is originated from AudioService, + // we can ignore it as it has already been handled and doesn't need to be + // sent back down again. + return; + } + try { + if (mCarAudioManager == null) { + Log.w(TAG, "Ignoring volume change event because the car isn't connected"); + return; + } + mAvailableVolumeItems.get(mVolumeGroupId).progress = progress; + mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); + } } - private final class CustomDialog extends Dialog implements DialogInterface { - public CustomDialog(Context context) { - super(context, com.android.systemui.R.style.qs_theme); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - rescheduleTimeoutH(); - return super.dispatchTouchEvent(ev); - } - - @Override - protected void onStart() { - super.setCanceledOnTouchOutside(true); - super.onStart(); - } + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} - @Override - protected void onStop() { - super.onStop(); - mHandler.sendEmptyMessage(H.RECHECK_ALL); - } + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + } - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isShowing()) { - if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { - dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE); - return true; - } - } - return false; - } + private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() { + @Override + public void onGroupVolumeChanged(int groupId) { + VolumeItem volumeItem = mAvailableVolumeItems.get(groupId); + int value = getSeekbarValue(mCarAudioManager, groupId); + // Do not update the progress if it is the same as before. When car audio manager sets its + // group volume caused by the seekbar progress changed, it also triggers this callback. + // Updating the seekbar at the same time could block the continuous seeking. + if (value != volumeItem.progress) { + volumeItem.listItem.setProgress(value); + volumeItem.progress = value; + show(Events.SHOW_REASON_VOLUME_CHANGED); + } } - private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener { - private final VolumeRow mRow; - - private VolumeSeekBarChangeListener(VolumeRow volumeRow) { - mRow = volumeRow; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (mRow.ss == null) { - return; - } - if (D.BUG) { - Log.d(TAG, AudioSystem.streamToString(mRow.stream) - + " onProgressChanged " + progress + " fromUser=" + fromUser); - } - if (!fromUser) { - return; - } - if (mRow.ss.levelMin > 0) { - final int minProgress = mRow.ss.levelMin; - if (progress < minProgress) { - seekBar.setProgress(minProgress); - progress = minProgress; - } - } - final int userLevel = getImpliedLevel(seekBar, progress); - if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) { - if (mRow.requestedLevel != userLevel) { - mController.setStreamVolume(mRow.stream, userLevel); - mRow.requestedLevel = userLevel; - Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream, - userLevel); - } - } - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - if (D.BUG) { - Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream); - } - mController.setActiveStream(mRow.stream); - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - if (D.BUG) { - Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream); - } - final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress()); - Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel); - if (mRow.ss.level != userLevel) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow), - USER_ATTEMPT_GRACE_PERIOD); - } - } + @Override + public void onMasterMuteChanged() { + // ignored } + }; - private final class ExpandIconListener implements View.OnClickListener { - @Override - public void onClick(final View v) { - mExpanded = !mExpanded; - Animator inAnimator; - if (mExpanded) { - for (VolumeRow row : mRows) { - // Adding the items which are not coming from default stream. - if (!row.defaultStream) { - addSeekbarListItem(row, null); - } - } - inAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_in_rotate_up); - } else { - // Only keeping the default stream if it is not expended. - Iterator itr = mVolumeLineItems.iterator(); - while (itr.hasNext()) { - SeekbarListItem item = (SeekbarListItem) itr.next(); - VolumeRow row = findRow(item); - if (!row.defaultStream) { - itr.remove(); - } - } - inAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_in_rotate_down); - } - - Animator outAnimator = AnimatorInflater.loadAnimator( - mContext, R.anim.car_arrow_fade_out); - inAnimator.setStartDelay(100); - AnimatorSet animators = new AnimatorSet(); - animators.playTogether(outAnimator, inAnimator); - animators.setTarget(v); - animators.start(); - mPagedListAdapter.notifyDataSetChanged(); - } + private final ServiceConnection mServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + try { + mExpanded = false; + mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE); + int volumeGroupCount = mCarAudioManager.getVolumeGroupCount(); + // Populates volume slider items from volume groups to UI. + for (int groupId = 0; groupId < volumeGroupCount; groupId++) { + VolumeItem volumeItem = getVolumeItemForUsages( + mCarAudioManager.getUsagesForVolumeGroupId(groupId)); + mAvailableVolumeItems.add(volumeItem); + // The first one is the default item. + if (groupId == 0) { + volumeItem.defaultItem = true; + addSeekbarListItem(volumeItem, groupId, R.drawable.car_ic_keyboard_arrow_down, + new ExpandIconListener()); + } + } + + // If list is already initiated, update its content. + if (mPagedListAdapter != null) { + mPagedListAdapter.notifyDataSetChanged(); + } + mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder()); + } catch (CarNotConnectedException e) { + Log.e(TAG, "Car is not connected!", e); + } } - private static class VolumeRow { - private int stream; - private StreamState ss; - private boolean important; - private boolean defaultStream; - private int primaryActionIcon; - private int supplementalIcon; - private SeekbarListItem listItem; - private int requestedLevel = -1; // pending user-requested level via progress changed + /** + * This does not get called when service is properly disconnected. + * So we need to also handle cleanups in destroy(). + */ + @Override + public void onServiceDisconnected(ComponentName name) { + cleanupAudioManager(); } -}
\ No newline at end of file + }; + + /** + * Wrapper class which contains information of each volume group. + */ + private static class VolumeItem { + private @AudioAttributes.AttributeUsage int usage; + private int rank; + private boolean defaultItem = false; + private @DrawableRes int icon; + private SeekbarListItem listItem; + private int progress; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java index 6e5b5484cabe..dd552646955a 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java @@ -103,11 +103,7 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna } private VolumeDialog createCarDefault() { - CarVolumeDialogImpl impl = new CarVolumeDialogImpl(mContext); - impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); - impl.setAutomute(true); - impl.setSilentMode(false); - return impl; + return new CarVolumeDialogImpl(mContext); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index b0e40fc88054..6b322c72936c 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -695,26 +695,6 @@ public class VolumeDialogImpl implements VolumeDialog { private void enableVolumeRowViewsH(VolumeRow row, boolean enable) { boolean showDndIcon = !enable; row.dndIcon.setVisibility(showDndIcon ? VISIBLE : GONE); - - if (showDndIcon && getNumVisibleRows() == 1) { - row.dndIcon.setLayoutParams(new FrameLayout.LayoutParams( - mContext.getResources().getDimensionPixelSize( - R.dimen.volume_dialog_panel_width), - FrameLayout.LayoutParams.WRAP_CONTENT)); - } else if (row.view.getVisibility() == VISIBLE) { - row.dndIcon.setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); - } - } - - private int getNumVisibleRows() { - int count = 0; - for (int i = 0; i < mRows.size(); i++) { - if (mRows.get(i).view.getVisibility() == VISIBLE) { - count++; - } - } - return count; } /** diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java index 210764adb4a6..17a4fbc4c1f6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java @@ -90,6 +90,17 @@ public class KeyguardSliceViewTest extends SysuiTestCase { } @Test + public void refresh_replacesSliceContentAndNotifiesListener() { + AtomicBoolean notified = new AtomicBoolean(); + mKeyguardSliceView.setContentChangeListener((hasHeader)-> { + notified.set(true); + }); + mKeyguardSliceView.refresh(); + Assert.assertTrue("Listener should be notified about slice changes.", + notified.get()); + } + + @Test public void getTextColor_whiteTextWhenAOD() { // Set text color to red since the default is white and test would always pass mKeyguardSliceView.setTextColor(Color.RED); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java new file mode 100644 index 000000000000..1d8de2fbbcca --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.keyguard; + +import static org.mockito.Mockito.verify; + +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.LayoutInflater; +import android.widget.TextClock; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; + +@SmallTest +@RunWithLooper(setAsMainLooper = true) +@RunWith(AndroidTestingRunner.class) +public class KeyguardStatusViewTest extends SysuiTestCase { + + @Mock + KeyguardSliceView mKeyguardSlice; + @Mock + TextClock mClockView; + @InjectMocks + KeyguardStatusView mKeyguardStatusView; + + @Before + public void setUp() { + LayoutInflater layoutInflater = LayoutInflater.from(getContext()); + mKeyguardStatusView = + (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null); + org.mockito.MockitoAnnotations.initMocks(this); + } + + @Test + public void dozeTimeTick_updatesSlice() { + mKeyguardStatusView.dozeTimeTick(); + verify(mKeyguardSlice).refresh(); + } + + @Test + public void dozeTimeTick_updatesClock() { + mKeyguardStatusView.dozeTimeTick(); + verify(mClockView).refresh(); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java index a45e6900ece4..46e2bfbb1431 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java @@ -85,13 +85,6 @@ public class KeyguardSliceProviderTest extends SysuiTestCase { } @Test - public void unregisterClockUpdate() { - mProvider.unregisterClockUpdate(); - Assert.assertFalse("Clock updates should have been unregistered.", - mProvider.isRegistered()); - } - - @Test public void returnsValidSlice() { Slice slice = mProvider.onBindSlice(mProvider.getUri()); SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java index 9e8fa2243ccc..231cdf5e8e7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java @@ -121,7 +121,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.initNotificationIconArea(mMockNotificiationAreaController); fragment.disable(StatusBarManager.DISABLE_CLOCK, 0, false); - assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility()); + assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility()); fragment.disable(0, 0, false); diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index d61f228adb69..1541231befc6 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -3967,6 +3967,7 @@ message MetricsEvent { // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // NOTE: starting on OS P, it also added the following field: // Tag FIELD_FLAGS - Flags used to start the session + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SESSION_STARTED = 906; // An autofill request was processed by a service @@ -3980,6 +3981,7 @@ message MetricsEvent { // Type TYPE_CLOSE: Service returned a null response. // Tag FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS: if service requested field classification, // number of entries field ids in the request. + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_REQUEST = 907; // Tag of a field for a package of an autofill service @@ -3998,6 +4000,7 @@ message MetricsEvent { // Tag FIELD_AUTOFILL_NUM_DATASETS: The number of datasets shown // NOTE: starting on OS P, it also added the following field: // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_FILL_UI = 910; // Tag of a field for the length of the filter text @@ -4005,12 +4008,17 @@ message MetricsEvent { // An autofill authentication succeeded // Package: Package of app that was autofilled + // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_AUTHENTICATED = 912; // An activity was autofilled and all values could be applied // Package: Package of app that is autofilled // Tag FIELD_AUTOFILL_NUM_VALUES: Number of values that were suggested to be autofilled // Tag FIELD_AUTOFILL_NUM_VIEWS_FILLED: Number of views that could be filled + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_DATASET_APPLIED = 913; // Tag of a field for the number values to be filled in @@ -4027,6 +4035,7 @@ message MetricsEvent { // Tag FIELD_AUTOFILL_NUM_IDS: The number of ids that are saved // NOTE: starting on OS P, it also added the following field: // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_UI = 916; // Tag of a field for the number of saveable ids @@ -4038,10 +4047,14 @@ message MetricsEvent { // Type TYPE_FAILURE: The request failed // Package: Package of app that was autofilled // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_DATA_SAVE_REQUEST = 918; // An auto-fill session was finished // Package: Package of app that was autofilled + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SESSION_FINISHED = 919; // meta-event: a reader has checkpointed the log here. @@ -4167,6 +4180,8 @@ message MetricsEvent { // Package: Real package of the app being autofilled // Tag FIELD_AUTOFILL_SERVICE: Package of the autofill service that processed the request // TAG FIELD_AUTOFILL_FORGED_COMPONENT_NAME: Component name being forged + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_FORGED_COMPONENT_ATTEMPT = 948; // FIELD - The component that an app tried tro forged. @@ -4624,6 +4639,8 @@ message MetricsEvent { // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_PREVIOUS_LENGTH: the previous length of the value + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_VALUE_RESET = 1124; // Tag of AUTOFILL_VALUE_RESET @@ -4634,18 +4651,24 @@ message MetricsEvent { // Package: Package of app that was autofilled // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_DATASET_AUTHENTICATED = 1126; // An autofill service provided an invalid dataset authentication // Package: Package of app that was autofilled // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127; // An autofill service provided an invalid authentication extra // Package: Package of app that was autofilled // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_INVALID_AUTHENTICATION = 1128; // An autofill service used a custom description (using RemoteViews) in the autofill save UI @@ -4653,6 +4676,8 @@ message MetricsEvent { // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_CUSTOM_DESCRIPTION = 1129; // FIELD - Type of save object passed by the service when the Save UI is shown @@ -4664,6 +4689,8 @@ message MetricsEvent { // OS: O MR // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131; // User tapped a link in the custom description of the autofill save UI provided by an autofill service @@ -4674,6 +4701,8 @@ message MetricsEvent { // Type TYPE_FAILURE: The link could not launc an activity // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_LINK_TAPPED = 1132; // Result of the validation on save when an autofill service provided a validator @@ -4684,6 +4713,8 @@ message MetricsEvent { // Type TYPE_DISMISS: The validation failed // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_VALIDATION = 1133; // Result of an operation in the autofill save UI after the user tapped a link in the custom description @@ -4693,6 +4724,8 @@ message MetricsEvent { // Type TYPE_OPEN: The autofill save UI was restored // Type TYPE_DISMISS: The autofill save UI was destroyed // Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134; // Autofill service called API that disables itself @@ -4705,6 +4738,8 @@ message MetricsEvent { // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Package: Package of the autofill service // OS: O MR + // NOTE: starting on OS P, it also added the following field: + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_UI_LATENCY = 1136; // Action: the snooze leave-behind was shown after the user clicked the snooze icon @@ -4872,12 +4907,14 @@ message MetricsEvent { // Package: Package of app that is autofilled // OS: P // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION = 1228; // The autofill context was commited when the user clicked a view explicitly marked by the // service as committing it // Package: Package of app that is autofilled // OS: P + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229; // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling @@ -4890,6 +4927,7 @@ message MetricsEvent { // OS: P // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SERVICE_DISABLED_APP = 1231; // An autofill service asked to disable autofill for a given activity. @@ -4898,6 +4936,7 @@ message MetricsEvent { // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232; // ACTION: Stop an app and turn on background check @@ -5109,6 +5148,7 @@ message MetricsEvent { // OS: P // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request // Tag FIELD_AUTOFILL_MATCH_SCORE: Average score of the matches, in the range of 0 to 100 + // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode. AUTOFILL_FIELD_CLASSIFICATION_MATCHES = 1273; // Tag used to report autofill field classification scores @@ -5775,6 +5815,9 @@ message MetricsEvent { // OS: P ACTION_STORAGE_MIGRATE_LATER = 1413; + // Tag used to report whether an activity is being autofilled on compatibility mode. + FIELD_AUTOFILL_COMPAT_MODE = 1414; + // ---- End P Constants, all P constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java index e582daa29335..6ff9539ff5e6 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -504,7 +504,7 @@ final class AutofillManagerServiceImpl { sessionId = sRandom.nextInt(); } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0); - assertCallerLocked(componentName); + assertCallerLocked(componentName, compatMode); final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock, sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory, @@ -518,7 +518,7 @@ final class AutofillManagerServiceImpl { /** * Asserts the component is owned by the caller. */ - private void assertCallerLocked(@NonNull ComponentName componentName) { + private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) { final String packageName = componentName.getPackageName(); final PackageManager pm = mContext.getPackageManager(); final int callingUid = Binder.getCallingUid(); @@ -536,7 +536,7 @@ final class AutofillManagerServiceImpl { + ") passed component (" + componentName + ") owned by UID " + packageUid); mMetricsLogger.write( Helper.newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT, - callingPackage, getServicePackageName()) + callingPackage, getServicePackageName(), compatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME, componentName == null ? "null" : componentName.flattenToShortString())); @@ -774,10 +774,10 @@ final class AutofillManagerServiceImpl { @Nullable ArrayList<String> changedDatasetIds, @Nullable ArrayList<AutofillId> manuallyFilledFieldIds, @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, - @NonNull String appPackageName) { + @NonNull String appPackageName, boolean compatMode) { logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, - manuallyFilledDatasetIds, null, null, appPackageName); + manuallyFilledDatasetIds, null, null, appPackageName, compatMode); } @GuardedBy("mLock") @@ -790,7 +790,7 @@ final class AutofillManagerServiceImpl { @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds, @Nullable ArrayList<AutofillId> detectedFieldIdsList, @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList, - @NonNull String appPackageName) { + @NonNull String appPackageName, boolean compatMode) { if (isValidEventLocked("logDatasetNotSelected()", sessionId)) { if (sVerbose) { Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId @@ -800,7 +800,8 @@ final class AutofillManagerServiceImpl { + ", changedDatasetIds=" + changedDatasetIds + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds + ", detectedFieldIds=" + detectedFieldIdsList - + ", detectedFieldClassifications=" + detectedFieldClassificationsList); + + ", detectedFieldClassifications=" + detectedFieldClassificationsList + + ", compatMode=" + compatMode); } AutofillId[] detectedFieldsIds = null; FieldClassification[] detectedFieldClassifications = null; @@ -827,7 +828,7 @@ final class AutofillManagerServiceImpl { final int averageScore = (int) ((totalScore * 100) / totalSize); mMetricsLogger.write(Helper .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES, - appPackageName, getServicePackageName()) + appPackageName, getServicePackageName(), compatMode) .setCounterValue(numberFields) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE, averageScore)); @@ -1122,7 +1123,7 @@ final class AutofillManagerServiceImpl { /** * Called by {@link Session} when service asked to disable autofill for an app. */ - void disableAutofillForApp(@NonNull String packageName, long duration) { + void disableAutofillForApp(@NonNull String packageName, long duration, boolean compatMode) { synchronized (mLock) { if (mDisabledApps == null) { mDisabledApps = new ArrayMap<>(1); @@ -1135,7 +1136,7 @@ final class AutofillManagerServiceImpl { mDisabledApps.put(packageName, expiration); int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration; mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP, - packageName, getServicePackageName()) + packageName, getServicePackageName(), compatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)); } } @@ -1143,7 +1144,8 @@ final class AutofillManagerServiceImpl { /** * Called by {@link Session} when service asked to disable autofill an app. */ - void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) { + void disableAutofillForActivity(@NonNull ComponentName componentName, long duration, + boolean compatMode) { synchronized (mLock) { if (mDisabledActivities == null) { mDisabledActivities = new ArrayMap<>(1); @@ -1160,7 +1162,8 @@ final class AutofillManagerServiceImpl { mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY) .setComponentName(componentName) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName()) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)); + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0)); } } diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 78526f53cdde..5372d0c649cf 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.app.assist.AssistStructure; import android.app.assist.AssistStructure.ViewNode; import android.metrics.LogMaker; -import android.os.Bundle; import android.service.autofill.Dataset; import android.util.ArrayMap; import android.util.ArraySet; @@ -35,10 +34,7 @@ import com.android.internal.util.ArrayUtils; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedList; -import java.util.Objects; -import java.util.Set; public final class Helper { @@ -119,6 +115,13 @@ public final class Helper { return log; } + @NonNull + public static LogMaker newLogMaker(int category, String packageName, + String servicePackageName, boolean compatMode) { + return newLogMaker(category, packageName, servicePackageName) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0); + } + public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable CharSequence text) { if (text == null) { pw.println("null"); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index c05506066e6a..88a679b43cc6 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -644,9 +644,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, message.toString()); } if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) { - mService.disableAutofillForActivity(mComponentName, disableDuration); + mService.disableAutofillForActivity(mComponentName, disableDuration, mCompatMode); } else { - mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration); + mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, + mCompatMode); } sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE; } @@ -1251,7 +1252,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - mComponentName.getPackageName()); + mComponentName.getPackageName(), mCompatMode); } } @@ -1306,7 +1307,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, - mComponentName.getPackageName()); + mComponentName.getPackageName(), mCompatMode); return; } final Scores scores = result.getParcelable(EXTRA_SCORES); @@ -1371,7 +1372,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, - mComponentName.getPackageName()); + mComponentName.getPackageName(), mCompatMode); }); fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues); @@ -1602,7 +1603,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(), mService.getServicePackageName(), saveInfo, this, mComponentName.getPackageName(), this, - mPendingSaveUi); + mPendingSaveUi, mCompatMode); if (client != null) { try { client.setSaveUiState(id, true); @@ -2080,7 +2081,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getUiForShowing().showFillUi(filledId, response, filterText, mService.getServicePackageName(), mComponentName.getPackageName(), - mService.getServiceLabel(), mService.getServiceIcon(), this); + mService.getServiceLabel(), mService.getServiceIcon(), this, mCompatMode); synchronized (mLock) { if (mUiShownTime == 0) { @@ -2717,7 +2718,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private LogMaker newLogMaker(int category, String servicePackageName) { - return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName); + return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName, + mCompatMode); } private void writeLog(int category) { diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index ee18dc2e5824..811d87beaaa5 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -170,13 +170,14 @@ public final class AutoFillUI { public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull String packageName, @NonNull CharSequence serviceLabel, - @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback) { + @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, boolean compatMode) { if (sDebug) { final int size = filterText == null ? 0 : filterText.length(); Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars"); } - final LogMaker log = - Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName) + final LogMaker log = Helper + .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName, + compatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN, filterText == null ? 0 : filterText.length()) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, @@ -262,14 +263,16 @@ public final class AutoFillUI { public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @Nullable String servicePackageName, @NonNull SaveInfo info, @NonNull ValueFinder valueFinder, @NonNull String packageName, - @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) { + @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi, + boolean compatMode) { if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info); int numIds = 0; numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length; numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length; - final LogMaker log = - Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName) + final LogMaker log = Helper + .newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName, + compatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds); mHandler.post(() -> { @@ -319,7 +322,7 @@ public final class AutoFillUI { } mMetricsLogger.write(log); } - }); + }, compatMode); }); } diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index 80903c1a1fe4..fa2a0d9ad837 100644 --- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java @@ -60,6 +60,7 @@ import com.android.internal.R; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.UiThread; +import com.android.server.autofill.Helper; import java.io.PrintWriter; import java.util.ArrayList; @@ -134,6 +135,7 @@ final class SaveUi { private final PendingUi mPendingUi; private final String mServicePackageName; private final String mPackageName; + private final boolean mCompatMode; private boolean mDestroyed; @@ -141,12 +143,14 @@ final class SaveUi { @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon, @Nullable String servicePackageName, @NonNull String packageName, @NonNull SaveInfo info, @NonNull ValueFinder valueFinder, - @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) { + @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener, + boolean compatMode) { mPendingUi= pendingUi; mListener = new OneTimeListener(listener); mOverlayControl = overlayControl; mServicePackageName = servicePackageName; mPackageName = packageName; + mCompatMode = compatMode; context = new ContextThemeWrapper(context, THEME_ID); final LayoutInflater inflater = LayoutInflater.from(context); @@ -409,14 +413,12 @@ final class SaveUi { } private LogMaker newLogMaker(int category, int saveType) { - return newLogMaker(category) + return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode) .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType); } private LogMaker newLogMaker(int category) { - return new LogMaker(category) - .setPackageName(mPackageName) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName); + return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode); } private void writeLog(int category, int saveType) { @@ -505,6 +507,7 @@ final class SaveUi { pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi); pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName); pw.print(prefix); pw.print("app: "); pw.println(mPackageName); + pw.print(prefix); pw.print("compat mode: "); pw.println(mCompatMode); final View view = mDialog.getWindow().getDecorView(); final int[] loc = view.getLocationOnScreen(); diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index 0f42103e4178..27eae57302b0 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -78,9 +78,13 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> int mDisplayId; Display mDisplay; - /** All of the stacks on this display. Order matters, topmost stack is in front of all other - * stacks, bottommost behind. Accessed directly by ActivityManager package classes */ + /** + * All of the stacks on this display. Order matters, topmost stack is in front of all other + * stacks, bottommost behind. Accessed directly by ActivityManager package classes. Any calls + * changing the list should also call {@link #onStackOrderChanged()}. + */ private final ArrayList<ActivityStack> mStacks = new ArrayList<>(); + private ArrayList<OnStackOrderChangedListener> mStackOrderChangedCallbacks = new ArrayList<>(); /** Array of all UIDs that are present on the display. */ private IntArray mDisplayAccessUIDs = new IntArray(); @@ -145,6 +149,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> mStacks.remove(stack); removeStackReferenceIfNeeded(stack); mSupervisor.mService.updateSleepIfNeededLocked(); + onStackOrderChanged(); } void positionChildAtTop(ActivityStack stack) { @@ -163,6 +168,7 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> mStacks.add(insertPosition, stack); mWindowContainerController.positionChildAt(stack.getWindowContainerController(), insertPosition); + onStackOrderChanged(); } private int getTopInsertPosition(ActivityStack stack, int candidatePosition) { @@ -770,6 +776,30 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> mSleeping = asleep; } + /** + * Adds a listener to be notified whenever the stack order in the display changes. Currently + * only used by the {@link RecentsAnimation} to determine whether to interrupt and cancel the + * current animation when the system state changes. + */ + void registerStackOrderChangedListener(OnStackOrderChangedListener listener) { + if (!mStackOrderChangedCallbacks.contains(listener)) { + mStackOrderChangedCallbacks.add(listener); + } + } + + /** + * Removes a previously registered stack order change listener. + */ + void unregisterStackOrderChangedListener(OnStackOrderChangedListener listener) { + mStackOrderChangedCallbacks.remove(listener); + } + + private void onStackOrderChanged() { + for (int i = mStackOrderChangedCallbacks.size() - 1; i >= 0; i--) { + mStackOrderChangedCallbacks.get(i).onStackOrderChanged(); + } + } + public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "displayId=" + mDisplayId + " stacks=" + mStacks.size()); final String myPrefix = prefix + " "; @@ -806,4 +836,11 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } proto.end(token); } + + /** + * Callback for when the order of the stacks in the display changes. + */ + interface OnStackOrderChangedListener { + void onStackOrderChanged(); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a8e63f6695d4..9de876ec4408 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5291,14 +5291,9 @@ public class ActivityManagerService extends IActivityManager.Stub final int callingPid = Binder.getCallingPid(); final long origId = Binder.clearCallingIdentity(); try { - final int recentsUid; - final String recentsPackage; - final List<IBinder> topVisibleActivities; synchronized (this) { final ComponentName recentsComponent = mRecentTasks.getRecentsComponent(); - recentsPackage = recentsComponent.getPackageName(); - recentsUid = mRecentTasks.getRecentsComponentUid(); - topVisibleActivities = mStackSupervisor.getTopVisibleActivities(); + final int recentsUid = mRecentTasks.getRecentsComponentUid(); // Start a new recents animation final RecentsAnimation anim = new RecentsAnimation(this, mStackSupervisor, @@ -5314,13 +5309,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void cancelRecentsAnimation(boolean restoreHomeStackPosition) { enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "cancelRecentsAnimation()"); + final long callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); try { synchronized (this) { // Cancel the recents animation synchronously (do not hold the WM lock) mWindowManager.cancelRecentsAnimationSynchronously(restoreHomeStackPosition ? REORDER_MOVE_TO_ORIGINAL_POSITION - : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation"); + : REORDER_KEEP_IN_PLACE, "cancelRecentsAnimation/uid=" + callingUid); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 0c328a89ef84..ef23a83cd881 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1214,11 +1214,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub private void dumpHelp(PrintWriter pw) { pw.println("Battery stats (batterystats) dump options:"); - pw.println(" [--checkin] [--history] [--history-start] [--charged] [-c]"); + pw.println(" [--checkin] [--proto] [--history] [--history-start] [--charged] [-c]"); pw.println(" [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]"); pw.println(" --checkin: generate output for a checkin report; will write (and clear) the"); pw.println(" last old completed stats when they had been reset."); pw.println(" -c: write the current stats in checkin format."); + pw.println(" --proto: write the current aggregate stats (without history) in proto format."); pw.println(" --history: show only history data."); pw.println(" --history-start <num>: show only history data starting at given time offset."); pw.println(" --charged: only output data since last charged."); @@ -1431,7 +1432,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub null, mStats.mHandler, null, mUserManagerUserInfoProvider); checkinStats.readSummaryFromParcel(in); in.recycle(); - checkinStats.dumpProtoLocked(mContext, fd, apps, flags); + checkinStats.dumpProtoLocked( + mContext, fd, apps, flags, historyStart); mStats.mCheckinFile.delete(); return; } @@ -1444,7 +1446,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub } if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid()); synchronized (mStats) { - mStats.dumpProtoLocked(mContext, fd, apps, flags); + mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart); if (writeData) { mStats.writeAsyncLocked(); } diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index a88f4080a784..a3d2173b294c 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -49,7 +49,8 @@ import com.android.server.wm.WindowManagerService; * Manages the recents animation, including the reordering of the stacks for the transition and * cleanup. See {@link com.android.server.wm.RecentsAnimationController}. */ -class RecentsAnimation implements RecentsAnimationCallbacks { +class RecentsAnimation implements RecentsAnimationCallbacks, + ActivityDisplay.OnStackOrderChangedListener { private static final String TAG = RecentsAnimation.class.getSimpleName(); // TODO (b/73188263): Reset debugging flags private static final boolean DEBUG = true; @@ -140,13 +141,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks { recentsUid, recentsComponent.getPackageName()); } - final ActivityDisplay display; if (hasExistingActivity) { // Move the recents activity into place for the animation if it is not top most - display = targetActivity.getDisplay(); - display.moveStackBehindBottomMostVisibleStack(targetStack); + mDefaultDisplay.moveStackBehindBottomMostVisibleStack(targetStack); if (DEBUG) Slog.d(TAG, "Moved stack=" + targetStack + " behind stack=" - + display.getStackAbove(targetStack)); + + mDefaultDisplay.getStackAbove(targetStack)); // If there are multiple tasks in the target stack (ie. the home stack, with 3p // and default launchers coexisting), then move the task to the top as a part of @@ -173,7 +172,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks { targetActivity = mDefaultDisplay.getStack(WINDOWING_MODE_UNDEFINED, mTargetActivityType).getTopActivity(); - display = targetActivity.getDisplay(); // TODO: Maybe wait for app to draw in this particular case? @@ -190,7 +188,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mWindowManager.cancelRecentsAnimationSynchronously(REORDER_MOVE_TO_ORIGINAL_POSITION, "startRecentsActivity"); mWindowManager.initializeRecentsAnimation(mTargetActivityType, recentsAnimationRunner, - this, display.mDisplayId, mStackSupervisor.mRecentTasks.getRecentTaskIds()); + this, mDefaultDisplay.mDisplayId, + mStackSupervisor.mRecentTasks.getRecentTaskIds()); // If we updated the launch-behind state, update the visibility of the activities after // we fetch the visible tasks to be controlled by the animation @@ -198,6 +197,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mStackSupervisor.getActivityMetricsLogger().notifyActivityLaunched(START_TASK_TO_FRONT, targetActivity); + + // Register for stack order changes + mDefaultDisplay.registerStackOrderChangedListener(this); } catch (Exception e) { Slog.e(TAG, "Failed to start recents activity", e); throw e; @@ -219,6 +221,9 @@ class RecentsAnimation implements RecentsAnimationCallbacks { mAssistDataRequester = null; } + // Unregister for stack order changes + mDefaultDisplay.unregisterStackOrderChangedListener(this); + if (mWindowManager.getRecentsAnimationController() == null) return; // Just to be sure end the launch hint in case the target activity was never launched. @@ -316,6 +321,14 @@ class RecentsAnimation implements RecentsAnimationCallbacks { } } + @Override + public void onStackOrderChanged() { + // If the activity display stack order changes, cancel any running recents animation in + // place + mWindowManager.cancelRecentsAnimationSynchronously(REORDER_KEEP_IN_PLACE, + "stackOrderChanged"); + } + /** * Called only when the animation should be canceled prior to starting. */ diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index b2cf1b7d60f3..36e5340f6be7 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -525,6 +525,8 @@ public class AudioService extends IAudioService.Stub private static final int SCO_STATE_ACTIVE_INTERNAL = 3; // SCO audio deactivation request waiting for headset service to connect private static final int SCO_STATE_DEACTIVATE_REQ = 5; + // SCO audio deactivation in progress, waiting for Bluetooth audio intent + private static final int SCO_STATE_DEACTIVATING = 6; // SCO audio state is active due to an action in BT handsfree (either voice recognition or // in call audio) @@ -2705,9 +2707,13 @@ public class AudioService extends IAudioService.Stub } public void binderDied() { + int oldModeOwnerPid = 0; int newModeOwnerPid = 0; synchronized(mSetModeDeathHandlers) { Log.w(TAG, "setMode() client died"); + if (!mSetModeDeathHandlers.isEmpty()) { + oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); + } int index = mSetModeDeathHandlers.indexOf(this); if (index < 0) { Log.w(TAG, "unregistered setMode() client died"); @@ -2716,8 +2722,8 @@ public class AudioService extends IAudioService.Stub } } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all - // SCO connections not started by the application changing the mode - if (newModeOwnerPid != 0) { + // SCO connections not started by the application changing the mode when pid changes + if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { final long ident = Binder.clearCallingIdentity(); disconnectBluetoothSco(newModeOwnerPid); Binder.restoreCallingIdentity(ident); @@ -2761,17 +2767,21 @@ public class AudioService extends IAudioService.Stub return; } + int oldModeOwnerPid = 0; int newModeOwnerPid = 0; synchronized(mSetModeDeathHandlers) { + if (!mSetModeDeathHandlers.isEmpty()) { + oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid(); + } if (mode == AudioSystem.MODE_CURRENT) { mode = mMode; } newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage); } // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all - // SCO connections not started by the application changing the mode - if (newModeOwnerPid != 0) { - disconnectBluetoothSco(newModeOwnerPid); + // SCO connections not started by the application changing the mode when pid changes + if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) { + disconnectBluetoothSco(newModeOwnerPid); } } @@ -3187,28 +3197,17 @@ public class AudioService extends IAudioService.Stub } public void setBluetoothScoOnInt(boolean on, String eventSource) { - if (DEBUG_DEVICES) { - Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); - } + Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource); if (on) { // do not accept SCO ON if SCO audio is not connected synchronized (mScoClients) { - if (mBluetoothHeadset != null) { - if (mBluetoothHeadsetDevice == null) { - BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice(); - if (activeDevice != null) { - // setBtScoActiveDevice() might trigger resetBluetoothSco() which - // will call setBluetoothScoOnInt(false, "resetBluetoothSco") - setBtScoActiveDevice(activeDevice); - } - } - if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_CONNECTED) { - mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; - Log.w(TAG, "setBluetoothScoOnInt(true) failed because " - + mBluetoothHeadsetDevice + " is not in audio connected mode"); - return; - } + if ((mBluetoothHeadset != null) + && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_CONNECTED)) { + mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO; + Log.w(TAG, "setBluetoothScoOnInt(true) failed because " + + mBluetoothHeadsetDevice + " is not in audio connected mode"); + return; } } mForcedUseForComm = AudioSystem.FORCE_BT_SCO; @@ -3388,9 +3387,8 @@ public class AudioService extends IAudioService.Stub public int totalCount() { synchronized(mScoClients) { int count = 0; - int size = mScoClients.size(); - for (int i = 0; i < size; i++) { - count += mScoClients.get(i).getCount(); + for (ScoClient mScoClient : mScoClients) { + count += mScoClient.getCount(); } return count; } @@ -3398,128 +3396,161 @@ public class AudioService extends IAudioService.Stub private void requestScoState(int state, int scoAudioMode) { checkScoAudioState(); - if (totalCount() == 0) { - if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { - // Make sure that the state transitions to CONNECTING even if we cannot initiate - // the connection. - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); - // Accept SCO audio activation only in NORMAL audio mode or if the mode is - // currently controlled by the same client process. - synchronized(mSetModeDeathHandlers) { - if ((mSetModeDeathHandlers.isEmpty() || - mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) && - (mScoAudioState == SCO_STATE_INACTIVE || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { - if (mScoAudioState == SCO_STATE_INACTIVE) { - mScoAudioMode = scoAudioMode; - if (scoAudioMode == SCO_MODE_UNDEFINED) { - if (mBluetoothHeadsetDevice != null) { - mScoAudioMode = new Integer(Settings.Global.getInt( - mContentResolver, - "bluetooth_sco_channel_"+ - mBluetoothHeadsetDevice.getAddress(), - SCO_MODE_VIRTUAL_CALL)); - if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { - mScoAudioMode = SCO_MODE_VIRTUAL_CALL; - } - } else { - mScoAudioMode = SCO_MODE_RAW; + int clientCount = totalCount(); + if (clientCount != 0) { + Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode + + ", clientCount=" + clientCount); + return; + } + if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) { + // Make sure that the state transitions to CONNECTING even if we cannot initiate + // the connection. + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING); + // Accept SCO audio activation only in NORMAL audio mode or if the mode is + // currently controlled by the same client process. + synchronized(mSetModeDeathHandlers) { + int modeOwnerPid = mSetModeDeathHandlers.isEmpty() + ? 0 : mSetModeDeathHandlers.get(0).getPid(); + if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) { + Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid " + + modeOwnerPid + " != creatorPid " + mCreatorPid); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + return; + } + switch (mScoAudioState) { + case SCO_STATE_INACTIVE: + mScoAudioMode = scoAudioMode; + if (scoAudioMode == SCO_MODE_UNDEFINED) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; + if (mBluetoothHeadsetDevice != null) { + mScoAudioMode = Settings.Global.getInt(mContentResolver, + "bluetooth_sco_channel_" + + mBluetoothHeadsetDevice.getAddress(), + SCO_MODE_VIRTUAL_CALL); + if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) { + mScoAudioMode = SCO_MODE_VIRTUAL_CALL; } } - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { - boolean status = false; - if (mScoAudioMode == SCO_MODE_RAW) { - status = mBluetoothHeadset.connectAudio(); - } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { - status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - } else if (mScoAudioMode == SCO_MODE_VR) { - status = mBluetoothHeadset.startVoiceRecognition( - mBluetoothHeadsetDevice); - } - - if (status) { - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - } else { - broadcastScoConnectionState( - AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - } else if (getBluetoothHeadset()) { + } + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { mScoAudioState = SCO_STATE_ACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " connection, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } - } else { + break; + } + if (mBluetoothHeadsetDevice == null) { + Log.w(TAG, "requestScoState: no active device while connecting," + + " mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + } else { + Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice + + " failed, mScoAudioMode=" + mScoAudioMode); + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } - } else { + break; + case SCO_STATE_DEACTIVATING: + mScoAudioState = SCO_STATE_ACTIVATE_REQ; + break; + case SCO_STATE_DEACTIVATE_REQ: + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED); + break; + default: + Log.w(TAG, "requestScoState: failed to connect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } - } - } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED && - (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || - mScoAudioState == SCO_STATE_ACTIVATE_REQ)) { - if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) { - boolean status = false; - if (mScoAudioMode == SCO_MODE_RAW) { - status = mBluetoothHeadset.disconnectAudio(); - } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { - status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - } else if (mScoAudioMode == SCO_MODE_VR) { - status = mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice); - } + break; - if (!status) { + } + } + } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + switch (mScoAudioState) { + case SCO_STATE_ACTIVE_INTERNAL: + if (mBluetoothHeadset == null) { + if (getBluetoothHeadset()) { + mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + } else { + Log.w(TAG, "requestScoState: getBluetoothHeadset failed during" + + " disconnection, mScoAudioMode=" + mScoAudioMode); mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState( AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } - } else if (getBluetoothHeadset()) { - mScoAudioState = SCO_STATE_DEACTIVATE_REQ; + break; } - } else { + if (mBluetoothHeadsetDevice == null) { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; + } + if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState( + AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + } + break; + case SCO_STATE_ACTIVATE_REQ: mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - } + break; + default: + Log.w(TAG, "requestScoState: failed to disconnect in state " + + mScoAudioState + ", scoAudioMode=" + scoAudioMode); + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); + break; } } } } private void checkScoAudioState() { - if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && - mScoAudioState == SCO_STATE_INACTIVE && - mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) - != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + synchronized (mScoClients) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null && + mScoAudioState == SCO_STATE_INACTIVE && + mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice) + != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } } } + private ScoClient getScoClient(IBinder cb, boolean create) { synchronized(mScoClients) { - ScoClient client = null; - int size = mScoClients.size(); - for (int i = 0; i < size; i++) { - client = mScoClients.get(i); - if (client.getBinder() == cb) - return client; + for (ScoClient existingClient : mScoClients) { + if (existingClient.getBinder() == cb) { + return existingClient; + } } if (create) { - client = new ScoClient(cb); - mScoClients.add(client); + ScoClient newClient = new ScoClient(cb); + mScoClients.add(newClient); + return newClient; } - return client; + return null; } } public void clearAllScoClients(int exceptPid, boolean stopSco) { synchronized(mScoClients) { ScoClient savedClient = null; - int size = mScoClients.size(); - for (int i = 0; i < size; i++) { - ScoClient cl = mScoClients.get(i); + for (ScoClient cl : mScoClients) { if (cl.getPid() != exceptPid) { cl.clearCount(stopSco); } else { @@ -3556,10 +3587,17 @@ public class AudioService extends IAudioService.Stub mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) { if (mBluetoothHeadsetDevice != null) { if (mBluetoothHeadset != null) { - if (!mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice)) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, - SENDMSG_REPLACE, 0, 0, null, 0); + boolean status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_RAW) + || disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) + || disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_VR); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } else { + clearAllScoClients(exceptPid, false); + mScoAudioState = SCO_STATE_INACTIVE; } } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL && getBluetoothHeadset()) { @@ -3572,6 +3610,34 @@ public class AudioService extends IAudioService.Stub } } + private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.disconnectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.stopScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.stopVoiceRecognition(device); + default: + return false; + } + } + + private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset, + BluetoothDevice device, int scoAudioMode) { + switch (scoAudioMode) { + case SCO_MODE_RAW: + return bluetoothHeadset.connectAudio(); + case SCO_MODE_VIRTUAL_CALL: + return bluetoothHeadset.startScoUsingVirtualVoiceCall(); + case SCO_MODE_VR: + return bluetoothHeadset.startVoiceRecognition(device); + default: + return false; + } + } + private void resetBluetoothSco() { synchronized(mScoClients) { clearAllScoClients(0, false); @@ -3629,11 +3695,9 @@ public class AudioService extends IAudioService.Stub return result; } - void setBtScoActiveDevice(BluetoothDevice btDevice) { - if (DEBUG_DEVICES) { - Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")"); - } + private void setBtScoActiveDevice(BluetoothDevice btDevice) { synchronized (mScoClients) { + Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice); final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice; if (!Objects.equals(btDevice, previousActiveDevice)) { if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) { @@ -3713,37 +3777,36 @@ public class AudioService extends IAudioService.Stub boolean status = false; if (mBluetoothHeadsetDevice != null) { switch (mScoAudioState) { - case SCO_STATE_ACTIVATE_REQ: - mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; - if (mScoAudioMode == SCO_MODE_RAW) { - status = mBluetoothHeadset.connectAudio(); - } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { - status = mBluetoothHeadset.startScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - } else if (mScoAudioMode == SCO_MODE_VR) { - status = mBluetoothHeadset.startVoiceRecognition( - mBluetoothHeadsetDevice); - } - break; - case SCO_STATE_DEACTIVATE_REQ: - if (mScoAudioMode == SCO_MODE_RAW) { - status = mBluetoothHeadset.disconnectAudio(); - } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) { - status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall( - mBluetoothHeadsetDevice); - } else if (mScoAudioMode == SCO_MODE_VR) { - status = mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice); - } - break; - case SCO_STATE_DEACTIVATE_EXT_REQ: - status = mBluetoothHeadset.stopVoiceRecognition( - mBluetoothHeadsetDevice); + case SCO_STATE_ACTIVATE_REQ: + status = connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + } + break; + case SCO_STATE_DEACTIVATE_REQ: + status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } + break; + case SCO_STATE_DEACTIVATE_EXT_REQ: + status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_RAW) || + disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) || + disconnectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, SCO_MODE_VR); + if (status) { + mScoAudioState = SCO_STATE_DEACTIVATING; + } + break; } } if (!status) { - sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED, - SENDMSG_REPLACE, 0, 0, null, 0); + mScoAudioState = SCO_STATE_INACTIVE; + broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); } } } @@ -6313,33 +6376,47 @@ public class AudioService extends IAudioService.Stub if (!mScoClients.isEmpty() && (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL || mScoAudioState == SCO_STATE_ACTIVATE_REQ || - mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) { + mScoAudioState == SCO_STATE_DEACTIVATE_REQ || + mScoAudioState == SCO_STATE_DEACTIVATING)) { broadcast = true; } switch (btState) { - case BluetoothHeadset.STATE_AUDIO_CONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ && - mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - break; - case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: - scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; - mScoAudioState = SCO_STATE_INACTIVE; - clearAllScoClients(0, false); - break; - case BluetoothHeadset.STATE_AUDIO_CONNECTING: - if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && - mScoAudioState != SCO_STATE_DEACTIVATE_REQ && - mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { - mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; - } - default: - // do not broadcast CONNECTING or invalid state - broadcast = false; - break; + case BluetoothHeadset.STATE_AUDIO_CONNECTED: + scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED; + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && + mScoAudioState != SCO_STATE_DEACTIVATE_REQ && + mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + setBluetoothScoOn(true); + break; + case BluetoothHeadset.STATE_AUDIO_DISCONNECTED: + setBluetoothScoOn(false); + scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED; + // startBluetoothSco called after stopBluetoothSco + if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) { + if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null + && connectBluetoothScoAudioHelper(mBluetoothHeadset, + mBluetoothHeadsetDevice, mScoAudioMode)) { + mScoAudioState = SCO_STATE_ACTIVE_INTERNAL; + broadcast = false; + break; + } + } + // Tear down SCO if disconnected from external + clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL); + mScoAudioState = SCO_STATE_INACTIVE; + break; + case BluetoothHeadset.STATE_AUDIO_CONNECTING: + if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL && + mScoAudioState != SCO_STATE_DEACTIVATE_REQ && + mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) { + mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL; + } + default: + // do not broadcast CONNECTING or invalid state + broadcast = false; + break; } } if (broadcast) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index af5521d036af..c2769380dc58 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -88,11 +88,11 @@ import static android.os.storage.StorageManager.FLAG_STORAGE_CE; import static android.os.storage.StorageManager.FLAG_STORAGE_DE; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; + import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT; import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME; import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME; -import static com.android.internal.util.ArrayUtils.appendElement; import static com.android.internal.util.ArrayUtils.appendInt; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet; @@ -275,7 +275,6 @@ import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; -import com.android.internal.app.SuspendedAppActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; import com.android.internal.logging.MetricsLogger; @@ -411,7 +410,7 @@ public class PackageManagerService extends IPackageManager.Stub static final boolean DEBUG_DOMAIN_VERIFICATION = false; private static final boolean DEBUG_BACKUP = false; public static final boolean DEBUG_INSTALL = false; - public static final boolean DEBUG_REMOVE = true; + public static final boolean DEBUG_REMOVE = false; private static final boolean DEBUG_BROADCASTS = false; private static final boolean DEBUG_SHOW_INFO = false; private static final boolean DEBUG_PACKAGE_INFO = false; @@ -8482,6 +8481,29 @@ public class PackageManagerService extends IPackageManager.Stub } /** + * Clear the package profile if this was an upgrade and the package + * version was updated. + */ + private void maybeClearProfilesForUpgradesLI( + @Nullable PackageSetting originalPkgSetting, + @NonNull PackageParser.Package currentPkg) { + if (originalPkgSetting == null || !isUpgrade()) { + return; + } + if (originalPkgSetting.versionCode == currentPkg.mVersionCode) { + return; + } + + clearAppProfilesLIF(currentPkg, UserHandle.USER_ALL); + if (DEBUG_INSTALL) { + Slog.d(TAG, originalPkgSetting.name + + " clear profile due to version change " + + originalPkgSetting.versionCode + " != " + + currentPkg.mVersionCode); + } + } + + /** * Traces a package scan. * @see #scanPackageLI(File, int, int, long, UserHandle) */ @@ -8763,6 +8785,13 @@ public class PackageManagerService extends IPackageManager.Stub (forceCollect && canSkipFullPackageVerification(pkg)); collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify); + // Reset profile if the application version is changed + maybeClearProfilesForUpgradesLI(pkgSetting, pkg); + + /* + * A new system app appeared, but we already had a non-system one of the + * same name installed earlier. + */ boolean shouldHideSystemApp = false; // A new application appeared on /system, but, we already have a copy of // the application installed on /data. @@ -9415,6 +9444,17 @@ public class PackageManagerService extends IPackageManager.Stub mPackageUsage.writeNow(mPackages); mCompilerStats.writeNow(); mDexManager.writePackageDexUsageNow(); + + // This is the last chance to write out pending restriction settings + synchronized (mPackages) { + if (mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) { + mHandler.removeMessages(WRITE_PACKAGE_RESTRICTIONS); + for (int userId : mDirtyUsers) { + mSettings.writePackageRestrictionsLPr(userId); + } + mDirtyUsers.clear(); + } + } } @Override @@ -10186,10 +10226,14 @@ public class PackageManagerService extends IPackageManager.Stub // if this is is a sharedUser, check to see if the new package is signed by a newer // signing certificate than the existing one, and if so, copy over the new details - if (signatureCheckPs.sharedUser != null - && pkg.mSigningDetails.hasAncestor( + if (signatureCheckPs.sharedUser != null) { + if (pkg.mSigningDetails.hasAncestor( signatureCheckPs.sharedUser.signatures.mSigningDetails)) { - signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails; + signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails; + } + if (signatureCheckPs.sharedUser.signaturesChanged == null) { + signatureCheckPs.sharedUser.signaturesChanged = Boolean.FALSE; + } } } catch (PackageManagerException e) { if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { @@ -10198,10 +10242,24 @@ public class PackageManagerService extends IPackageManager.Stub // The signature has changed, but this package is in the system // image... let's recover! pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails; + // If the system app is part of a shared user we allow that shared user to change - // signatures as well in part as part of an OTA. + // signatures as well as part of an OTA. We still need to verify that the signatures + // are consistent within the shared user for a given boot, so only allow updating + // the signatures on the first package scanned for the shared user (i.e. if the + // signaturesChanged state hasn't been initialized yet in SharedUserSetting). if (signatureCheckPs.sharedUser != null) { + if (signatureCheckPs.sharedUser.signaturesChanged != null && + compareSignatures( + signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures, + pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) { + throw new PackageManagerException( + INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, + "Signature mismatch for shared user: " + pkgSetting.sharedUser); + } + signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails; + signatureCheckPs.sharedUser.signaturesChanged = Boolean.TRUE; } // File a report about this. String msg = "System package " + pkg.packageName @@ -14031,13 +14089,10 @@ public class PackageManagerService extends IPackageManager.Stub + Manifest.permission.MANAGE_USERS); } final int callingUid = Binder.getCallingUid(); - mPermissionManager.enforceCrossUserPermission(callingUid, userId, - true /* requireFullPermission */, true /* checkShell */, - "setPackagesSuspended for user " + userId); - if (callingUid != Process.ROOT_UID && - !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) { - throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not" - + " belong to calling app id " + UserHandle.getAppId(callingUid)); + if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID + && getPackageUid(callingPackage, 0, userId) != callingUid) { + throw new SecurityException("Calling package " + callingPackage + " in user " + + userId + " does not belong to calling uid " + callingUid); } if (!PLATFORM_PACKAGE_NAME.equals(callingPackage) && mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId) != null) { @@ -14164,9 +14219,19 @@ public class PackageManagerService extends IPackageManager.Stub } } - void onSuspendingPackageRemoved(String packageName, int removedForUser) { - final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds() - : new int[] {removedForUser}; + /** + * Immediately unsuspends any packages suspended by the given package. To be called + * when such a package's data is cleared or it is removed from the device. + * + * <p><b>Should not be used on a frequent code path</b> as it flushes state to disk + * synchronously + * + * @param packageName The package holding {@link Manifest.permission#SUSPEND_APPS} permission + * @param affectedUser The user for which the changes are taking place. + */ + void unsuspendForSuspendingPackage(String packageName, int affectedUser) { + final int[] userIds = (affectedUser == UserHandle.USER_ALL) ? sUserManager.getUserIds() + : new int[] {affectedUser}; for (int userId : userIds) { List<String> affectedPackages = new ArrayList<>(); synchronized (mPackages) { @@ -14183,6 +14248,8 @@ public class PackageManagerService extends IPackageManager.Stub new String[affectedPackages.size()]); sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId); sendPackagesSuspendedForUser(packageArray, userId, false, null); + // Write package restrictions immediately to avoid an inconsistent state. + mSettings.writePackageRestrictionsLPr(userId); } } } @@ -18758,7 +18825,7 @@ public class PackageManagerService extends IPackageManager.Stub final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier(); if (ps.getPermissionsState().hasPermission(Manifest.permission.SUSPEND_APPS, userId)) { - onSuspendingPackageRemoved(packageName, userId); + unsuspendForSuspendingPackage(packageName, userId); } @@ -18899,10 +18966,10 @@ public class PackageManagerService extends IPackageManager.Stub true /*notLaunched*/, false /*hidden*/, false /*suspended*/, - null, /*suspendingPackage*/ - null, /*dialogMessage*/ - null, /*suspendedAppExtras*/ - null, /*suspendedLauncherExtras*/ + null /*suspendingPackage*/, + null /*dialogMessage*/, + null /*suspendedAppExtras*/, + null /*suspendedLauncherExtras*/, false /*instantApp*/, false /*virtualPreload*/, null /*lastDisableAppCaller*/, @@ -19089,6 +19156,10 @@ public class PackageManagerService extends IPackageManager.Stub if (dsm != null) { dsm.checkMemory(); } + if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId) + == PERMISSION_GRANTED) { + unsuspendForSuspendingPackage(packageName, userId); + } } } else { succeeded = false; diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index f8bf9c45e8b1..5c6338dd0a16 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -734,10 +734,10 @@ public final class Settings { true /*notLaunched*/, false /*hidden*/, false /*suspended*/, - null, /*suspendingPackage*/ - null, /*dialogMessage*/ - null, /*suspendedAppExtras*/ - null, /*suspendedLauncherExtras*/ + null /*suspendingPackage*/, + null /*dialogMessage*/, + null /*suspendedAppExtras*/, + null /*suspendedLauncherExtras*/, instantApp, virtualPreload, null /*lastDisableAppCaller*/, @@ -1634,10 +1634,10 @@ public final class Settings { false /*notLaunched*/, false /*hidden*/, false /*suspended*/, - null, /*suspendingPackage*/ - null, /*dialogMessage*/ - null, /*suspendedAppExtras*/ - null, /*suspendedLauncherExtras*/ + null /*suspendingPackage*/, + null /*dialogMessage*/, + null /*suspendedAppExtras*/, + null /*suspendedLauncherExtras*/, false /*instantApp*/, false /*virtualPreload*/, null /*lastDisableAppCaller*/, diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java index b6b94f5e3cab..ca08415f7a77 100644 --- a/services/core/java/com/android/server/pm/SharedUserSetting.java +++ b/services/core/java/com/android/server/pm/SharedUserSetting.java @@ -46,6 +46,7 @@ public final class SharedUserSetting extends SettingBase { final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>(); final PackageSignatures signatures = new PackageSignatures(); + Boolean signaturesChanged; SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) { super(_pkgFlags, _pkgPrivateFlags); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index f6592257b636..d3526f761301 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1953,7 +1953,7 @@ public class PermissionManagerService { UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId); } if (userId == UserHandle.getUserId(callingUid)) return; - if (callingUid != Process.SYSTEM_UID && callingUid != 0) { + if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) { if (requireFullPermission) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 18f3434ece49..e6d851b83aa8 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -6092,14 +6092,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { && (!isNavBarVirtKey || mNavBarVirtualKeyHapticFeedbackEnabled) && event.getRepeatCount() == 0; - // Cancel any pending remote recents animations before handling the button itself. In the - // case where we are going home and the recents animation has already started, just cancel - // the recents animation, leaving the home stack in place for the pending start activity - if (isNavBarVirtKey && !down && !canceled) { - boolean isHomeKey = keyCode == KeyEvent.KEYCODE_HOME; - mActivityManagerInternal.cancelRecentsAnimation(!isHomeKey); - } - // Handle special keys. switch (keyCode) { case KeyEvent.KEYCODE_BACK: { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 0ccbb251d2a2..a7c203dca03b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1303,8 +1303,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { if (mCurrentUserId == userId) { if (mWaitingForUnlock) { - // If we're switching users, now is when we transition the wallpaper - switchUser(userId, null); + // the desired wallpaper is not direct-boot aware, load it now + final WallpaperData systemWallpaper = + getWallpaperSafeLocked(userId, FLAG_SYSTEM); + switchWallpaper(systemWallpaper, null); } // Make sure that the SELinux labeling of all the relevant files is correct. diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9621eddc348a..c0dc750949bc 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1510,6 +1510,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return (stack != null && stack.isVisible()) ? stack : null; } + boolean hasSplitScreenPrimaryStack() { + return getSplitScreenPrimaryStack() != null; + } + /** * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently * not visible. @@ -1613,6 +1617,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mService.mWindowsChanged = true; } + /** + * In split-screen mode we process the IME containers above the docked divider + * rather than directly above their target. + */ + private boolean skipTraverseChild(WindowContainer child) { + if (child == mImeWindowsContainers && mService.mInputMethodTarget != null + && !hasSplitScreenPrimaryStack()) { + return true; + } + return false; + } + @Override boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { // Special handling so we can process IME windows with #forAllImeWindows above their IME @@ -1620,11 +1636,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (traverseTopToBottom) { for (int i = mChildren.size() - 1; i >= 0; --i) { final DisplayChildWindowContainer child = mChildren.get(i); - if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) { - // In this case the Ime windows will be processed above their target so we skip - // here. + if (skipTraverseChild(child)) { continue; } + if (child.forAllWindows(callback, traverseTopToBottom)) { return true; } @@ -1633,11 +1648,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int count = mChildren.size(); for (int i = 0; i < count; i++) { final DisplayChildWindowContainer child = mChildren.get(i); - if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) { - // In this case the Ime windows will be processed above their target so we skip - // here. + if (skipTraverseChild(child)) { continue; } + if (child.forAllWindows(callback, traverseTopToBottom)) { return true; } diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index c8baced4cd5d..39a362906b9d 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -408,7 +408,7 @@ public class DockedStackDividerController { } void positionDockedStackedDivider(Rect frame) { - TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack(); + TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility(); if (stack == null) { // Unfortunately we might end up with still having a divider, even though the underlying // stack was already removed. This is because we are on AM thread and the removal of the diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 85e4ac7fe4ce..553b4fe53115 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -79,7 +79,7 @@ public class RecentsAnimationController implements DeathRecipient { public @interface ReorderMode {} private final WindowManagerService mService; - private final IRecentsAnimationRunner mRunner; + private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>(); private final int mDisplayId; @@ -426,7 +426,10 @@ public class RecentsAnimationController implements DeathRecipient { removeAnimation(taskAdapter); } + // Clear references to the runner unlinkToDeathOfRunner(); + mRunner = null; + // Clear associated input consumers mService.mInputMonitor.updateInputWindowsLw(true /*force*/); mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 0de3c921735e..c710c9789004 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3997,32 +3997,33 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback, + boolean traverseTopToBottom) { + // If this window is the current IME target, so we need to process the IME windows + // directly above it. The exception is if we are in split screen + // in which case we process the IME at the DisplayContent level to + // ensure it is above the docked divider. + if (isInputMethodTarget() && !inSplitScreenWindowingMode()) { + if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) { + return true; + } + } + return false; + } + private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) { if (traverseTopToBottom) { - if (isInputMethodTarget()) { - // This window is the current IME target, so we need to process the IME windows - // directly above it. - if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) { - return true; - } - } - if (callback.apply(this)) { + if (applyImeWindowsIfNeeded(callback, traverseTopToBottom) + || callback.apply(this)) { return true; } } else { - if (callback.apply(this)) { + if (callback.apply(this) + || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) { return true; } - if (isInputMethodTarget()) { - // This window is the current IME target, so we need to process the IME windows - // directly above it. - if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) { - return true; - } - } } - return false; } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java index 4b8dcc1e56c1..01425ed51b55 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java @@ -565,6 +565,47 @@ public class ActivityStackTests extends ActivityTestsBase { false /* displaySleeping */, false /* expected*/); } + @Test + public void testStackOrderChangedOnRemoveStack() throws Exception { + StackOrderChangedListener listener = new StackOrderChangedListener(); + mDefaultDisplay.registerStackOrderChangedListener(listener); + try { + mDefaultDisplay.removeChild(mStack); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + + @Test + public void testStackOrderChangedOnAddPositionStack() throws Exception { + mDefaultDisplay.removeChild(mStack); + + StackOrderChangedListener listener = new StackOrderChangedListener(); + mDefaultDisplay.registerStackOrderChangedListener(listener); + try { + mDefaultDisplay.addChild(mStack, 0); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + + @Test + public void testStackOrderChangedOnPositionStack() throws Exception { + StackOrderChangedListener listener = new StackOrderChangedListener(); + try { + final TestActivityStack fullscreenStack1 = createStackForShouldBeVisibleTest( + mDefaultDisplay, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, + true /* onTop */); + mDefaultDisplay.registerStackOrderChangedListener(listener); + mDefaultDisplay.positionChildAtBottom(fullscreenStack1); + } finally { + mDefaultDisplay.unregisterStackOrderChangedListener(listener); + } + assertTrue(listener.changed); + } + private void verifyShouldSleepActivities(boolean focusedStack, boolean keyguardGoingAway, boolean displaySleeping, boolean expected) { mSupervisor.mFocusedStack = focusedStack ? mStack : null; @@ -578,4 +619,13 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(expected, mStack.shouldSleepActivities()); } + + private class StackOrderChangedListener implements ActivityDisplay.OnStackOrderChangedListener { + boolean changed = false; + + @Override + public void onStackOrderChanged() { + changed = true; + } + } } diff --git a/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java new file mode 100644 index 000000000000..eefd973112f5 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/RecentsAnimationTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2017 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.am; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.platform.test.annotations.Presubmit; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.IRecentsAnimationRunner; +import com.android.server.AttributeCache; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * atest FrameworksServicesTests:RecentsAnimationTest + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class RecentsAnimationTest extends ActivityTestsBase { + private static final int TEST_CALLING_PID = 3; + + private Context mContext = InstrumentationRegistry.getContext(); + private ActivityManagerService mService; + private ComponentName mRecentsComponent; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + mRecentsComponent = new ComponentName(mContext.getPackageName(), "RecentsActivity"); + mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); + AttributeCache.init(mContext); + } + + @Test + public void testCancelAnimationOnStackOrderChange() throws Exception { + ActivityStack fullscreenStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ActivityStack recentsStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_RECENTS, true /* onTop */); + ActivityRecord recentsActivity = new ActivityBuilder(mService) + .setComponent(mRecentsComponent) + .setCreateTask(true) + .setStack(recentsStack) + .build(); + ActivityStack fullscreenStack2 = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ActivityRecord fsActivity = new ActivityBuilder(mService) + .setComponent(new ComponentName(mContext.getPackageName(), "App1")) + .setCreateTask(true) + .setStack(fullscreenStack2) + .build(); + doReturn(true).when(mService.mWindowManager).canStartRecentsAnimation(); + + // Start the recents animation + Intent recentsIntent = new Intent(); + recentsIntent.setComponent(mRecentsComponent); + mService.startRecentsActivity(recentsIntent, null, mock(IRecentsAnimationRunner.class)); + + fullscreenStack.moveToFront("Activity start"); + + // Ensure that the recents animation was canceled + verify(mService.mWindowManager, times(1)).cancelRecentsAnimationSynchronously( + eq(REORDER_KEEP_IN_PLACE), any()); + } + + private class MyTestActivityManagerService extends TestActivityManagerService { + MyTestActivityManagerService(Context context) { + super(context); + } + + @Override + protected RecentTasks createRecentTasks() { + RecentTasks recents = mock(RecentTasks.class); + doReturn(mRecentsComponent).when(recents).getRecentsComponent(); + System.out.println(mRecentsComponent); + return recents; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java new file mode 100644 index 000000000000..e07639933b7a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; +import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; +import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Matchers.eq; + +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.mockito.Mock; + +import java.util.function.Consumer; + +/** + * Tests for {@link WindowContainer#forAllWindows} and various implementations. + */ +@SmallTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class WindowContainerTraversalTests extends WindowTestsBase { + + @Test + public void testDockedDividerPosition() throws Exception { + final WindowState splitScreenWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, + mDisplayContent, "splitScreenWindow"); + final WindowState splitScreenSecondaryWindow = createWindowOnStack(null, + WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD, + TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow"); + + sWm.mInputMethodTarget = splitScreenWindow; + + Consumer<WindowState> c = mock(Consumer.class); + mDisplayContent.forAllWindows(c, false); + + verify(c).accept(eq(mDockedDividerWindow)); + verify(c).accept(eq(mImeWindow)); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java index 1be164395d23..942a07acbf9f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ScheduleCalendarTest.java @@ -206,15 +206,14 @@ public class ScheduleCalendarTest extends UiServiceTestCase { assertTrue(mScheduleCalendar.shouldExitForAlarm(1000)); } - @Ignore @Test public void testShouldExitForAlarm_oldAlarm() { // Cal: today 2:15pm - Calendar cal = new GregorianCalendar(); - cal.set(Calendar.HOUR_OF_DAY, 14); - cal.set(Calendar.MINUTE, 15); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); + Calendar now = new GregorianCalendar(); + now.set(Calendar.HOUR_OF_DAY, 14); + now.set(Calendar.MINUTE, 15); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); // ScheduleInfo: today 12:16pm - today 3:15pm mScheduleInfo.days = new int[] {getTodayDay()}; @@ -226,10 +225,45 @@ public class ScheduleCalendarTest extends UiServiceTestCase { mScheduleInfo.nextAlarm = 1000; // very old alarm mScheduleCalendar.setSchedule(mScheduleInfo); - assertTrue(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); + assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis())); // don't exit for an alarm if it's an old alarm - assertFalse(mScheduleCalendar.shouldExitForAlarm(1000)); + assertFalse(mScheduleCalendar.shouldExitForAlarm(now.getTimeInMillis())); + } + + @Test + public void testShouldExitForAlarm_oldAlarmInSchedule() { + // calNow: day 2 at 9pm + Calendar calNow = new GregorianCalendar(); + calNow.set(Calendar.HOUR_OF_DAY, 21); + calNow.set(Calendar.MINUTE, 0); + calNow.set(Calendar.SECOND, 0); + calNow.set(Calendar.MILLISECOND, 0); + calNow.add(Calendar.DATE, 1); // add a day + + // calAlarm: day 2 at 5am + Calendar calAlarm = new GregorianCalendar(); + calAlarm.set(Calendar.HOUR_OF_DAY, 5); + calAlarm.set(Calendar.MINUTE, 0); + calAlarm.set(Calendar.SECOND, 0); + calAlarm.set(Calendar.MILLISECOND, 0); + calAlarm.add(Calendar.DATE, 1); // add a day + + // ScheduleInfo: day 1, day 2: 9pm-7am + mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1}; + mScheduleInfo.startHour = 21; + mScheduleInfo.endHour = 7; + mScheduleInfo.startMinute = 0; + mScheduleInfo.endMinute = 0; + mScheduleInfo.exitAtAlarm = true; + mScheduleInfo.nextAlarm = calAlarm.getTimeInMillis(); // old alarm (5am day 2) + + mScheduleCalendar.setSchedule(mScheduleInfo); + assertTrue(mScheduleCalendar.isInSchedule(calNow.getTimeInMillis())); + assertTrue(mScheduleCalendar.isInSchedule(calAlarm.getTimeInMillis())); + + // don't exit for an alarm if it's an old alarm + assertFalse(mScheduleCalendar.shouldExitForAlarm(calNow.getTimeInMillis())); } @Test @@ -369,6 +403,64 @@ public class ScheduleCalendarTest extends UiServiceTestCase { assertFalse(mScheduleCalendar.isInSchedule(cal.getTimeInMillis())); } + @Test + public void testIsAlarmInSchedule_alarmAndNowInSchedule_sameScheduleTrigger() { + Calendar alarm = new GregorianCalendar(); + alarm.set(Calendar.HOUR_OF_DAY, 23); + alarm.set(Calendar.MINUTE, 15); + alarm.set(Calendar.SECOND, 0); + alarm.set(Calendar.MILLISECOND, 0); + + Calendar now = new GregorianCalendar(); + now.set(Calendar.HOUR_OF_DAY, 2); + now.set(Calendar.MINUTE, 15); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); + now.add(Calendar.DATE, 1); // add a day + + mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1}; + mScheduleInfo.startHour = 22; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endHour = 3; + mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); + + assertTrue(mScheduleCalendar.isInSchedule(alarm.getTimeInMillis())); + assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis())); + assertTrue(mScheduleCalendar.isAlarmInSchedule(alarm.getTimeInMillis(), + now.getTimeInMillis())); + } + + @Test + public void testIsAlarmInSchedule_alarmAndNowInSchedule_differentScheduleTrigger() { + Calendar alarm = new GregorianCalendar(); + alarm.set(Calendar.HOUR_OF_DAY, 23); + alarm.set(Calendar.MINUTE, 15); + alarm.set(Calendar.SECOND, 0); + alarm.set(Calendar.MILLISECOND, 0); + + Calendar now = new GregorianCalendar(); + now.set(Calendar.HOUR_OF_DAY, 23); + now.set(Calendar.MINUTE, 15); + now.set(Calendar.SECOND, 0); + now.set(Calendar.MILLISECOND, 0); + now.add(Calendar.DATE, 1); // add a day + + mScheduleInfo.days = new int[] {getTodayDay(), getTodayDay() + 1}; + mScheduleInfo.startHour = 22; + mScheduleInfo.startMinute = 15; + mScheduleInfo.endHour = 3; + mScheduleInfo.endMinute = 15; + mScheduleCalendar.setSchedule(mScheduleInfo); + + // even though both alarm and now are in schedule, they are not in the same part of + // the schedule (alarm is in schedule for the previous day's schedule compared to now) + assertTrue(mScheduleCalendar.isInSchedule(alarm.getTimeInMillis())); + assertTrue(mScheduleCalendar.isInSchedule(now.getTimeInMillis())); + assertFalse(mScheduleCalendar.isAlarmInSchedule(alarm.getTimeInMillis(), + now.getTimeInMillis())); + } + private int getTodayDay() { return new GregorianCalendar().get(Calendar.DAY_OF_WEEK); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index cd524a517376..13e3069b302b 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -46,6 +46,10 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.hardware.soundtrigger.SoundTrigger.SoundModel; +import android.media.AudioAttributes; +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.SoundTriggerDetectionService; @@ -654,8 +658,45 @@ public class SoundTriggerService extends SystemService { } } - private interface Operation { - void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; + /** + * A single operation run in a {@link RemoteSoundTriggerDetectionService}. + * + * <p>Once the remote service is connected either setup + execute or setup + stop is executed. + */ + private static class Operation { + private interface ExecuteOp { + void run(int opId, ISoundTriggerDetectionService service) throws RemoteException; + } + + private final @Nullable Runnable mSetupOp; + private final @NonNull ExecuteOp mExecuteOp; + private final @Nullable Runnable mDropOp; + + private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp, + @Nullable Runnable cancelOp) { + mSetupOp = setupOp; + mExecuteOp = executeOp; + mDropOp = cancelOp; + } + + private void setup() { + if (mSetupOp != null) { + mSetupOp.run(); + } + } + + void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException { + setup(); + mExecuteOp.run(opId, service); + } + + void drop() { + setup(); + + if (mDropOp != null) { + mDropOp.run(); + } + } } /** @@ -902,6 +943,10 @@ public class SoundTriggerService extends SystemService { private void runOrAddOperation(Operation op) { synchronized (mRemoteServiceLock) { if (mIsDestroyed || mDestroyOnceRunningOpsDone) { + Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for " + + "destruction"); + + op.drop(); return; } @@ -922,9 +967,15 @@ public class SoundTriggerService extends SystemService { int opsAdded = mNumOps.getOpsAdded(); if (mNumOps.getOpsAdded() >= opsAllowed) { - if (DEBUG || opsAllowed + 10 > opsAdded) { - Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were " - + "run in last 24 hours"); + try { + if (DEBUG || opsAllowed + 10 > opsAdded) { + Slog.w(TAG, mPuuid + ": Dropped operation as too many operations " + + "were run in last 24 hours"); + } + + op.drop(); + } catch (Exception e) { + Slog.e(TAG, mPuuid + ": Could not drop operation", e); } } else { mNumOps.addOp(currentTime); @@ -972,34 +1023,87 @@ public class SoundTriggerService extends SystemService { + ")"); } + /** + * Create an AudioRecord enough for starting and releasing the data buffered for the event. + * + * @param event The event that was received + * @return The initialized AudioRecord + */ + private @NonNull AudioRecord createAudioRecordForEvent( + @NonNull SoundTrigger.GenericRecognitionEvent event) { + AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder(); + attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD); + AudioAttributes attributes = attributesBuilder.build(); + + // Use same AudioFormat processing as in RecognitionEvent.fromParcel + AudioFormat originalFormat = event.getCaptureFormat(); + AudioFormat captureFormat = (new AudioFormat.Builder()) + .setChannelMask(originalFormat.getChannelMask()) + .setEncoding(originalFormat.getEncoding()) + .setSampleRate(originalFormat.getSampleRate()) + .build(); + + int bufferSize = AudioRecord.getMinBufferSize( + captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED + ? AudioFormat.SAMPLE_RATE_HZ_MAX + : captureFormat.getSampleRate(), + captureFormat.getChannelCount() == 2 + ? AudioFormat.CHANNEL_IN_STEREO + : AudioFormat.CHANNEL_IN_MONO, + captureFormat.getEncoding()); + + return new AudioRecord(attributes, captureFormat, bufferSize, + event.getCaptureSession()); + } + @Override public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); - runOrAddOperation((opId, service) -> { - if (!mRecognitionConfig.allowMultipleTriggers) { - synchronized (mCallbacksLock) { - mCallbacks.remove(mPuuid.getUuid()); - } - mDestroyOnceRunningOpsDone = true; - } - - service.onGenericRecognitionEvent(mPuuid, opId, event); - }); + runOrAddOperation(new Operation( + // always execute: + () -> { + if (!mRecognitionConfig.allowMultipleTriggers) { + // Unregister this remoteService once op is done + synchronized (mCallbacksLock) { + mCallbacks.remove(mPuuid.getUuid()); + } + mDestroyOnceRunningOpsDone = true; + } + }, + // execute if not throttled: + (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event), + // execute if throttled: + () -> { + if (event.isCaptureAvailable()) { + AudioRecord capturedData = createAudioRecordForEvent(event); + + // Currently we need to start and release the audio record to reset + // the DSP even if we don't want to process the event + capturedData.startRecording(); + capturedData.release(); + } + })); } @Override public void onError(int status) { if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); - runOrAddOperation((opId, service) -> { - synchronized (mCallbacksLock) { - mCallbacks.remove(mPuuid.getUuid()); - } - mDestroyOnceRunningOpsDone = true; - - service.onError(mPuuid, opId, status); - }); + runOrAddOperation( + new Operation( + // always execute: + () -> { + // Unregister this remoteService once op is done + synchronized (mCallbacksLock) { + mCallbacks.remove(mPuuid.getUuid()); + } + mDestroyOnceRunningOpsDone = true; + }, + // execute if not throttled: + (opId, service) -> service.onError(mPuuid, opId, status), + // nothing to do if throttled + null)); } @Override diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 72c67d378158..29898ffbd343 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -1299,13 +1299,18 @@ public class TelecomManager { } /** - * Ends an ongoing call. - * TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this - * method (clockwork & gearhead). - * @hide + * Ends the foreground call on the device. + * <p> + * If there is a ringing call, calling this method rejects the ringing call. Otherwise the + * foreground call is ended. + * <p> + * Requires permission {@link android.Manifest.permission#ANSWER_PHONE_CALLS}. + * + * @return {@code true} if there is a call which will be rejected or terminated, {@code false} + * otherwise. */ + @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS) @SystemApi - @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean endCall() { try { if (isServiceConnected()) { diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 3f9588acf827..4e202df3134e 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -14,13 +14,12 @@ * limitations under the License. */ - #include <frameworks/base/core/proto/android/os/incident.pb.h> #include <map> #include <set> -#include <string> #include <sstream> +#include <string> using namespace android; using namespace android::os; @@ -422,7 +421,8 @@ static bool generateSectionListCpp(Descriptor const* descriptor) { printf(" NULL),\n"); break; case SECTION_DUMPSYS: - printf(" new DumpsysSection(%d,", field->number()); + printf(" new DumpsysSection(%d, \"%s\",", field->number(), + s.userdebug_and_eng_only() ? "true" : "false"); splitAndPrint(s.args()); printf(" NULL),\n"); break; |