diff options
242 files changed, 6454 insertions, 2450 deletions
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index 80308d26a430..0d3f4208a2ab 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -182,12 +182,6 @@ interface IStatsd { void unsetBroadcastSubscriber(long configId, long subscriberId, int callingUid); /** - * 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); - - /** * Tell the stats daemon that all the pullers registered during boot have been sent. */ oneway void allPullersFromBootRegistered(); diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java index 536b71a0e625..4eeae57fe195 100644 --- a/apex/statsd/framework/java/android/util/StatsLog.java +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -25,8 +25,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; import android.os.IStatsd; -import android.os.RemoteException; -import android.os.StatsFrameworkInitializer; +import android.os.Process; import android.util.proto.ProtoOutputStream; import com.android.internal.util.StatsdStatsLog; @@ -45,10 +44,6 @@ public final class StatsLog { private static final boolean DEBUG = false; private static final int EXPERIMENT_IDS_FIELD_ID = 1; - private static IStatsd sService; - - private static Object sLogLock = new Object(); - private StatsLog() { } @@ -59,26 +54,13 @@ public final class StatsLog { * @return True if the log request was sent to statsd. */ public static boolean logStart(int label) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Log.d(TAG, "Failed to find statsd when logging start"); - } - return false; - } - service.sendAppBreadcrumbAtom(label, - StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Log.d(TAG, "Failed to connect to statsd when logging start"); - } - return false; - } - } + int callingUid = Process.myUid(); + StatsdStatsLog.write( + StatsdStatsLog.APP_BREADCRUMB_REPORTED, + callingUid, + label, + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + return true; } /** @@ -88,26 +70,13 @@ public final class StatsLog { * @return True if the log request was sent to statsd. */ public static boolean logStop(int label) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Log.d(TAG, "Failed to find statsd when logging stop"); - } - return false; - } - service.sendAppBreadcrumbAtom( - label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Log.d(TAG, "Failed to connect to statsd when logging stop"); - } - return false; - } - } + int callingUid = Process.myUid(); + StatsdStatsLog.write( + StatsdStatsLog.APP_BREADCRUMB_REPORTED, + callingUid, + label, + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + return true; } /** @@ -117,26 +86,13 @@ public final class StatsLog { * @return True if the log request was sent to statsd. */ public static boolean logEvent(int label) { - synchronized (sLogLock) { - try { - IStatsd service = getIStatsdLocked(); - if (service == null) { - if (DEBUG) { - Log.d(TAG, "Failed to find statsd when logging event"); - } - return false; - } - service.sendAppBreadcrumbAtom( - label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); - return true; - } catch (RemoteException e) { - sService = null; - if (DEBUG) { - Log.d(TAG, "Failed to connect to statsd when logging event"); - } - return false; - } - } + int callingUid = Process.myUid(); + StatsdStatsLog.write( + StatsdStatsLog.APP_BREADCRUMB_REPORTED, + callingUid, + label, + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + return true; } /** @@ -181,17 +137,6 @@ public final class StatsLog { return true; } - private static IStatsd getIStatsdLocked() throws RemoteException { - if (sService != null) { - return sService; - } - sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer - .getStatsServiceManager() - .getStatsdServiceRegisterer() - .get()); - return sService; - } - /** * Write an event to stats log using the raw format. * diff --git a/api/current.txt b/api/current.txt index 80e2d00b3e48..3f8bee9230a6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -48759,12 +48759,8 @@ package android.telephony.ims { method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities); } - public class ImsRcsManager implements android.telephony.ims.RegistrationManager { - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>); + public class ImsRcsManager { method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter(); - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException; - method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback); field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN"; } diff --git a/api/test-current.txt b/api/test-current.txt index 755380e93b2a..ca291f373be2 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -126,6 +126,7 @@ package android.app { public class ActivityTaskManager { method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>); + method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks(); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect); @@ -504,6 +505,11 @@ package android.app { method public boolean isStatusBarExpansionDisabled(); } + public class TaskInfo { + method @NonNull public android.content.res.Configuration getConfiguration(); + method @NonNull public android.window.WindowContainerToken getToken(); + } + public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener { method public android.widget.TimePicker getTimePicker(); } diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index acd9ec3be210..0e49d187a3b5 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -104,7 +104,7 @@ cc_defaults { "src/subscriber/IncidentdReporter.cpp", "src/subscriber/SubscriberReporter.cpp", "src/uid_data.proto", - "src/utils/NamedLatch.cpp", + "src/utils/MultiConditionTrigger.cpp", ], local_include_dirs: [ @@ -366,7 +366,7 @@ cc_test { "tests/StatsService_test.cpp", "tests/storage/StorageManager_test.cpp", "tests/UidMap_test.cpp", - "tests/utils/NamedLatch_test.cpp", + "tests/utils/MultiConditionTrigger_test.cpp", ], static_libs: [ diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index e251399776fb..ba4cf11b84f1 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -382,10 +382,6 @@ public: inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); } - inline void setResetState(int32_t resetState) { - mResetState = resetState; - } - // Default value = false inline bool isNested() const { return getValueFromBitmask(NESTED_POS); } @@ -398,12 +394,6 @@ public: // Default value = false inline bool isUidField() const { return getValueFromBitmask(UID_POS); } - // If a reset state is not sent in the StatsEvent, returns -1. Note that a - // reset satate is only sent if and only if a reset should be triggered. - inline int32_t getResetState() const { - return mResetState; - } - private: inline void setBitmaskAtPos(int pos, bool value) { mBooleanBitmask &= ~(1 << pos); // clear @@ -417,8 +407,6 @@ private: // This is a bitmask over all annotations stored in boolean form. Because // there are only 4 booleans, just one byte is required. uint8_t mBooleanBitmask = 0; - - int32_t mResetState = -1; }; /** diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index d914ab2436c7..cc48d50ebd50 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -389,15 +389,24 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); + // Tell StatsdStats about new event + const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); + int atomId = event->GetTagId(); + StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC); + if (!event->isValid()) { + StatsdStats::getInstance().noteAtomError(atomId); + return; + } + // Hard-coded logic to update train info on disk and fill in any information // this log event may be missing. - if (event->GetTagId() == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { + if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) { onBinaryPushStateChangedEventLocked(event); } // Hard-coded logic to update experiment ids on disk for certain rollback // types and fill the rollback atom with experiment ids - if (event->GetTagId() == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { + if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) { onWatchdogRollbackOccurredLocked(event); } @@ -406,16 +415,11 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { ALOGI("%s", event->ToString().c_str()); } #endif - const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs(); - resetIfConfigTtlExpiredLocked(eventElapsedTimeNs); - StatsdStats::getInstance().noteAtomLogged( - event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC); - // Hard-coded logic to update the isolated uid's in the uid-map. // The field numbers need to be currently updated by hand with atoms.proto - if (event->GetTagId() == android::os::statsd::util::ISOLATED_UID_CHANGED) { + if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) { onIsolatedUidChangedEventLocked(*event); } @@ -432,7 +436,7 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) { } - if (event->GetTagId() != android::os::statsd::util::ISOLATED_UID_CHANGED) { + if (atomId != android::os::statsd::util::ISOLATED_UID_CHANGED) { // Map the isolated uid to host uid if necessary. mapIsolatedUidToHostUidIfNecessaryLocked(event); } @@ -1051,8 +1055,8 @@ int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) { void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, const int64_t version) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received app upgrade"); - for (auto it : mMetricsManagers) { + VLOG("Received app upgrade"); + for (const auto& it : mMetricsManagers) { it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version); } } @@ -1060,20 +1064,28 @@ void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const strin void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received app removed"); - for (auto it : mMetricsManagers) { + VLOG("Received app removed"); + for (const auto& it : mMetricsManagers) { it.second->notifyAppRemoved(eventTimeNs, apk, uid); } } void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) { std::lock_guard<std::mutex> lock(mMetricsMutex); - ALOGW("Received uid map"); - for (auto it : mMetricsManagers) { + VLOG("Received uid map"); + for (const auto& it : mMetricsManagers) { it.second->onUidMapReceived(eventTimeNs); } } +void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) { + std::lock_guard<std::mutex> lock(mMetricsMutex); + VLOG("Received boot completed signal"); + for (const auto& it : mMetricsManagers) { + it.second->onStatsdInitCompleted(elapsedTimeNs); + } +} + void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) { std::lock_guard<std::mutex> lock(mMetricsMutex); mOnDiskDataConfigs.insert(key); diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h index 97512ed7595c..ffd83ba978f4 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -120,6 +120,11 @@ public: /* Notify all MetricsManagers of uid map snapshots received */ void onUidMapReceived(const int64_t& eventTimeNs) override; + /* Notify all metrics managers of boot completed + * This will force a bucket split when the boot is finished. + */ + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + // Reset all configs. void resetConfigs(); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index ae7a8d0d30cc..bd9f7a59fcbd 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -118,7 +118,8 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ } })), mEventQueue(queue), - mBootCompleteLatch({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}), + mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}, + [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }), mStatsCompanionServiceDeathRecipient( AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) { mUidMap = UidMap::getInstance(); @@ -165,12 +166,6 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ std::thread pushedEventThread([this] { readLogs(); }); pushedEventThread.detach(); } - - std::thread bootCompletedThread([this] { - mBootCompleteLatch.wait(); - VLOG("In the boot completed thread"); - }); - bootCompletedThread.detach(); } StatsService::~StatsService() { @@ -946,7 +941,7 @@ Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) { packageNames, installers); - mBootCompleteLatch.countDown(kUidMapReceivedTag); + mBootCompleteTrigger.markComplete(kUidMapReceivedTag); VLOG("StatsService::informAllUidData UidData proto parsed successfully."); return Status::ok(); } @@ -1066,7 +1061,7 @@ Status StatsService::bootCompleted() { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::bootCompleted was called"); - mBootCompleteLatch.countDown(kBootCompleteTag); + mBootCompleteTrigger.markComplete(kBootCompleteTag); return Status::ok(); } @@ -1222,20 +1217,11 @@ 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::os::statsd::util::stats_write(android::os::statsd::util::APP_BREADCRUMB_REPORTED, - (int32_t) AIBinder_getCallingUid(), label, - state); - return Status::ok(); -} - Status StatsService::allPullersFromBootRegistered() { ENFORCE_UID(AID_SYSTEM); VLOG("StatsService::allPullersFromBootRegistered was called"); - mBootCompleteLatch.countDown(kAllPullersRegisteredTag); + mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag); return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 79324d89d8e8..b49fa1d42e66 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -33,7 +33,7 @@ #include "packages/UidMap.h" #include "shell/ShellSubscriber.h" #include "statscompanion_util.h" -#include "utils/NamedLatch.h" +#include "utils/MultiConditionTrigger.h" using namespace android; using namespace android::os; @@ -162,11 +162,6 @@ public: virtual void sayHiToStatsCompanion(); /** - * Binder call to get AppBreadcrumbReported atom. - */ - virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override; - - /** * Binder call to notify statsd that all pullers from boot have been registered. */ virtual Status allPullersFromBootRegistered(); @@ -386,7 +381,7 @@ private: mutable mutex mShellSubscriberMutex; std::shared_ptr<LogEventQueue> mEventQueue; - NamedLatch mBootCompleteLatch; + MultiConditionTrigger mBootCompleteTrigger; static const inline string kBootCompleteTag = "BOOT_COMPLETE"; static const inline string kUidMapReceivedTag = "UID_MAP"; static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED"; @@ -399,11 +394,14 @@ private: FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid); FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp); + FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval); FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit); + FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket); + FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket); FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket); }; diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp index 933f48d1714b..3618bb0dd08b 100644 --- a/cmds/statsd/src/external/StatsCallbackPuller.cpp +++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp @@ -67,8 +67,14 @@ bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) { lock_guard<mutex> lk(*cv_mutex); for (const StatsEventParcel& parcel: output) { shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1); - event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size()); - sharedData->push_back(event); + bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(), + parcel.buffer.size()); + if (valid) { + sharedData->push_back(event); + } else { + StatsdStats::getInstance().noteAtomError(event->GetTagId(), + /*pull=*/true); + } } *pullSuccess = success; *pullFinish = true; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index db637b15e969..46f5dbda5521 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -54,6 +54,7 @@ const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19; const int FIELD_ID_ATOM_STATS_TAG = 1; const int FIELD_ID_ATOM_STATS_COUNT = 2; +const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3; const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1; const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1; @@ -549,6 +550,20 @@ void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayN std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs); } +void StatsdStats::noteAtomError(int atomTag, bool pull) { + lock_guard<std::mutex> lock(mLock); + if (pull) { + mPulledAtomStats[atomTag].atomErrorCount++; + return; + } + + bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end()); + bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize); + if (!full || present) { + mPushedAtomErrorStats[atomTag]++; + } +} + StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) { auto atomMetricStatsIter = mAtomMetricStats.find(metricId); if (atomMetricStatsIter != mAtomMetricStats.end()) { @@ -604,9 +619,11 @@ void StatsdStats::resetInternalLocked() { pullStats.second.pullExceedMaxDelay = 0; pullStats.second.registeredCount = 0; pullStats.second.unregisteredCount = 0; + pullStats.second.atomErrorCount = 0; } mAtomMetricStats.clear(); mActivationBroadcastGuardrailStats.clear(); + mPushedAtomErrorStats.clear(); } string buildTimeString(int64_t timeSec) { @@ -617,6 +634,15 @@ string buildTimeString(int64_t timeSec) { return string(timeBuffer); } +int StatsdStats::getPushedAtomErrors(int atomId) const { + const auto& it = mPushedAtomErrorStats.find(atomId); + if (it != mPushedAtomErrorStats.end()) { + return it->second; + } else { + return 0; + } +} + void StatsdStats::dumpStats(int out) const { lock_guard<std::mutex> lock(mLock); time_t t = mStartTimeSec; @@ -721,11 +747,13 @@ void StatsdStats::dumpStats(int out) const { const size_t atomCounts = mPushedAtomStats.size(); for (size_t i = 2; i < atomCounts; i++) { if (mPushedAtomStats[i] > 0) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]); + dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i], + getPushedAtomErrors((int)i)); } } for (const auto& pair : mNonPlatformPushedAtomStats) { - dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second); + dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second, + getPushedAtomErrors(pair.first)); } dprintf(out, "********Pulled Atom stats***********\n"); @@ -737,13 +765,15 @@ void StatsdStats::dumpStats(int out) const { "nanos)%lld, " " (max pull delay nanos)%lld, (data error)%ld\n" " (pull timeout)%ld, (pull exceed max delay)%ld\n" - " (registered count) %ld, (unregistered count) %ld\n", + " (registered count) %ld, (unregistered count) %ld\n" + " (atom error count) %d\n", (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache, (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec, (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs, (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs, pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay, - pair.second.registeredCount, pair.second.unregisteredCount); + pair.second.registeredCount, pair.second.unregisteredCount, + pair.second.atomErrorCount); } if (mAnomalyAlarmRegisteredStats > 0) { @@ -919,6 +949,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]); + int errors = getPushedAtomErrors(i); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } } @@ -928,6 +962,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first); proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second); + int errors = getPushedAtomErrors(pair.first); + if (errors > 0) { + proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors); + } proto.end(token); } diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index ff31e9e73fd0..21e524a9fadf 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -461,6 +461,16 @@ public: */ void noteActivationBroadcastGuardrailHit(const int uid); + /** + * Reports that an atom is erroneous or cannot be parsed successfully by + * statsd. An atom tag of 0 indicates that the client did not supply the + * atom id within the encoding. + * + * For pushed atoms only, this call should be preceded by a call to + * noteAtomLogged. + */ + void noteAtomError(int atomTag, bool pull=false); + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue @@ -499,6 +509,7 @@ public: long emptyData = 0; long registeredCount = 0; long unregisteredCount = 0; + int32_t atomErrorCount = 0; } PulledAtomStats; typedef struct { @@ -546,6 +557,12 @@ private: // Maps PullAtomId to its stats. The size is capped by the puller atom counts. std::map<int, PulledAtomStats> mPulledAtomStats; + // Stores the number of times a pushed atom was logged erroneously. The + // corresponding counts for pulled atoms are stored in PulledAtomStats. + // The max size of this map is kMaxAtomErrorsStatsSize. + std::map<int, int> mPushedAtomErrorStats; + int kMaxPushedAtomErrorStatsSize = 100; + // Maps metric ID to its stats. The size is capped by the number of metrics. std::map<int64_t, AtomMetricStats> mAtomMetricStats; @@ -613,6 +630,8 @@ private: void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats); + int getPushedAtomErrors(int atomId) const; + /** * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference * will live as long as `this`. @@ -631,6 +650,7 @@ private: FRIEND_TEST(StatsdStatsTest, TestPullAtomStats); FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats); FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit); + FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats); }; } // namespace statsd diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index 61cd01728ab1..eb830e114b40 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -114,14 +114,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status))); } -LogEvent::~LogEvent() { - if (mContext) { - // This is for the case when LogEvent is created using the test interface - // but init() isn't called. - android_log_destroy(&mContext); - } -} - void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) { int32_t value = readNextValue<int32_t>(); addToValues(pos, depth, value, last); @@ -303,8 +295,7 @@ void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) { return; } - int32_t resetState = readNextValue<int32_t>(); - mValues[mValues.size() - 1].mAnnotations.setResetState(resetState); + mResetState = readNextValue<int32_t>(); } void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) { @@ -386,7 +377,6 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { typeInfo = readNextValue<uint8_t>(); uint8_t typeId = getTypeId(typeInfo); - // TODO(b/144373276): handle errors passed to the socket switch (typeId) { case BOOL_TYPE: parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); @@ -413,8 +403,13 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) { parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo)); if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0]; break; + case ERROR_TYPE: + mErrorBitmask = readNextValue<int32_t>(); + mValid = false; + break; default: mValid = false; + break; } } diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h index 41fdcc2cbe7a..dedcfaf6cd87 100644 --- a/cmds/statsd/src/logd/LogEvent.h +++ b/cmds/statsd/src/logd/LogEvent.h @@ -70,7 +70,7 @@ public: explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs, const InstallTrainInfo& installTrainInfo); - ~LogEvent(); + ~LogEvent() {} /** * Get the timestamp associated with this event. @@ -184,6 +184,12 @@ public: return mExclusiveStateFieldIndex; } + // If a reset state is not sent in the StatsEvent, returns -1. Note that a + // reset state is sent if and only if a reset should be triggered. + inline int getResetState() const { + return mResetState; + } + inline LogEvent makeCopy() { return LogEvent(*this); } @@ -204,6 +210,14 @@ public: return BAD_INDEX; } + bool isValid() const { + return mValid; + } + + int32_t getErrorBitmask() const { + return mErrorBitmask; + } + private: /** * Only use this if copy is absolutely needed. @@ -230,12 +244,13 @@ private: bool checkPreviousValueType(Type expected); /** - * The below three variables are only valid during the execution of + * The below two variables are only valid during the execution of * parseBuffer. There are no guarantees about the state of these variables * before/after. */ uint8_t* mBuf; uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed + bool mValid = true; // stores whether the event we received from the socket is valid /** @@ -287,19 +302,15 @@ private: // matching. std::vector<FieldValue> mValues; - // This field is used when statsD wants to create log event object and write fields to it. After - // calling init() function, this object would be destroyed to save memory usage. - // When the log event is created from log msg, this field is never initiated. - android_log_context mContext = NULL; - // The timestamp set by the logd. int64_t mLogdTimestampNs; // The elapsed timestamp set by statsd log writer. int64_t mElapsedTimestampNs; - // The atom tag of the event. - int mTagId; + // The atom tag of the event (defaults to 0 if client does not + // appropriately set the atom id). + int mTagId = 0; // The uid of the logging client (defaults to -1). int32_t mLogUid = -1; @@ -307,11 +318,15 @@ private: // The pid of the logging client (defaults to -1). int32_t mLogPid = -1; + // Bitmask of errors sent by StatsEvent/AStatsEvent. + int32_t mErrorBitmask = 0; + // Annotations bool mTruncateTimestamp = false; int mUidFieldIndex = -1; int mAttributionChainIndex = -1; int mExclusiveStateFieldIndex = -1; + int mResetState = -1; }; void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index a4711e8357f2..f9a8842efc3d 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -109,10 +109,11 @@ private: FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition); FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); - FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade); - FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket); FRIEND_TEST(CountMetricProducerTest, TestFirstBucket); FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit); + + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket); + FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index cc48f99add01..6f84076ee6b5 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -154,12 +154,14 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition); FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState); - FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade); - FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket); - FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade); - FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket); FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket); + + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, + TestSumDurationWithSplitInFollowingBucket); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration); + FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index aa0cae26080d..2eb584b097ea 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -73,18 +73,23 @@ public: bool pullSuccess, int64_t originalPullTimeNs) override; // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) override { + void notifyAppUpgrade(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); if (!mSplitBucketForAppUpgrade) { return; } - if (eventTimeNs > getCurrentBucketEndTimeNs()) { - // Flush full buckets on the normal path up to the latest bucket boundary. - flushIfNeededLocked(eventTimeNs); + flushLocked(eventTimeNs); + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + pullAndMatchEventsLocked(eventTimeNs); } - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + + // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard<std::mutex> lock(mMutex); + + flushLocked(eventTimeNs); if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { pullAndMatchEventsLocked(eventTimeNs); } @@ -190,13 +195,14 @@ private: FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade); - FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade); FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled); FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection); FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket); FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger); FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput); + + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 6aba13ca7859..91c98ea27269 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -141,30 +141,25 @@ public: } /** - * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we - * first call the standard flushing code to flush up to the latest full bucket. Then we call - * the flush again when the end timestamp is forced to be now, and then after flushing, update - * the start timestamp to be now. + * Force a partial bucket split on app upgrade */ - virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) { + virtual void notifyAppUpgrade(const int64_t& eventTimeNs) { std::lock_guard<std::mutex> lock(mMutex); - - if (eventTimeNs > getCurrentBucketEndTimeNs()) { - // Flush full buckets on the normal path up to the latest bucket boundary. - flushIfNeededLocked(eventTimeNs); - } - // Now flush a partial bucket. - flushCurrentBucketLocked(eventTimeNs, eventTimeNs); - // Don't update the current bucket number so that the anomaly tracker knows this bucket - // is a partial bucket and can merge it with the previous bucket. + flushLocked(eventTimeNs); }; - void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { + void notifyAppRemoved(const int64_t& eventTimeNs) { // Force buckets to split on removal also. - notifyAppUpgrade(eventTimeNs, apk, uid, 0); + notifyAppUpgrade(eventTimeNs); }; + /** + * Force a partial bucket split on boot complete. + */ + virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) { + std::lock_guard<std::mutex> lock(mMutex); + flushLocked(eventTimeNs); + } // Consume the parsed stats log entry that already matched the "what" of the metric. void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) { std::lock_guard<std::mutex> lock(mMutex); @@ -292,8 +287,7 @@ public: // End: getters/setters protected: /** - * Flushes the current bucket if the eventTime is after the current bucket's end time. This will - also flush the current partial bucket in memory. + * Flushes the current bucket if the eventTime is after the current bucket's end time. */ virtual void flushIfNeededLocked(const int64_t& eventTime){}; diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp index d832ed86580d..bcca1fd9fdc9 100644 --- a/cmds/statsd/src/metrics/MetricsManager.cpp +++ b/cmds/statsd/src/metrics/MetricsManager.cpp @@ -231,8 +231,8 @@ bool MetricsManager::isConfigValid() const { void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, const int64_t version) { // Inform all metric producers. - for (auto it : mAllMetricProducers) { - it->notifyAppUpgrade(eventTimeNs, apk, uid, version); + for (const auto& it : mAllMetricProducers) { + it->notifyAppUpgrade(eventTimeNs); } // check if we care this package if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { @@ -252,8 +252,8 @@ void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) { // Inform all metric producers. - for (auto it : mAllMetricProducers) { - it->notifyAppRemoved(eventTimeNs, apk, uid); + for (const auto& it : mAllMetricProducers) { + it->notifyAppRemoved(eventTimeNs); } // check if we care this package if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) { @@ -282,6 +282,13 @@ void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) { initLogSourceWhiteList(); } +void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) { + // Inform all metric producers. + for (const auto& it : mAllMetricProducers) { + it->onStatsdInitCompleted(eventTimeNs); + } +} + void MetricsManager::init() { for (const auto& producer : mAllMetricProducers) { producer->prepareFirstBucket(); diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h index 1fd6572cc760..ef03d2064ab0 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -70,6 +70,8 @@ public: void onUidMapReceived(const int64_t& eventTimeNs); + void onStatsdInitCompleted(const int64_t& elapsedTimeNs); + void init(); vector<int32_t> getPullAtomUids(int32_t atomId) override; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index e9273dc54424..c8dc8cc290c4 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -69,8 +69,7 @@ public: bool pullSuccess, int64_t originalPullTimeNs) override; // ValueMetric needs special logic if it's a pulled atom. - void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, - const int64_t version) override { + void notifyAppUpgrade(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); if (!mSplitBucketForAppUpgrade) { return; @@ -81,6 +80,15 @@ public: flushCurrentBucketLocked(eventTimeNs, eventTimeNs); }; + // ValueMetric needs special logic if it's a pulled atom. + void onStatsdInitCompleted(const int64_t& eventTimeNs) override { + std::lock_guard<std::mutex> lock(mMutex); + if (mIsPulled && mCondition) { + pullAndMatchEventsLocked(eventTimeNs); + } + flushCurrentBucketLocked(eventTimeNs, eventTimeNs); + }; + void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey, int oldState, int newState) override; @@ -256,7 +264,6 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection); FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange); - FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition); FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition); @@ -269,10 +276,8 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled); FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); - FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid); FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff); FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff); - FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated); FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries); FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse); FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue); @@ -283,15 +288,12 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering); - FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade); - FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse); FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition); - FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade); FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition); FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded); FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange); @@ -313,6 +315,14 @@ private: FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit); FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenAccumulateEventWrongBucket); + + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue); + FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse); + friend class ValueMetricProducerTestHelper; }; diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp index b7f314a819df..b63713b64c5d 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -51,7 +51,7 @@ void StateTracker::onLogEvent(const LogEvent& event) { return; } - const int32_t resetState = stateValue.mAnnotations.getResetState(); + const int32_t resetState = event.getResetState(); if (resetState != -1) { VLOG("StateTracker new reset state: %d", resetState); handleReset(eventTimeNs, resetState); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index f4247eca6e23..868247bc9d64 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -425,6 +425,7 @@ message StatsdStatsReport { message AtomStats { optional int32 tag = 1; optional int32 count = 2; + optional int32 error_count = 3; } repeated AtomStats atom_stats = 7; @@ -460,6 +461,7 @@ message StatsdStatsReport { optional int64 empty_data = 15; optional int64 registered_count = 16; optional int64 unregistered_count = 17; + optional int32 atom_error_count = 18; } repeated PulledAtomStats pulled_atom_stats = 10; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 563531351fe0..f9fddc8a7c8a 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -80,6 +80,8 @@ const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14; const int FIELD_ID_EMPTY_DATA = 15; const int FIELD_ID_PULL_REGISTERED_COUNT = 16; const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17; +const int FIELD_ID_ATOM_ERROR_COUNT = 18; + // for AtomMetricStats proto const int FIELD_ID_ATOM_METRIC_STATS = 17; const int FIELD_ID_METRIC_ID = 1; @@ -492,6 +494,7 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats> (long long) pair.second.registeredCount); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT, (long long) pair.second.unregisteredCount); + protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount); protoOutput->end(token); } diff --git a/cmds/statsd/src/utils/NamedLatch.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp index 6e77977857cc..43a69337f368 100644 --- a/cmds/statsd/src/utils/NamedLatch.cpp +++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp @@ -15,7 +15,9 @@ */ #define DEBUG false // STOPSHIP if true -#include "NamedLatch.h" +#include "MultiConditionTrigger.h" + +#include <thread> using namespace std; @@ -23,26 +25,33 @@ namespace android { namespace os { namespace statsd { -NamedLatch::NamedLatch(const set<string>& eventNames) : mRemainingEventNames(eventNames) { +MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames, + function<void()> trigger) + : mRemainingConditionNames(conditionNames), + mTrigger(trigger), + mCompleted(mRemainingConditionNames.empty()) { + if (mCompleted) { + thread executorThread([this] { mTrigger(); }); + executorThread.detach(); + } } -void NamedLatch::countDown(const string& eventName) { - bool notify = false; +void MultiConditionTrigger::markComplete(const string& conditionName) { + bool doTrigger = false; { lock_guard<mutex> lg(mMutex); - mRemainingEventNames.erase(eventName); - notify = mRemainingEventNames.empty(); + if (mCompleted) { + return; + } + mRemainingConditionNames.erase(conditionName); + mCompleted = mRemainingConditionNames.empty(); + doTrigger = mCompleted; } - if (notify) { - mConditionVariable.notify_all(); + if (doTrigger) { + std::thread executorThread([this] { mTrigger(); }); + executorThread.detach(); } } - -void NamedLatch::wait() const { - unique_lock<mutex> unique_lk(mMutex); - mConditionVariable.wait(unique_lk, [this] { return mRemainingEventNames.empty(); }); -} - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.h b/cmds/statsd/src/utils/MultiConditionTrigger.h new file mode 100644 index 000000000000..51f6029915be --- /dev/null +++ b/cmds/statsd/src/utils/MultiConditionTrigger.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include <gtest/gtest_prod.h> + +#include <mutex> +#include <set> + +namespace android { +namespace os { +namespace statsd { + +/** + * This class provides a utility to wait for a set of named conditions to occur. + * + * It will execute the trigger runnable in a detached thread once all conditions have been marked + * true. + */ +class MultiConditionTrigger { +public: + explicit MultiConditionTrigger(const std::set<std::string>& conditionNames, + std::function<void()> trigger); + + MultiConditionTrigger(const MultiConditionTrigger&) = delete; + MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete; + + // Mark a specific condition as true. If this condition has called markComplete already or if + // the event was not specified in the constructor, the function is a no-op. + void markComplete(const std::string& eventName); + +private: + mutable std::mutex mMutex; + std::set<std::string> mRemainingConditionNames; + std::function<void()> mTrigger; + bool mCompleted; + + FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName); +}; +} // namespace statsd +} // namespace os +} // namespace android diff --git a/cmds/statsd/src/utils/NamedLatch.h b/cmds/statsd/src/utils/NamedLatch.h deleted file mode 100644 index 70238370f647..000000000000 --- a/cmds/statsd/src/utils/NamedLatch.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once - -#include <gtest/gtest_prod.h> - -#include <condition_variable> -#include <mutex> -#include <set> - -namespace android { -namespace os { -namespace statsd { - -/** - * This class provides a threading primitive similar to a latch. - * The primary difference is that it waits for named events to occur instead of waiting for - * N threads to reach a certain point. - * - * It uses a condition variable under the hood. - */ -class NamedLatch { -public: - explicit NamedLatch(const std::set<std::string>& eventNames); - - NamedLatch(const NamedLatch&) = delete; - NamedLatch& operator=(const NamedLatch&) = delete; - - // Mark a specific event as completed. If this event has called countDown already or if the - // event was not specified in the constructor, the function is a no-op. - void countDown(const std::string& eventName); - - // Blocks the calling thread until all events in eventNames have called countDown. - void wait() const; - -private: - mutable std::mutex mMutex; - mutable std::condition_variable mConditionVariable; - std::set<std::string> mRemainingEventNames; - - FRIEND_TEST(NamedLatchTest, TestCountDownCalledBySameEventName); -}; -} // namespace statsd -} // namespace os -} // namespace android diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp index bb4578d9b701..e52e2d024e94 100644 --- a/cmds/statsd/tests/LogEvent_test.cpp +++ b/cmds/statsd/tests/LogEvent_test.cpp @@ -352,7 +352,7 @@ TEST(LogEventTest, TestResetStateAnnotation) { const vector<FieldValue>& values = event.getValues(); EXPECT_EQ(values.size(), 1); - EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState); + EXPECT_EQ(event.getResetState(), resetState); } } // namespace statsd diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp index b173ee0334a7..911762339b70 100644 --- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp +++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp @@ -89,6 +89,7 @@ StatsdConfig MakeValueMetricConfig(int64_t minTime) { valueMetric->set_bucket(FIVE_MINUTES); valueMetric->set_min_bucket_size_nanos(minTime); valueMetric->set_use_absolute_value_on_reset(true); + valueMetric->set_skip_zero_diff_output(false); return config; } @@ -217,6 +218,35 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) { EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); } +TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + SendConfig(service, MakeConfig()); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + // Goes into the first bucket + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get()); + int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + // Goes into the second bucket. + service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get()); + + ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size()); + EXPECT_TRUE(report.metrics(0) + .count_metrics() + .data(0) + .bucket_info(0) + .has_start_bucket_elapsed_nanos()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos()); + EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count()); +} + TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); service->mPullerManager->RegisterPullAtomCallback( @@ -229,13 +259,22 @@ TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) { // initialized with. service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); - service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, - String16("v2"), String16("")); + int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; + service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"), + String16("")); ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); + backfillStartEndTimestamp(&report); + EXPECT_EQ(1, report.metrics_size()); EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { @@ -249,13 +288,13 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. - const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2; + const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC; service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); @@ -264,10 +303,49 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) { // Can't test the start time since it will be based on the actual time when the pulling occurs. EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + EXPECT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + // Initial pull will fail since puller is not registered. + SendConfig(service, MakeValueMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + // First bucket is dropped due to the initial pull failing + ASSERT_EQ(1, report.metrics_size()); + EXPECT_EQ(1, report.metrics(0).value_metrics().skipped_size()); + EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos()); + + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).value_metrics().data_size()); + ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); SendConfig(service, MakeGaugeMetricConfig(0)); @@ -278,16 +356,22 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) { service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2, String16("v2"), String16("")); - ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true); - EXPECT_EQ(1, report.metrics_size()); + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + ASSERT_EQ(1, report.metrics_size()); EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + EXPECT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); } TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); // Partial buckets don't occur when app is first installed. service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16("")); + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */)); int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are // initialized with. @@ -298,7 +382,7 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { String16("")); ConfigMetricsReport report = - GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true); + GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC); backfillStartEndTimestamp(&report); ASSERT_EQ(1, report.metrics_size()); ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size()); @@ -306,6 +390,38 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) { EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos()); EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)), report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos()); + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + EXPECT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); +} + +TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) { + shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr); + // Initial pull will fail since puller hasn't been registered. + SendConfig(service, MakeGaugeMetricConfig(0)); + int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are + // initialized with. + + service->mPullerManager->RegisterPullAtomCallback( + /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {}, + SharedRefBase::make<FakeSubsystemSleepCallback>()); + + int64_t bootCompleteTimeNs = start + NS_PER_SEC; + service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs); + + service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start); + + ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100); + backfillStartEndTimestamp(&report); + + ASSERT_EQ(1, report.metrics_size()); + EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size()); + // The fake subsystem state sleep puller returns two atoms. + ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size()); + // No data in the first bucket, so nothing is reported + ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size()); + EXPECT_EQ( + MillisToNano(NanoToMillis(bootCompleteTimeNs)), + report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos()); } #else diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 129fafa75b41..cdde603f4c0b 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -486,6 +486,41 @@ TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) { EXPECT_TRUE(uid2Good); } +TEST(StatsdStatsTest, TestAtomErrorStats) { + StatsdStats stats; + + int pushAtomTag = 100; + int pullAtomTag = 1000; + int numErrors = 10; + + for (int i = 0; i < numErrors; i++) { + // We must call noteAtomLogged as well because only those pushed atoms + // that have been logged will have stats printed about them in the + // proto. + stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0); + stats.noteAtomError(pushAtomTag, /*pull=*/false); + + stats.noteAtomError(pullAtomTag, /*pull=*/true); + } + + vector<uint8_t> output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + EXPECT_TRUE(report.ParseFromArray(&output[0], output.size())); + + // Check error count = numErrors for push atom + EXPECT_EQ(1, report.atom_stats_size()); + const auto& pushedAtomStats = report.atom_stats(0); + EXPECT_EQ(pushAtomTag, pushedAtomStats.tag()); + EXPECT_EQ(numErrors, pushedAtomStats.error_count()); + + // Check error count = numErrors for pull atom + EXPECT_EQ(1, report.pulled_atom_stats_size()); + const auto& pulledAtomStats = report.pulled_atom_stats(0); + EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id()); + EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 65f8de69711d..8131725cd148 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -38,9 +38,9 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, 12345); namespace { +const ConfigKey kConfigKey(0, 12345); void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { AStatsEvent* statsEvent = AStatsEvent_obtain(); @@ -61,6 +61,13 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string ui } // namespace +// Setup for parameterized tests. +class CountMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket, + CountMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + TEST(CountMetricProducerTest, TestFirstBucket) { CountMetric metric; metric.set_id(1); @@ -237,11 +244,11 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { EXPECT_EQ(1LL, bucketInfo.mCount); } -TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) { sp<AlarmMonitor> alarmMonitor; int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; int tagId = 1; int conditionTagId = 2; @@ -260,22 +267,30 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); - // Bucket is flushed yet. + // Bucket is not flushed yet. LogEvent event1(/*uid=*/0, /*pid=*/0); makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); - // App upgrade forces bucket flush. + // App upgrade or boot complete forces bucket flush. // Check that there's a past bucket and the bucket end is not adjusted. - countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((long long)bucketStartTimeNs, + EXPECT_EQ(bucketStartTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); - EXPECT_EQ((long long)eventUpgradeTimeNs, + EXPECT_EQ(eventTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); // Anomaly tracker only contains full buckets. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); @@ -285,7 +300,8 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, countProducer.getCurrentBucketNum()); EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); // Third event in following bucket. @@ -294,13 +310,14 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, countProducer.getCurrentBucketNum()); EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { +TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; int tagId = 1; int conditionTagId = 2; @@ -319,15 +336,23 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(0UL, countProducer.mPastBuckets.size()); - // App upgrade forces bucket flush. - // Check that there's a past bucket and the bucket end is not adjusted. - countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + // App upgrade or boot complete forces bucket flush. + // Check that there's a past bucket and the bucket end is not adjusted since the upgrade + // occurred after the bucket end time. + switch (GetParam()) { + case APP_UPGRADE: + countProducer.notifyAppUpgrade(eventTimeNs); + break; + case BOOT_COMPLETE: + countProducer.onStatsdInitCompleted(eventTimeNs); + break; + } EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)bucketStartTimeNs, + EXPECT_EQ(bucketStartTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs); // Next event occurs in same bucket as partial bucket created. LogEvent event2(/*uid=*/0, /*pid=*/0); @@ -340,7 +365,7 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333"); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, + EXPECT_EQ((int64_t)eventTimeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs); diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 30f815962160..8ef251952db7 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -41,10 +41,10 @@ namespace android { namespace os { namespace statsd { -const ConfigKey kConfigKey(0, 12345); namespace { +const ConfigKey kConfigKey(0, 12345); void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { AStatsEvent* statsEvent = AStatsEvent_obtain(); AStatsEvent_setAtomId(statsEvent, atomId); @@ -55,6 +55,13 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) { } // namespace +// Setup for parameterized tests. +class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket, + DurationMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + TEST(DurationMetricTrackerTest, TestFirstBucket) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); DurationMetric metric; @@ -205,7 +212,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) { EXPECT_EQ(1LL, buckets2[0].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) { /** * The duration starts from the first bucket, through the two partial buckets (10-70sec), * another bucket, and ends at the beginning of the next full bucket. @@ -217,15 +224,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { */ int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); DurationMetric metric; metric.set_id(1); @@ -238,32 +237,47 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs); - EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(3UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); EXPECT_EQ(bucketSizeNs, buckets[2].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) { /** * Expected buckets (start at 11s, upgrade at 75s, end at 135s): * - [10,70]: 59 secs @@ -272,15 +286,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { */ int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); DurationMetric metric; metric.set_id(1); @@ -293,11 +299,22 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; @@ -305,32 +322,29 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs); - EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs); - EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs); + EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(3UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs); - EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration); + EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs, + buckets[2].mDuration); } -TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) { sp<AlarmMonitor> alarmMonitor; int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); // Setup metric with alert. DurationMetric metric; @@ -351,27 +365,35 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); + EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); DurationMetric metric; metric.set_id(1); @@ -385,15 +407,30 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, durationProducer.getCurrentBucketNum()); // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket. + int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); @@ -406,18 +443,10 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } -TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { +TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; - int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; - int64_t startTimeNs = bucketStartTimeNs + 1; - int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; - int tagId = 1; - LogEvent event1(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event1, startTimeNs, tagId); - LogEvent event2(/*uid=*/0, /*pid=*/0); - makeLogEvent(&event2, endTimeNs, tagId); DurationMetric metric; metric.set_id(1); @@ -431,24 +460,39 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); + int64_t startTimeNs = bucketStartTimeNs + 1; + LogEvent event1(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event1, startTimeNs, tagId); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); EXPECT_EQ(0UL, durationProducer.mPastBuckets.size()); EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs); - durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC; + switch (GetParam()) { + case APP_UPGRADE: + durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, durationProducer.getCurrentBucketNum()); // Stop occurs in the same partial bucket as created for the app upgrade. + int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC; + LogEvent event2(/*uid=*/0, /*pid=*/0); + makeLogEvent(&event2, endTimeNs, tagId); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs); durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1); std::vector<DurationBucket> buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY]; EXPECT_EQ(1UL, buckets.size()); - EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs); EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs); EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration); } diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 42d0d5d8c530..9d2ec88e2f9b 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -42,6 +42,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; @@ -52,9 +54,8 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs; const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; -const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; +const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; -namespace { shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1, int32_t value2) { AStatsEvent* statsEvent = AStatsEvent_obtain(); @@ -71,6 +72,13 @@ shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t v } } // anonymous namespace +// Setup for parameterized tests. +class GaugeMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket, + GaugeMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + /* * Tests that the first bucket works correctly */ @@ -194,7 +202,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(25L, it->mValue.int_value); } -TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) { sp<AlarmMonitor> alarmMonitor; GaugeMetric metric; metric.set_id(metricId); @@ -230,11 +238,22 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY)); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); // Partial buckets are not sent to anomaly tracker. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); @@ -244,7 +263,11 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); + EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); // Partial buckets are not sent to anomaly tracker. EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); @@ -267,7 +290,7 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) { EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY)); } -TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { +TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) { GaugeMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -293,7 +316,8 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { .WillOnce(Invoke( [](int tagId, const ConfigKey&, vector<std::shared_ptr<LogEvent>>* data, bool) { data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, eventUpgradeTimeNs, 2)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 2)); return true; })); @@ -311,10 +335,21 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { .mFields->begin() ->mValue.int_value); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + switch (GetParam()) { + case APP_UPGRADE: + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + EXPECT_EQ(bucketStartTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, + gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); - EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -370,7 +405,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) { .mFields->begin() ->mValue.int_value); - gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1); + gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs); EXPECT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum); EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 3b4d646f0f2f..f493cc4033ad 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -41,6 +41,8 @@ namespace android { namespace os { namespace statsd { +namespace { + const ConfigKey kConfigKey(0, 12345); const int tagId = 1; const int64_t metricId = 123; @@ -58,10 +60,18 @@ double epsilon = 0.001; static void assertPastBucketValuesSingleKey( const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets, const std::initializer_list<int>& expectedValuesList, - const std::initializer_list<int64_t>& expectedDurationNsList) { - std::vector<int> expectedValues(expectedValuesList); - std::vector<int64_t> expectedDurationNs(expectedDurationNsList); + const std::initializer_list<int64_t>& expectedDurationNsList, + const std::initializer_list<int64_t>& expectedStartTimeNsList, + const std::initializer_list<int64_t>& expectedEndTimeNsList) { + vector<int> expectedValues(expectedValuesList); + vector<int64_t> expectedDurationNs(expectedDurationNsList); + vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList); + vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList); + ASSERT_EQ(expectedValues.size(), expectedDurationNs.size()); + ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size()); + ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size()); + if (expectedValues.size() == 0) { ASSERT_EQ(0, mPastBuckets.size()); return; @@ -70,15 +80,21 @@ static void assertPastBucketValuesSingleKey( ASSERT_EQ(1, mPastBuckets.size()); ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size()); - auto buckets = mPastBuckets.begin()->second; + const vector<ValueBucket>& buckets = mPastBuckets.begin()->second; for (int i = 0; i < expectedValues.size(); i++) { EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value) << "Values differ at index " << i; EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs) << "Condition duration value differ at index " << i; + EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs) + << "Start time differs at index " << i; + EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs) + << "End time differs at index " << i; } } +} // anonymous namespace + class ValueMetricProducerTestHelper { public: static sp<ValueMetricProducer> createValueProducerNoConditions( @@ -191,6 +207,13 @@ public: } }; +// Setup for parameterized tests. +class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {}; + +INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket, + ValueMetricProducerTest_PartialBucket, + testing::Values(APP_UPGRADE, BOOT_COMPLETE)); + /* * Tests that the first bucket works correctly */ @@ -325,9 +348,10 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs); } -TEST(ValueMetricProducerTest, TestPartialBucketCreated) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) // Initialize bucket. .WillOnce(Invoke([](int tagId, const ConfigKey&, @@ -337,10 +361,12 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) { return true; })) // Partial bucket. - .WillOnce(Invoke([](int tagId, const ConfigKey&, - vector<std::shared_ptr<LogEvent>>* data, bool) { + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + vector<std::shared_ptr<LogEvent>>* data, + bool) { data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5)); + data->push_back( + CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5)); return true; })); @@ -354,19 +380,21 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) { valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs); // Partial buckets created in 2nd bucket. - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer->getCurrentBucketNum()); - // One full bucket and one partial bucket. - EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second; - EXPECT_EQ(2UL, buckets.size()); - // Full bucket (2 - 1) - EXPECT_EQ(1, buckets[0].values[0].long_value); - EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs); - // Full bucket (5 - 3) - EXPECT_EQ(3, buckets[1].values[0].long_value); - // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2] - EXPECT_EQ(2, buckets[1].mConditionTrueNs); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3}, + {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}); } /* @@ -613,7 +641,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { allData.clear(); allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); @@ -625,7 +654,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_EQ(10, curInterval.value.long_value); valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); // has one slice EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); @@ -636,10 +666,12 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_EQ(false, curBaseInfo.hasBase); valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1}, + {bucketStartTimeNs, bucket2StartTimeNs}, + {bucket2StartTimeNs, bucket3StartTimeNs}); } -TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; @@ -660,25 +692,46 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); + // Event arrives after the bucket split. LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); + + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer.getCurrentBucketNum()); // Next value should create a new bucket. LogEvent event3(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10); + CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20}, + {partialBucketSplitTimeNs - bucketStartTimeNs, + bucket2StartTimeNs - partialBucketSplitTimeNs}, + {bucketStartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket2StartTimeNs}); EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); UidMap uidMap; @@ -689,14 +742,16 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150; EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) .WillOnce(Return(true)) - .WillOnce(Invoke([](int tagId, const ConfigKey&, - vector<std::shared_ptr<LogEvent>>* data, bool) { + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + vector<std::shared_ptr<LogEvent>>* data, + bool) { data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 149, 120)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120)); return true; })); ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex, @@ -711,20 +766,27 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); - EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(1, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs}, + {partialBucketSplitTimeNs}); allData.clear(); allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150)); valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); - EXPECT_EQ(20L, - valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, - {150, bucketSizeNs - 150}); + EXPECT_EQ(2, valueProducer.getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150}, + {bucket2StartTimeNs, partialBucketSplitTimeNs}, + {partialBucketSplitTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { @@ -754,12 +816,12 @@ TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); - valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); + valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150); EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs); } -TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); @@ -784,14 +846,21 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100); EXPECT_FALSE(valueProducer->mCondition); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs - 50, "ANY.APP", 1, 1); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50; + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Expect one full buckets already done and starting a partial bucket. - EXPECT_EQ(bucket2StartTimeNs - 50, valueProducer->mCurrentBucketStartTimeNs); - EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); - EXPECT_EQ(bucketStartTimeNs, - valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, - {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}); + {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); EXPECT_FALSE(valueProducer->mCondition); } TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { @@ -834,7 +903,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { EXPECT_EQ(30, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { @@ -895,7 +965,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { EXPECT_EQ(50, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestAnomalyDetection) { @@ -1012,7 +1083,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(23, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); // pull 3 come late. // The previous bucket gets closed with error. (Has start value 23, no ending) @@ -1028,7 +1100,15 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(36, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); + // The 3rd bucket is dropped due to multiple buckets being skipped. + ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); + EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket4StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); + ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size()); + EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason); + EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs); } /* @@ -1073,7 +1153,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); EXPECT_EQ(false, curBaseInfo.hasBase); // Now the alarm is delivered. @@ -1082,7 +1163,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); @@ -1090,10 +1172,9 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { } /* -* Test pulled event with non sliced condition. The pull on boundary come late, after the -condition -* change to false, and then true again. This is due to alarm delivered late. -*/ + * Test pulled event with non sliced condition. The pull on boundary come late, after the condition + * change to false, and then true again. This is due to alarm delivered late. + */ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); @@ -1139,7 +1220,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // pull on bucket boundary come late, condition change happens before it valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; @@ -1148,7 +1230,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { // condition changed to true again, before the pull alarm is delivered valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); @@ -1167,13 +1250,15 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { EXPECT_EQ(140, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); EXPECT_EQ(10, curInterval.value.long_value); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30}, - {bucketSizeNs - 8, bucketSizeNs - 24}); + assertPastBucketValuesSingleKey( + valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24}, + {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMin) { @@ -1216,7 +1301,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { EXPECT_EQ(10, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateMax) { @@ -1239,9 +1325,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { LogEvent event1(/*uid=*/0, /*pid=*/0); CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); // has one slice @@ -1251,6 +1334,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { EXPECT_EQ(10, curInterval.value.long_value); EXPECT_EQ(true, curInterval.hasValue); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice @@ -1258,10 +1343,9 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(20, curInterval.value.long_value); - valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */ - /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */ - /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */ + valueProducer.flushIfNeededLocked(bucket2StartTimeNs); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { @@ -1351,7 +1435,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { EXPECT_EQ(25, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { @@ -1375,10 +1460,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { LogEvent event1(/*uid=*/0, /*pid=*/0); CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10); - - LogEvent event2(/*uid=*/0, /*pid=*/0); - CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); + // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = @@ -1388,6 +1471,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { EXPECT_EQ(10, curBaseInfo.base.long_value); EXPECT_EQ(false, curInterval.hasValue); + LogEvent event2(/*uid=*/0, /*pid=*/0); + CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2); // has one slice @@ -1400,12 +1485,14 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { LogEvent event3(/*uid=*/0, /*pid=*/0); CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); LogEvent event4(/*uid=*/0, /*pid=*/0); CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15); @@ -1416,11 +1503,11 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) { EXPECT_EQ(true, curBaseInfo.hasBase); EXPECT_EQ(15, curBaseInfo.base.long_value); EXPECT_EQ(true, curInterval.hasValue); + EXPECT_EQ(0, curInterval.value.long_value); valueProducer.flushIfNeededLocked(bucket3StartTimeNs); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); - assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) { @@ -1740,8 +1827,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(3, baseInfo1.base.long_value); EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(0UL, valueProducer->mPastBuckets.size()); - vector<shared_ptr<LogEvent>> allData; + vector<shared_ptr<LogEvent>> allData; allData.clear(); allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4)); allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11)); @@ -1753,7 +1840,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(false, interval1.hasValue); EXPECT_EQ(8, interval1.value.long_value); EXPECT_FALSE(interval1.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); auto it = valueProducer->mCurrentSlicedBucket.begin(); for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) { @@ -1769,14 +1857,13 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { } EXPECT_TRUE(it != iter); EXPECT_TRUE(itBase != iterBase); - auto& interval2 = it->second[0]; - auto& baseInfo2 = itBase->second[0]; + auto interval2 = it->second[0]; + auto baseInfo2 = itBase->second[0]; EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value); EXPECT_EQ(true, baseInfo2.hasBase); EXPECT_EQ(4, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); // next pull somehow did not happen, skip to end of bucket 3 allData.clear(); @@ -1791,7 +1878,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(5, baseInfo2.base.long_value); EXPECT_EQ(false, interval2.hasValue); EXPECT_FALSE(interval2.seenNewData); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); allData.clear(); allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14)); @@ -1805,9 +1893,13 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_FALSE(interval2.seenNewData); ASSERT_EQ(2UL, valueProducer->mPastBuckets.size()); auto iterator = valueProducer->mPastBuckets.begin(); + EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(9, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); iterator++; + EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs); + EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs); EXPECT_EQ(8, iterator->second[0].values[0].long_value); EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs); } @@ -2414,7 +2506,8 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) { EXPECT_EQ(true, valueProducer->mHasGlobalBase); EXPECT_EQ(1UL, valueProducer->mPastBuckets.size()); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { @@ -2461,10 +2554,11 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) { EXPECT_EQ(true, valueProducer->mHasGlobalBase); } -TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2; EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) // Initialization. .WillOnce(Invoke( @@ -2474,23 +2568,41 @@ TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) { return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, const ConfigKey&, - vector<std::shared_ptr<LogEvent>>* data, bool) { + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + vector<std::shared_ptr<LogEvent>>* data, + bool) { data->clear(); - data->push_back(CreateRepeatedValueLogEvent( - tagId, bucketStartTimeNs + bucketSizeNs / 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); - valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs); + EXPECT_EQ(0, valueProducer->getCurrentBucketNum()); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size()); vector<shared_ptr<LogEvent>> allData; allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4)); valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, + {partialBucketSplitTimeNs - bucketStartTimeNs}, + {bucketStartTimeNs}, {partialBucketSplitTimeNs}); + ASSERT_EQ(1, valueProducer->mSkippedBuckets.size()); + EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs); + EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs); ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size()); } @@ -2537,7 +2649,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) { valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10}, + {bucket2StartTimeNs}, {bucket3StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { @@ -2557,7 +2670,8 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { @@ -2585,12 +2699,14 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } -TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { +TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) { ValueMetric metric = ValueMetricProducerTestHelper::createMetric(); + int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2; sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _)) // Initialization. @@ -2601,20 +2717,29 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) { return true; })) // notifyAppUpgrade. - .WillOnce(Invoke([](int tagId, const ConfigKey&, - vector<std::shared_ptr<LogEvent>>* data, bool) { + .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&, + vector<std::shared_ptr<LogEvent>>* data, + bool) { data->clear(); - data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 2, 10)); + data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10)); return true; })); sp<ValueMetricProducer> valueProducer = ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric); - valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1); + switch (GetParam()) { + case APP_UPGRADE: + valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs); + break; + case BOOT_COMPLETE: + valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs); + break; + } // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { @@ -2642,7 +2767,7 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { valueProducer->onConditionChanged(true, bucketStartTimeNs + 8); valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); - valueProducer->onConditionChanged(false, bucketStartTimeNs + 10); + valueProducer->onConditionChanged(false, bucketStartTimeNs + 12); EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size()); auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; @@ -2654,7 +2779,8 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs}, + {bucket2StartTimeNs}); } // TODO: b/145705635 fix or delete this test @@ -2705,7 +2831,7 @@ TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // There was not global base available so all buckets are invalid. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPullNeededFastDump) { @@ -2849,7 +2975,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30); // Bucket should have been completed. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) { @@ -2892,7 +3019,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; EXPECT_EQ(false, curBaseInfo.hasBase); @@ -2923,7 +3051,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) { allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30)); valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8}, + {bucketStartTimeNs}, {bucket2StartTimeNs}); ValueMetricProducer::Interval curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0]; ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0]; @@ -2946,7 +3075,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // Condition was always false. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { @@ -2976,7 +3105,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) { valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs); // No buckets, we had a failure. - assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}); + assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {}); } /* diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h index 37b98891797a..4d68ea2ecb79 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -42,6 +42,8 @@ using Status = ::ndk::ScopedAStatus; const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED; const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED; +enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE }; + // Converts a ProtoOutputStream to a StatsLogReport proto. StatsLogReport outputStreamToProto(ProtoOutputStream* proto); diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp new file mode 100644 index 000000000000..db402a0dd658 --- /dev/null +++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "utils/MultiConditionTrigger.h" + +#include <gtest/gtest.h> + +#include <chrono> +#include <set> +#include <thread> +#include <vector> + +#ifdef __ANDROID__ + +using namespace std; +using std::this_thread::sleep_for; + +namespace android { +namespace os { +namespace statsd { + +TEST(MultiConditionTrigger, TestMultipleConditions) { + int numConditions = 5; + string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; + set<string> conditionNames = {t1, t2, t3, t4, t5}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + // Mark done as true and notify in the done. + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + vector<thread> threads; + vector<bool> done(numConditions, false); + + int i = 0; + for (const string& conditionName : conditionNames) { + threads.emplace_back([&done, &conditionName, &trigger, i] { + sleep_for(chrono::milliseconds(3)); + done[i] = true; + trigger.markComplete(conditionName); + }); + i++; + } + + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { + return triggerCalled; + }); + + for (i = 0; i < numConditions; i++) { + EXPECT_EQ(done[i], 1); + } + + for (i = 0; i < numConditions; i++) { + threads[i].join(); + } +} + +TEST(MultiConditionTrigger, TestNoConditions) { + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + // Ensure that trigger occurs immediately if no events need to be completed. +} + +TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) { + string t1 = "t1", t2 = "t2"; + set<string> conditionNames = {t1, t2}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] { + { + lock_guard lg(lock); + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + trigger.markComplete(t1); + + // Ensure that the trigger still hasn't fired. + { + lock_guard lg(lock); + EXPECT_FALSE(triggerCalled); + } + + trigger.markComplete(t2); + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); +} + +TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) { + string t1 = "t1"; + set<string> conditionNames = {t1}; + + mutex lock; + condition_variable cv; + bool triggerCalled = false; + int triggerCount = 0; + + MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] { + { + lock_guard lg(lock); + triggerCount++; + triggerCalled = true; + } + cv.notify_all(); + }); + + trigger.markComplete(t1); + + // Ensure that the trigger fired. + { + unique_lock<mutex> unique_lk(lock); + cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; }); + EXPECT_TRUE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + triggerCalled = false; + } + + trigger.markComplete(t1); + + // Ensure that the trigger does not fire again. + { + unique_lock<mutex> unique_lk(lock); + cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; }); + EXPECT_FALSE(triggerCalled); + EXPECT_EQ(triggerCount, 1); + } +} + +} // namespace statsd +} // namespace os +} // namespace android +#else +GTEST_LOG_(INFO) << "This test does nothing.\n"; +#endif diff --git a/cmds/statsd/tests/utils/NamedLatch_test.cpp b/cmds/statsd/tests/utils/NamedLatch_test.cpp deleted file mode 100644 index de48a133823e..000000000000 --- a/cmds/statsd/tests/utils/NamedLatch_test.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "utils/NamedLatch.h" - -#include <gtest/gtest.h> - -#include <chrono> -#include <set> -#include <thread> -#include <vector> - -#ifdef __ANDROID__ - -using namespace std; -using std::this_thread::sleep_for; - -namespace android { -namespace os { -namespace statsd { - -TEST(NamedLatchTest, TestWait) { - int numEvents = 5; - string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5"; - set<string> eventNames = {t1, t2, t3, t4, t5}; - - NamedLatch latch(eventNames); - vector<thread> threads; - vector<bool> done(numEvents, false); - - int i = 0; - for (const string& eventName : eventNames) { - threads.emplace_back([&done, &eventName, &latch, i] { - sleep_for(chrono::milliseconds(3)); - done[i] = true; - latch.countDown(eventName); - }); - i++; - } - - latch.wait(); - - for (i = 0; i < numEvents; i++) { - EXPECT_EQ(done[i], 1); - } - - for (i = 0; i < numEvents; i++) { - threads[i].join(); - } -} - -TEST(NamedLatchTest, TestNoWorkers) { - NamedLatch latch({}); - latch.wait(); - // Ensure that latch does not wait if no events need to countDown. -} - -TEST(NamedLatchTest, TestCountDownCalledBySameEventName) { - string t1 = "t1", t2 = "t2"; - set<string> eventNames = {t1, t2}; - - NamedLatch latch(eventNames); - - thread waiterThread([&latch] { latch.wait(); }); - - latch.countDown(t1); - latch.countDown(t1); - - // Ensure that the latch's remaining threads still has t2. - latch.mMutex.lock(); - ASSERT_EQ(latch.mRemainingEventNames.size(), 1); - EXPECT_NE(latch.mRemainingEventNames.find(t2), latch.mRemainingEventNames.end()); - latch.mMutex.unlock(); - - latch.countDown(t2); - waiterThread.join(); -} - -} // namespace statsd -} // namespace os -} // namespace android -#else -GTEST_LOG_(INFO) << "This test does nothing.\n"; -#endif diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java index 9a3dad2eb92f..623734ee5662 100644 --- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java @@ -30,7 +30,6 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.util.AttributeSet; import android.util.Xml; @@ -153,7 +152,7 @@ public final class AccessibilityShortcutInfo { com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity); asAttributes.recycle(); - if (mDescriptionResId == 0 || mSummaryResId == 0) { + if ((mDescriptionResId == 0 && mHtmlDescriptionRes == 0) || mSummaryResId == 0) { throw new XmlPullParserException("No description or summary in meta-data"); } } catch (PackageManager.NameNotFoundException e) { @@ -243,7 +242,10 @@ public final class AccessibilityShortcutInfo { public String loadHtmlDescription(@NonNull PackageManager packageManager) { final String htmlDescription = loadResourceString(packageManager, mActivityInfo, mHtmlDescriptionRes); - return TextUtils.isEmpty(htmlDescription) ? null : getFilteredHtmlText(htmlDescription); + if (htmlDescription != null) { + return getFilteredHtmlText(htmlDescription); + } + return null; } /** diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index 1b941defba42..1cc63da3db0a 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -23,8 +23,10 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -430,4 +432,14 @@ public class ActivityTaskManager { throw e.rethrowFromSystemServer(); } } + + /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */ + public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) { + final Configuration config = context.getResources().getConfiguration(); + int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; + return (modeType != Configuration.UI_MODE_TYPE_CAR + && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER) + && modeType != Configuration.UI_MODE_TYPE_TELEVISION + && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); + } } diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8edf03d033f8..af027837ec91 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -2301,11 +2301,11 @@ public class Notification implements Parcelable priority = parcel.readInt(); - category = parcel.readString(); + category = parcel.readString8(); - mGroupKey = parcel.readString(); + mGroupKey = parcel.readString8(); - mSortKey = parcel.readString(); + mSortKey = parcel.readString8(); extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null fixDuplicateExtras(); @@ -2329,12 +2329,12 @@ public class Notification implements Parcelable color = parcel.readInt(); if (parcel.readInt() != 0) { - mChannelId = parcel.readString(); + mChannelId = parcel.readString8(); } mTimeout = parcel.readLong(); if (parcel.readInt() != 0) { - mShortcutId = parcel.readString(); + mShortcutId = parcel.readString8(); } if (parcel.readInt() != 0) { @@ -2766,11 +2766,11 @@ public class Notification implements Parcelable parcel.writeInt(priority); - parcel.writeString(category); + parcel.writeString8(category); - parcel.writeString(mGroupKey); + parcel.writeString8(mGroupKey); - parcel.writeString(mSortKey); + parcel.writeString8(mSortKey); parcel.writeBundle(extras); // null ok @@ -2803,7 +2803,7 @@ public class Notification implements Parcelable if (mChannelId != null) { parcel.writeInt(1); - parcel.writeString(mChannelId); + parcel.writeString8(mChannelId); } else { parcel.writeInt(0); } @@ -2811,7 +2811,7 @@ public class Notification implements Parcelable if (mShortcutId != null) { parcel.writeInt(1); - parcel.writeString(mShortcutId); + parcel.writeString8(mShortcutId); } else { parcel.writeInt(0); } @@ -8873,7 +8873,7 @@ public class Notification implements Parcelable } mDesiredHeightResId = in.readInt(); if (in.readInt() != 0) { - mShortcutId = in.readString(); + mShortcutId = in.readString8(); } } @@ -9029,7 +9029,7 @@ public class Notification implements Parcelable out.writeInt(mDesiredHeightResId); out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1); if (!TextUtils.isEmpty(mShortcutId)) { - out.writeString(mShortcutId); + out.writeString8(mShortcutId); } } diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 2feb9277775c..9f8d3c4090d6 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -102,7 +102,7 @@ public final class NotificationChannel implements Parcelable { private static final String ATT_FG_SERVICE_SHOWN = "fgservice"; private static final String ATT_GROUP = "group"; private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system"; - private static final String ATT_ALLOW_BUBBLE = "allow_bubble"; + private static final String ATT_ALLOW_BUBBLE = "allow_bubbles"; private static final String ATT_ORIG_IMP = "orig_imp"; private static final String ATT_PARENT_CHANNEL = "parent"; private static final String ATT_CONVERSATION_ID = "conv_id"; @@ -168,7 +168,12 @@ public final class NotificationChannel implements Parcelable { NotificationManager.IMPORTANCE_UNSPECIFIED; private static final boolean DEFAULT_DELETED = false; private static final boolean DEFAULT_SHOW_BADGE = true; - private static final boolean DEFAULT_ALLOW_BUBBLE = false; + /** + * @hide + */ + public static final int DEFAULT_ALLOW_BUBBLE = -1; + private static final int ALLOW_BUBBLE_ON = 1; + private static final int ALLOW_BUBBLE_OFF = 0; @UnsupportedAppUsage private String mId; @@ -193,7 +198,7 @@ public final class NotificationChannel implements Parcelable { private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT; // If this is a blockable system notification channel. private boolean mBlockableSystem = false; - private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE; + private int mAllowBubbles = DEFAULT_ALLOW_BUBBLE; private boolean mImportanceLockedByOEM; private boolean mImportanceLockedDefaultApp; private String mParentId = null; @@ -261,7 +266,7 @@ public final class NotificationChannel implements Parcelable { mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null; mLightColor = in.readInt(); mBlockableSystem = in.readBoolean(); - mAllowBubbles = in.readBoolean(); + mAllowBubbles = in.readInt(); mImportanceLockedByOEM = in.readBoolean(); mOriginalImportance = in.readInt(); mParentId = in.readString(); @@ -320,7 +325,7 @@ public final class NotificationChannel implements Parcelable { } dest.writeInt(mLightColor); dest.writeBoolean(mBlockableSystem); - dest.writeBoolean(mAllowBubbles); + dest.writeInt(mAllowBubbles); dest.writeBoolean(mImportanceLockedByOEM); dest.writeInt(mOriginalImportance); dest.writeString(mParentId); @@ -550,7 +555,14 @@ public final class NotificationChannel implements Parcelable { * @see Notification#getBubbleMetadata() */ public void setAllowBubbles(boolean allowBubbles) { - mAllowBubbles = allowBubbles; + mAllowBubbles = allowBubbles ? ALLOW_BUBBLE_ON : ALLOW_BUBBLE_OFF; + } + + /** + * @hide + */ + public void setAllowBubbles(int allowed) { + mAllowBubbles = allowed; } /** @@ -701,6 +713,13 @@ public final class NotificationChannel implements Parcelable { * @see Notification#getBubbleMetadata() */ public boolean canBubble() { + return mAllowBubbles == ALLOW_BUBBLE_ON; + } + + /** + * @hide + */ + public int getAllowBubbles() { return mAllowBubbles; } @@ -872,7 +891,7 @@ public final class NotificationChannel implements Parcelable { lockFields(safeInt(parser, ATT_USER_LOCKED, 0)); setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false)); setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false)); - setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); + setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE)); setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE)); setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL), parser.getAttributeValue(null, ATT_CONVERSATION_ID)); @@ -996,8 +1015,8 @@ public final class NotificationChannel implements Parcelable { if (isBlockable()) { out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockable())); } - if (canBubble() != DEFAULT_ALLOW_BUBBLE) { - out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble())); + if (getAllowBubbles() != DEFAULT_ALLOW_BUBBLE) { + out.attribute(null, ATT_ALLOW_BUBBLE, Integer.toString(getAllowBubbles())); } if (getOriginalImportance() != DEFAULT_IMPORTANCE) { out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance())); @@ -1059,7 +1078,7 @@ public final class NotificationChannel implements Parcelable { record.put(ATT_DELETED, Boolean.toString(isDeleted())); record.put(ATT_GROUP, getGroup()); record.put(ATT_BLOCKABLE_SYSTEM, isBlockable()); - record.put(ATT_ALLOW_BUBBLE, canBubble()); + record.put(ATT_ALLOW_BUBBLE, getAllowBubbles()); // TODO: original importance return record; } diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 0173731995dd..c7a2a1e11c9e 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -20,6 +20,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; @@ -196,6 +197,20 @@ public class TaskInfo { return resizeMode != RESIZE_MODE_UNRESIZEABLE; } + /** @hide */ + @NonNull + @TestApi + public WindowContainerToken getToken() { + return token; + } + + /** @hide */ + @NonNull + @TestApi + public Configuration getConfiguration() { + return configuration; + } + /** * Reads the TaskInfo from a parcel. */ diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index 9c806fa286bc..1923bf3c5a23 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java @@ -1206,7 +1206,7 @@ public class ClipData implements Parcelable { } } else { dest.writeInt(PARCEL_TYPE_STRING); - dest.writeString(text); + dest.writeString8(text); } } @@ -1215,7 +1215,7 @@ public class ClipData implements Parcelable { */ private static String readHtmlTextFromParcel(Parcel in) { if (in.readInt() == PARCEL_TYPE_STRING) { - return in.readString(); + return in.readString8(); } ParcelFileDescriptor pfd = in.readParcelable(ParcelFileDescriptor.class.getClassLoader()); diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java index 494d2aeaf42a..1fb426eb3cfc 100644 --- a/core/java/android/content/ContentProviderOperation.java +++ b/core/java/android/content/ContentProviderOperation.java @@ -91,8 +91,8 @@ public class ContentProviderOperation implements Parcelable { private ContentProviderOperation(Parcel source) { mType = source.readInt(); mUri = Uri.CREATOR.createFromParcel(source); - mMethod = source.readInt() != 0 ? source.readString() : null; - mArg = source.readInt() != 0 ? source.readString() : null; + mMethod = source.readInt() != 0 ? source.readString8() : null; + mArg = source.readInt() != 0 ? source.readString8() : null; final int valuesSize = source.readInt(); if (valuesSize != -1) { mValues = new ArrayMap<>(valuesSize); @@ -107,7 +107,7 @@ public class ContentProviderOperation implements Parcelable { } else { mExtras = null; } - mSelection = source.readInt() != 0 ? source.readString() : null; + mSelection = source.readInt() != 0 ? source.readString8() : null; mSelectionArgs = source.readSparseArray(null); mExpectedCount = source.readInt() != 0 ? source.readInt() : null; mYieldAllowed = source.readInt() != 0; @@ -135,13 +135,13 @@ public class ContentProviderOperation implements Parcelable { Uri.writeToParcel(dest, mUri); if (mMethod != null) { dest.writeInt(1); - dest.writeString(mMethod); + dest.writeString8(mMethod); } else { dest.writeInt(0); } if (mArg != null) { dest.writeInt(1); - dest.writeString(mArg); + dest.writeString8(mArg); } else { dest.writeInt(0); } @@ -159,7 +159,7 @@ public class ContentProviderOperation implements Parcelable { } if (mSelection != null) { dest.writeInt(1); - dest.writeString(mSelection); + dest.writeString8(mSelection); } else { dest.writeInt(0); } @@ -591,7 +591,7 @@ public class ContentProviderOperation implements Parcelable { public BackReference(Parcel src) { this.fromIndex = src.readInt(); if (src.readInt() != 0) { - this.fromKey = src.readString(); + this.fromKey = src.readString8(); } else { this.fromKey = null; } @@ -620,7 +620,7 @@ public class ContentProviderOperation implements Parcelable { dest.writeInt(fromIndex); if (fromKey != null) { dest.writeInt(1); - dest.writeString(fromKey); + dest.writeString8(fromKey); } else { dest.writeInt(0); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b1d6c830d3b7..def150ab49e5 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -888,8 +888,8 @@ public class Intent implements Parcelable, Cloneable { public ShortcutIconResource createFromParcel(Parcel source) { ShortcutIconResource icon = new ShortcutIconResource(); - icon.packageName = source.readString(); - icon.resourceName = source.readString(); + icon.packageName = source.readString8(); + icon.resourceName = source.readString8(); return icon; } @@ -906,8 +906,8 @@ public class Intent implements Parcelable, Cloneable { } public void writeToParcel(Parcel dest, int flags) { - dest.writeString(packageName); - dest.writeString(resourceName); + dest.writeString8(packageName); + dest.writeString8(resourceName); } @Override @@ -10807,12 +10807,12 @@ public class Intent implements Parcelable, Cloneable { } public void writeToParcel(Parcel out, int flags) { - out.writeString(mAction); + out.writeString8(mAction); Uri.writeToParcel(out, mData); - out.writeString(mType); - out.writeString(mIdentifier); + out.writeString8(mType); + out.writeString8(mIdentifier); out.writeInt(mFlags); - out.writeString(mPackage); + out.writeString8(mPackage); ComponentName.writeToParcel(mComponent, out); if (mSourceBounds != null) { @@ -10826,7 +10826,7 @@ public class Intent implements Parcelable, Cloneable { final int N = mCategories.size(); out.writeInt(N); for (int i=0; i<N; i++) { - out.writeString(mCategories.valueAt(i)); + out.writeString8(mCategories.valueAt(i)); } } else { out.writeInt(0); @@ -10865,12 +10865,12 @@ public class Intent implements Parcelable, Cloneable { } public void readFromParcel(Parcel in) { - setAction(in.readString()); + setAction(in.readString8()); mData = Uri.CREATOR.createFromParcel(in); - mType = in.readString(); - mIdentifier = in.readString(); + mType = in.readString8(); + mIdentifier = in.readString8(); mFlags = in.readInt(); - mPackage = in.readString(); + mPackage = in.readString8(); mComponent = ComponentName.readFromParcel(in); if (in.readInt() != 0) { @@ -10882,7 +10882,7 @@ public class Intent implements Parcelable, Cloneable { mCategories = new ArraySet<String>(); int i; for (i=0; i<N; i++) { - mCategories.add(in.readString().intern()); + mCategories.add(in.readString8().intern()); } } else { mCategories = null; diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index f25ce76d9365..b1f88693d9c0 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1206,17 +1206,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeInt(theme); dest.writeInt(launchMode); dest.writeInt(documentLaunchMode); - dest.writeString(permission); - dest.writeString(taskAffinity); - dest.writeString(targetActivity); - dest.writeString(launchToken); + dest.writeString8(permission); + dest.writeString8(taskAffinity); + dest.writeString8(targetActivity); + dest.writeString8(launchToken); dest.writeInt(flags); dest.writeInt(privateFlags); dest.writeInt(screenOrientation); dest.writeInt(configChanges); dest.writeInt(softInputMode); dest.writeInt(uiOptions); - dest.writeString(parentActivityName); + dest.writeString8(parentActivityName); dest.writeInt(persistableMode); dest.writeInt(maxRecents); dest.writeInt(lockTaskLaunchMode); @@ -1227,7 +1227,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeInt(0); } dest.writeInt(resizeMode); - dest.writeString(requestedVrComponent); + dest.writeString8(requestedVrComponent); dest.writeInt(rotationAnimation); dest.writeInt(colorMode); dest.writeFloat(maxAspectRatio); @@ -1327,17 +1327,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { theme = source.readInt(); launchMode = source.readInt(); documentLaunchMode = source.readInt(); - permission = source.readString(); - taskAffinity = source.readString(); - targetActivity = source.readString(); - launchToken = source.readString(); + permission = source.readString8(); + taskAffinity = source.readString8(); + targetActivity = source.readString8(); + launchToken = source.readString8(); flags = source.readInt(); privateFlags = source.readInt(); screenOrientation = source.readInt(); configChanges = source.readInt(); softInputMode = source.readInt(); uiOptions = source.readInt(); - parentActivityName = source.readString(); + parentActivityName = source.readString8(); persistableMode = source.readInt(); maxRecents = source.readInt(); lockTaskLaunchMode = source.readInt(); @@ -1345,7 +1345,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { windowLayout = new WindowLayout(source); } resizeMode = source.readInt(); - requestedVrComponent = source.readString(); + requestedVrComponent = source.readString8(); rotationAnimation = source.readInt(); colorMode = source.readInt(); maxAspectRatio = source.readFloat(); @@ -1386,7 +1386,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { gravity = source.readInt(); minWidth = source.readInt(); minHeight = source.readInt(); - windowLayoutAffinity = source.readString(); + windowLayoutAffinity = source.readString8(); } /** @@ -1476,7 +1476,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeInt(gravity); dest.writeInt(minWidth); dest.writeInt(minHeight); - dest.writeString(windowLayoutAffinity); + dest.writeString8(windowLayoutAffinity); } } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index b67060111785..b37521b1189b 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1704,10 +1704,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { return; } super.writeToParcel(dest, parcelableFlags); - dest.writeString(taskAffinity); - dest.writeString(permission); - dest.writeString(processName); - dest.writeString(className); + dest.writeString8(taskAffinity); + dest.writeString8(permission); + dest.writeString8(processName); + dest.writeString8(className); dest.writeInt(theme); dest.writeInt(flags); dest.writeInt(privateFlags); @@ -1721,28 +1721,28 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { } else { dest.writeInt(0); } - dest.writeString(scanSourceDir); - dest.writeString(scanPublicSourceDir); - dest.writeString(sourceDir); - dest.writeString(publicSourceDir); + dest.writeString8(scanSourceDir); + dest.writeString8(scanPublicSourceDir); + dest.writeString8(sourceDir); + dest.writeString8(publicSourceDir); dest.writeStringArray(splitNames); dest.writeStringArray(splitSourceDirs); dest.writeStringArray(splitPublicSourceDirs); dest.writeSparseArray((SparseArray) splitDependencies); - dest.writeString(nativeLibraryDir); - dest.writeString(secondaryNativeLibraryDir); - dest.writeString(nativeLibraryRootDir); + dest.writeString8(nativeLibraryDir); + dest.writeString8(secondaryNativeLibraryDir); + dest.writeString8(nativeLibraryRootDir); dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0); - dest.writeString(primaryCpuAbi); - dest.writeString(secondaryCpuAbi); + dest.writeString8(primaryCpuAbi); + dest.writeString8(secondaryCpuAbi); dest.writeStringArray(resourceDirs); - dest.writeString(seInfo); - dest.writeString(seInfoUser); + dest.writeString8(seInfo); + dest.writeString8(seInfoUser); dest.writeStringArray(sharedLibraryFiles); dest.writeTypedList(sharedLibraryInfos); - dest.writeString(dataDir); - dest.writeString(deviceProtectedDataDir); - dest.writeString(credentialProtectedDataDir); + dest.writeString8(dataDir); + dest.writeString8(deviceProtectedDataDir); + dest.writeString8(credentialProtectedDataDir); dest.writeInt(uid); dest.writeInt(minSdkVersion); dest.writeInt(targetSdkVersion); @@ -1750,8 +1750,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(enabled ? 1 : 0); dest.writeInt(enabledSetting); dest.writeInt(installLocation); - dest.writeString(manageSpaceActivityName); - dest.writeString(backupAgentName); + dest.writeString8(manageSpaceActivityName); + dest.writeString8(backupAgentName); dest.writeInt(descriptionRes); dest.writeInt(uiOptions); dest.writeInt(fullBackupContent); @@ -1759,16 +1759,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(networkSecurityConfigRes); dest.writeInt(category); dest.writeInt(targetSandboxVersion); - dest.writeString(classLoaderName); + dest.writeString8(classLoaderName); dest.writeStringArray(splitClassLoaderNames); dest.writeInt(compileSdkVersion); - dest.writeString(compileSdkVersionCodename); - dest.writeString(appComponentFactory); + dest.writeString8(compileSdkVersionCodename); + dest.writeString8(appComponentFactory); dest.writeInt(iconRes); dest.writeInt(roundIconRes); dest.writeInt(mHiddenApiPolicy); dest.writeInt(hiddenUntilInstalled ? 1 : 0); - dest.writeString(zygotePreloadName); + dest.writeString8(zygotePreloadName); dest.writeInt(gwpAsanMode); } @@ -1788,10 +1788,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { @SuppressWarnings("unchecked") private ApplicationInfo(Parcel source) { super(source); - taskAffinity = source.readString(); - permission = source.readString(); - processName = source.readString(); - className = source.readString(); + taskAffinity = source.readString8(); + permission = source.readString8(); + processName = source.readString8(); + className = source.readString8(); theme = source.readInt(); flags = source.readInt(); privateFlags = source.readInt(); @@ -1802,28 +1802,28 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { storageUuid = new UUID(source.readLong(), source.readLong()); volumeUuid = StorageManager.convert(storageUuid); } - scanSourceDir = source.readString(); - scanPublicSourceDir = source.readString(); - sourceDir = source.readString(); - publicSourceDir = source.readString(); + scanSourceDir = source.readString8(); + scanPublicSourceDir = source.readString8(); + sourceDir = source.readString8(); + publicSourceDir = source.readString8(); splitNames = source.readStringArray(); splitSourceDirs = source.readStringArray(); splitPublicSourceDirs = source.readStringArray(); splitDependencies = source.readSparseArray(null); - nativeLibraryDir = source.readString(); - secondaryNativeLibraryDir = source.readString(); - nativeLibraryRootDir = source.readString(); + nativeLibraryDir = source.readString8(); + secondaryNativeLibraryDir = source.readString8(); + nativeLibraryRootDir = source.readString8(); nativeLibraryRootRequiresIsa = source.readInt() != 0; - primaryCpuAbi = source.readString(); - secondaryCpuAbi = source.readString(); + primaryCpuAbi = source.readString8(); + secondaryCpuAbi = source.readString8(); resourceDirs = source.readStringArray(); - seInfo = source.readString(); - seInfoUser = source.readString(); + seInfo = source.readString8(); + seInfoUser = source.readString8(); sharedLibraryFiles = source.readStringArray(); sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR); - dataDir = source.readString(); - deviceProtectedDataDir = source.readString(); - credentialProtectedDataDir = source.readString(); + dataDir = source.readString8(); + deviceProtectedDataDir = source.readString8(); + credentialProtectedDataDir = source.readString8(); uid = source.readInt(); minSdkVersion = source.readInt(); targetSdkVersion = source.readInt(); @@ -1831,8 +1831,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { enabled = source.readInt() != 0; enabledSetting = source.readInt(); installLocation = source.readInt(); - manageSpaceActivityName = source.readString(); - backupAgentName = source.readString(); + manageSpaceActivityName = source.readString8(); + backupAgentName = source.readString8(); descriptionRes = source.readInt(); uiOptions = source.readInt(); fullBackupContent = source.readInt(); @@ -1840,16 +1840,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { networkSecurityConfigRes = source.readInt(); category = source.readInt(); targetSandboxVersion = source.readInt(); - classLoaderName = source.readString(); + classLoaderName = source.readString8(); splitClassLoaderNames = source.readStringArray(); compileSdkVersion = source.readInt(); - compileSdkVersionCodename = source.readString(); - appComponentFactory = source.readString(); + compileSdkVersionCodename = source.readString8(); + appComponentFactory = source.readString8(); iconRes = source.readInt(); roundIconRes = source.readInt(); mHiddenApiPolicy = source.readInt(); hiddenUntilInstalled = source.readInt() != 0; - zygotePreloadName = source.readString(); + zygotePreloadName = source.readString8(); gwpAsanMode = source.readInt(); } diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index 362098c447ce..628bcd70cdf6 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -197,8 +197,8 @@ public class ComponentInfo extends PackageItemInfo { public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); applicationInfo.writeToParcel(dest, parcelableFlags); - dest.writeString(processName); - dest.writeString(splitName); + dest.writeString8(processName); + dest.writeString8(splitName); dest.writeInt(descriptionRes); dest.writeInt(enabled ? 1 : 0); dest.writeInt(exported ? 1 : 0); @@ -208,8 +208,8 @@ public class ComponentInfo extends PackageItemInfo { protected ComponentInfo(Parcel source) { super(source); applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source); - processName = source.readString(); - splitName = source.readString(); + processName = source.readString8(); + splitName = source.readString8(); descriptionRes = source.readInt(); enabled = (source.readInt() != 0); exported = (source.readInt() != 0); diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java index 9f3ab77062ef..89269e0d7aa5 100644 --- a/core/java/android/content/pm/FeatureInfo.java +++ b/core/java/android/content/pm/FeatureInfo.java @@ -108,7 +108,7 @@ public class FeatureInfo implements Parcelable { @Override public void writeToParcel(Parcel dest, int parcelableFlags) { - dest.writeString(name); + dest.writeString8(name); dest.writeInt(version); dest.writeInt(reqGlEsVersion); dest.writeInt(flags); @@ -138,7 +138,7 @@ public class FeatureInfo implements Parcelable { }; private FeatureInfo(Parcel source) { - name = source.readString(); + name = source.readString8(); version = source.readInt(); reqGlEsVersion = source.readInt(); flags = source.readInt(); diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl index 9819b5d4eeb9..ffe8b183e926 100644 --- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl +++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl @@ -21,16 +21,29 @@ package android.content.pm; * @hide */ oneway interface IDataLoaderStatusListener { - /** Data loader status */ + /** When this status is returned from DataLoader, it means that the DataLoader + * process is running, bound to and has handled onCreate(). */ const int DATA_LOADER_CREATED = 0; + /** Listener will receive this status when the DataLoader process died, + * binder disconnected or class destroyed. */ const int DATA_LOADER_DESTROYED = 1; + /** DataLoader can receive missing pages and read pages notifications, + * and ready to provide data. */ const int DATA_LOADER_STARTED = 2; + /** DataLoader no longer ready to provide data and is not receiving + * any notifications from IncFS. */ const int DATA_LOADER_STOPPED = 3; + /** DataLoader streamed everything necessary to continue installation. */ const int DATA_LOADER_IMAGE_READY = 4; + /** Installation can't continue as DataLoader failed to stream necessary data. */ const int DATA_LOADER_IMAGE_NOT_READY = 5; + /** DataLoader reports that this instance is invalid and can never be restored. + * Warning: this is a terminal status that data loader should use carefully and + * the system should almost never use - e.g. only if all recovery attempts + * fail and all retry limits are exceeded. */ const int DATA_LOADER_UNRECOVERABLE = 6; /** Data loader status callback */ diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java index 574a1ee2d0ce..745a6c1a0dff 100644 --- a/core/java/android/content/pm/InstrumentationInfo.java +++ b/core/java/android/content/pm/InstrumentationInfo.java @@ -157,21 +157,21 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); - dest.writeString(targetPackage); - dest.writeString(targetProcesses); - dest.writeString(sourceDir); - dest.writeString(publicSourceDir); + dest.writeString8(targetPackage); + dest.writeString8(targetProcesses); + dest.writeString8(sourceDir); + dest.writeString8(publicSourceDir); dest.writeStringArray(splitNames); dest.writeStringArray(splitSourceDirs); dest.writeStringArray(splitPublicSourceDirs); dest.writeSparseArray((SparseArray) splitDependencies); - dest.writeString(dataDir); - dest.writeString(deviceProtectedDataDir); - dest.writeString(credentialProtectedDataDir); - dest.writeString(primaryCpuAbi); - dest.writeString(secondaryCpuAbi); - dest.writeString(nativeLibraryDir); - dest.writeString(secondaryNativeLibraryDir); + dest.writeString8(dataDir); + dest.writeString8(deviceProtectedDataDir); + dest.writeString8(credentialProtectedDataDir); + dest.writeString8(primaryCpuAbi); + dest.writeString8(secondaryCpuAbi); + dest.writeString8(nativeLibraryDir); + dest.writeString8(secondaryNativeLibraryDir); dest.writeInt((handleProfiling == false) ? 0 : 1); dest.writeInt((functionalTest == false) ? 0 : 1); } @@ -189,21 +189,21 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { @SuppressWarnings("unchecked") private InstrumentationInfo(Parcel source) { super(source); - targetPackage = source.readString(); - targetProcesses = source.readString(); - sourceDir = source.readString(); - publicSourceDir = source.readString(); + targetPackage = source.readString8(); + targetProcesses = source.readString8(); + sourceDir = source.readString8(); + publicSourceDir = source.readString8(); splitNames = source.readStringArray(); splitSourceDirs = source.readStringArray(); splitPublicSourceDirs = source.readStringArray(); splitDependencies = source.readSparseArray(null); - dataDir = source.readString(); - deviceProtectedDataDir = source.readString(); - credentialProtectedDataDir = source.readString(); - primaryCpuAbi = source.readString(); - secondaryCpuAbi = source.readString(); - nativeLibraryDir = source.readString(); - secondaryNativeLibraryDir = source.readString(); + dataDir = source.readString8(); + deviceProtectedDataDir = source.readString8(); + credentialProtectedDataDir = source.readString8(); + primaryCpuAbi = source.readString8(); + secondaryCpuAbi = source.readString8(); + nativeLibraryDir = source.readString8(); + secondaryNativeLibraryDir = source.readString8(); handleProfiling = source.readInt() != 0; functionalTest = source.readInt() != 0; } diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index 85c698f3fb0c..bb56ef7fd3a0 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -441,14 +441,14 @@ public class PackageInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { // Allow ApplicationInfo to be squashed. final boolean prevAllowSquashing = dest.allowSquashing(); - dest.writeString(packageName); + dest.writeString8(packageName); dest.writeStringArray(splitNames); dest.writeInt(versionCode); dest.writeInt(versionCodeMajor); - dest.writeString(versionName); + dest.writeString8(versionName); dest.writeInt(baseRevisionCode); dest.writeIntArray(splitRevisionCodes); - dest.writeString(sharedUserId); + dest.writeString8(sharedUserId); dest.writeInt(sharedUserLabel); if (applicationInfo != null) { dest.writeInt(1); @@ -475,14 +475,14 @@ public class PackageInfo implements Parcelable { dest.writeInt(isStub ? 1 : 0); dest.writeInt(coreApp ? 1 : 0); dest.writeInt(requiredForAllUsers ? 1 : 0); - dest.writeString(restrictedAccountType); - dest.writeString(requiredAccountType); - dest.writeString(overlayTarget); - dest.writeString(overlayCategory); + dest.writeString8(restrictedAccountType); + dest.writeString8(requiredAccountType); + dest.writeString8(overlayTarget); + dest.writeString8(overlayCategory); dest.writeInt(overlayPriority); dest.writeBoolean(mOverlayIsStatic); dest.writeInt(compileSdkVersion); - dest.writeString(compileSdkVersionCodename); + dest.writeString8(compileSdkVersionCodename); if (signingInfo != null) { dest.writeInt(1); signingInfo.writeToParcel(dest, parcelableFlags); @@ -508,14 +508,14 @@ public class PackageInfo implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private PackageInfo(Parcel source) { - packageName = source.readString(); + packageName = source.readString8(); splitNames = source.createStringArray(); versionCode = source.readInt(); versionCodeMajor = source.readInt(); - versionName = source.readString(); + versionName = source.readString8(); baseRevisionCode = source.readInt(); splitRevisionCodes = source.createIntArray(); - sharedUserId = source.readString(); + sharedUserId = source.readString8(); sharedUserLabel = source.readInt(); int hasApp = source.readInt(); if (hasApp != 0) { @@ -540,14 +540,14 @@ public class PackageInfo implements Parcelable { isStub = source.readInt() != 0; coreApp = source.readInt() != 0; requiredForAllUsers = source.readInt() != 0; - restrictedAccountType = source.readString(); - requiredAccountType = source.readString(); - overlayTarget = source.readString(); - overlayCategory = source.readString(); + restrictedAccountType = source.readString8(); + requiredAccountType = source.readString8(); + overlayTarget = source.readString8(); + overlayCategory = source.readString8(); overlayPriority = source.readInt(); mOverlayIsStatic = source.readBoolean(); compileSdkVersion = source.readInt(); - compileSdkVersionCodename = source.readString(); + compileSdkVersionCodename = source.readString8(); int hasSigningInfo = source.readInt(); if (hasSigningInfo != 0) { signingInfo = SigningInfo.CREATOR.createFromParcel(source); diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 7fd5531bf20d..d41ace5bcf62 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -422,8 +422,8 @@ public class PackageItemInfo { } public void writeToParcel(Parcel dest, int parcelableFlags) { - dest.writeString(name); - dest.writeString(packageName); + dest.writeString8(name); + dest.writeString8(packageName); dest.writeInt(labelRes); TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags); dest.writeInt(icon); @@ -452,8 +452,8 @@ public class PackageItemInfo { } protected PackageItemInfo(Parcel source) { - name = source.readString(); - packageName = source.readString(); + name = source.readString8(); + packageName = source.readString8(); labelRes = source.readInt(); nonLocalizedLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java index 44def3321c34..8212224e114c 100644 --- a/core/java/android/content/pm/PackageParserCacheHelper.java +++ b/core/java/android/content/pm/PackageParserCacheHelper.java @@ -78,10 +78,19 @@ public class PackageParserCacheHelper { /** * Read an string index from a parcel, and returns the corresponding string from the pool. */ - @Override public String readString(Parcel p) { return mStrings.get(p.readInt()); } + + @Override + public String readString8(Parcel p) { + return readString(p); + } + + @Override + public String readString16(Parcel p) { + return readString(p); + } } /** @@ -110,7 +119,6 @@ public class PackageParserCacheHelper { * Instead of writing a string directly to a parcel, this method adds it to the pool, * and write the index in the pool to the parcel. */ - @Override public void writeString(Parcel p, String s) { final Integer cur = mIndexes.get(s); if (cur != null) { @@ -133,6 +141,16 @@ public class PackageParserCacheHelper { } } + @Override + public void writeString8(Parcel p, String s) { + writeString(p, s); + } + + @Override + public void writeString16(Parcel p, String s) { + writeString(p, s); + } + /** * Closes a parcel by appending the string pool at the end and updating the pool offset, * which it assumes is at the first byte. It also uninstalls itself as a read-write helper. diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index 07d42dc823c4..3984ade73d6c 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -145,9 +145,9 @@ public final class ProviderInfo extends ComponentInfo @Override public void writeToParcel(Parcel out, int parcelableFlags) { super.writeToParcel(out, parcelableFlags); - out.writeString(authority); - out.writeString(readPermission); - out.writeString(writePermission); + out.writeString8(authority); + out.writeString8(readPermission); + out.writeString8(writePermission); out.writeInt(grantUriPermissions ? 1 : 0); out.writeInt(forceUriPermissions ? 1 : 0); out.writeTypedArray(uriPermissionPatterns, parcelableFlags); @@ -175,9 +175,9 @@ public final class ProviderInfo extends ComponentInfo private ProviderInfo(Parcel in) { super(in); - authority = in.readString(); - readPermission = in.readString(); - writePermission = in.readString(); + authority = in.readString8(); + readPermission = in.readString8(); + writePermission = in.readString8(); grantUriPermissions = in.readInt() != 0; forceUriPermissions = in.readInt() != 0; uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 5f90b6c43c60..d3f9e2486a64 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -244,7 +244,7 @@ public class ServiceInfo extends ComponentInfo public void writeToParcel(Parcel dest, int parcelableFlags) { super.writeToParcel(dest, parcelableFlags); - dest.writeString(permission); + dest.writeString8(permission); dest.writeInt(flags); dest.writeInt(mForegroundServiceType); } @@ -261,7 +261,7 @@ public class ServiceInfo extends ComponentInfo private ServiceInfo(Parcel source) { super(source); - permission = source.readString(); + permission = source.readString8(); flags = source.readInt(); mForegroundServiceType = source.readInt(); } diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index f7c96a3a02c1..2f67f6ddc082 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -228,19 +228,26 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } catch (SQLiteCantOpenDatabaseException e) { String message = String.format("Cannot open database '%s'", file); - final Path path = FileSystems.getDefault().getPath(file); - final Path dir = path.getParent(); - - if (!Files.isDirectory(dir)) { - message += ": Directory " + dir + " doesn't exist"; - } else if (!Files.exists(path)) { - message += ": File " + path + " doesn't exist"; - } else if (!Files.isReadable(path)) { - message += ": File " + path + " is not readable"; - } else if (Files.isDirectory(path)) { - message += ": Path " + path + " is a directory"; - } else { - message += ": Unknown reason"; + try { + // Try to diagnose for common reasons. If something fails in here, that's fine; + // just swallow the exception. + + final Path path = FileSystems.getDefault().getPath(file); + final Path dir = path.getParent(); + + if (!Files.isDirectory(dir)) { + message += ": Directory " + dir + " doesn't exist"; + } else if (!Files.exists(path)) { + message += ": File " + path + " doesn't exist"; + } else if (!Files.isReadable(path)) { + message += ": File " + path + " is not readable"; + } else if (Files.isDirectory(path)) { + message += ": Path " + path + " is a directory"; + } else { + message += ": Unknown reason"; + } + } catch (Throwable th) { + message += ": Unknown reason; cannot examine filesystem: " + th.getMessage(); } throw new SQLiteCantOpenDatabaseException(message, e); } finally { diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java index 462110f5a795..ad9bf0745779 100644 --- a/core/java/android/hardware/display/DisplayManagerInternal.java +++ b/core/java/android/hardware/display/DisplayManagerInternal.java @@ -65,21 +65,23 @@ public abstract class DisplayManagerInternal { public abstract boolean isProximitySensorAvailable(); /** - * Take a screenshot of the specified display and return a buffer. + * Screenshot for internal system-only use such as rotation, etc. This method includes + * secure layers and the result should never be exposed to non-system applications. + * This method does not apply any rotation and provides the output in natural orientation. * * @param displayId The display id to take the screenshot of. * @return The buffer or null if we have failed. */ - public abstract SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId); + public abstract SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId); /** - * Take a screenshot without secure layer of the specified display and return a buffer. + * General screenshot functionality that excludes secure layers and applies appropriate + * rotation that the device is currently in. * * @param displayId The display id to take the screenshot of. * @return The buffer or null if we have failed. */ - public abstract SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer( - int displayId); + public abstract SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId); /** * Returns information about the specified logical display. diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 525bbfdbb06d..1cb4fe8cf4e7 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -500,7 +500,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } static Uri readFrom(Parcel parcel) { - return new StringUri(parcel.readString()); + return new StringUri(parcel.readString8()); } public int describeContents() { @@ -509,7 +509,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString(uriString); + parcel.writeString8(uriString); } /** Cached scheme separator index. */ @@ -875,7 +875,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { static Uri readFrom(Parcel parcel) { return new OpaqueUri( - parcel.readString(), + parcel.readString8(), Part.readFrom(parcel), Part.readFrom(parcel) ); @@ -887,7 +887,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString(scheme); + parcel.writeString8(scheme); ssp.writeTo(parcel); fragment.writeTo(parcel); } @@ -1195,7 +1195,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { static Uri readFrom(Parcel parcel) { return new HierarchicalUri( - parcel.readString(), + parcel.readString8(), Part.readFrom(parcel), PathPart.readFrom(parcel), Part.readFrom(parcel), @@ -1209,7 +1209,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(TYPE_ID); - parcel.writeString(scheme); + parcel.writeString8(scheme); authority.writeTo(parcel); path.writeTo(parcel); query.writeTo(parcel); @@ -2028,7 +2028,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { + mCanonicalRepresentation + ")"); } parcel.writeInt(mCanonicalRepresentation); - parcel.writeString(canonicalValue); + parcel.writeString8(canonicalValue); } } @@ -2060,7 +2060,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { static Part readFrom(Parcel parcel) { int representation = parcel.readInt(); - String value = parcel.readString(); + String value = parcel.readString8(); switch (representation) { case REPRESENTATION_ENCODED: return fromEncoded(value); @@ -2251,9 +2251,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { int representation = parcel.readInt(); switch (representation) { case REPRESENTATION_ENCODED: - return fromEncoded(parcel.readString()); + return fromEncoded(parcel.readString8()); case REPRESENTATION_DECODED: - return fromDecoded(parcel.readString()); + return fromDecoded(parcel.readString8()); default: throw new IllegalArgumentException("Unknown representation: " + representation); } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index f0b7b5fa5a1a..93f6607ff9d4 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -307,7 +307,9 @@ public final class Parcel { @FastNative private static native void nativeWriteDouble(long nativePtr, double val); @FastNative - static native void nativeWriteString(long nativePtr, String val); + private static native void nativeWriteString8(long nativePtr, String val); + @FastNative + private static native void nativeWriteString16(long nativePtr, String val); @FastNative private static native void nativeWriteStrongBinder(long nativePtr, IBinder val); @FastNative @@ -325,7 +327,9 @@ public final class Parcel { @CriticalNative private static native double nativeReadDouble(long nativePtr); @FastNative - static native String nativeReadString(long nativePtr); + private static native String nativeReadString8(long nativePtr); + @FastNative + private static native String nativeReadString16(long nativePtr); @FastNative private static native IBinder nativeReadStrongBinder(long nativePtr); @FastNative @@ -386,8 +390,12 @@ public final class Parcel { * must use {@link #writeStringNoHelper(String)} to avoid * infinity recursive calls. */ - public void writeString(Parcel p, String s) { - nativeWriteString(p.mNativePtr, s); + public void writeString8(Parcel p, String s) { + p.writeString8NoHelper(s); + } + + public void writeString16(Parcel p, String s) { + p.writeString16NoHelper(s); } /** @@ -395,8 +403,12 @@ public final class Parcel { * must use {@link #readStringNoHelper()} to avoid * infinity recursive calls. */ - public String readString(Parcel p) { - return nativeReadString(p.mNativePtr); + public String readString8(Parcel p) { + return p.readString8NoHelper(); + } + + public String readString16(Parcel p) { + return p.readString16NoHelper(); } } @@ -759,7 +771,17 @@ public final class Parcel { * growing dataCapacity() if needed. */ public final void writeString(@Nullable String val) { - mReadWriteHelper.writeString(this, val); + writeString16(val); + } + + /** {@hide} */ + public final void writeString8(@Nullable String val) { + mReadWriteHelper.writeString8(this, val); + } + + /** {@hide} */ + public final void writeString16(@Nullable String val) { + mReadWriteHelper.writeString16(this, val); } /** @@ -770,7 +792,17 @@ public final class Parcel { * @hide */ public void writeStringNoHelper(@Nullable String val) { - nativeWriteString(mNativePtr, val); + writeString16NoHelper(val); + } + + /** {@hide} */ + public void writeString8NoHelper(@Nullable String val) { + nativeWriteString8(mNativePtr, val); + } + + /** {@hide} */ + public void writeString16NoHelper(@Nullable String val) { + nativeWriteString16(mNativePtr, val); } /** @@ -2337,7 +2369,17 @@ public final class Parcel { */ @Nullable public final String readString() { - return mReadWriteHelper.readString(this); + return readString16(); + } + + /** {@hide} */ + public final @Nullable String readString8() { + return mReadWriteHelper.readString8(this); + } + + /** {@hide} */ + public final @Nullable String readString16() { + return mReadWriteHelper.readString16(this); } /** @@ -2347,9 +2389,18 @@ public final class Parcel { * * @hide */ - @Nullable - public String readStringNoHelper() { - return nativeReadString(mNativePtr); + public @Nullable String readStringNoHelper() { + return readString16NoHelper(); + } + + /** {@hide} */ + public @Nullable String readString8NoHelper() { + return nativeReadString8(mNativePtr); + } + + /** {@hide} */ + public @Nullable String readString16NoHelper() { + return nativeReadString16(mNativePtr); } /** diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index aee32ed769ac..a1a11ed54609 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -2447,7 +2447,12 @@ public class StorageManager { final String filePath = path.getCanonicalPath(); final StorageVolume volume = getStorageVolume(path); if (volume == null) { - throw new IllegalStateException("Failed to update quota type for " + filePath); + Log.w(TAG, "Failed to update quota type for " + filePath); + return; + } + if (!volume.isEmulated()) { + // We only support quota tracking on emulated filesystems + return; } final int userId = volume.getOwner().getIdentifier(); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index df4ead13d5cb..28639b3ea2f7 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -748,7 +748,7 @@ public class TextUtils { int parcelableFlags) { if (cs instanceof Spanned) { p.writeInt(0); - p.writeString(cs.toString()); + p.writeString8(cs.toString()); Spanned sp = (Spanned) cs; Object[] os = sp.getSpans(0, cs.length(), Object.class); @@ -785,9 +785,9 @@ public class TextUtils { } else { p.writeInt(1); if (cs != null) { - p.writeString(cs.toString()); + p.writeString8(cs.toString()); } else { - p.writeString(null); + p.writeString8(null); } } } @@ -807,7 +807,7 @@ public class TextUtils { public CharSequence createFromParcel(Parcel p) { int kind = p.readInt(); - String string = p.readString(); + String string = p.readString8(); if (string == null) { return null; } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index bdc8ba835fbe..df891303bb1d 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -153,7 +153,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { @Override protected boolean isRequestedVisibleAwaitingControl() { - return mIsRequestedVisibleAwaitingControl; + return mIsRequestedVisibleAwaitingControl || isRequestedVisible(); } private boolean isDummyOrEmptyEditor(EditorInfo info) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cc380f32297e..e4dbd63765b6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -514,33 +514,24 @@ public interface WindowManager extends ViewManager { int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3; /** - * Parcel key for the screen shot bitmap sent with messages of type - * {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap} - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap"; - - /** - * Parcel key for the screen bounds of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds"; - - /** - * Parcel key for the task id of the task that the screen shot was taken of, sent with messages - * of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int. - * @hide - */ - String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id"; - - /** - * Parcel key for the visible insets of the image sent with messages of type - * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in - * screen coordinates. + * Enum listing the possible sources from which a screenshot was originated. Used for logging. + * * @hide */ - String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets"; + @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS, + ScreenshotSource.SCREENSHOT_KEY_CHORD, + ScreenshotSource.SCREENSHOT_KEY_OTHER, + ScreenshotSource.SCREENSHOT_OVERVIEW, + ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS, + ScreenshotSource.SCREENSHOT_OTHER}) + @interface ScreenshotSource { + int SCREENSHOT_GLOBAL_ACTIONS = 0; + int SCREENSHOT_KEY_CHORD = 1; + int SCREENSHOT_KEY_OTHER = 2; + int SCREENSHOT_OVERVIEW = 3; + int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4; + int SCREENSHOT_OTHER = 5; + } /** * @hide diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 7f6c0d2077f1..7016c5cf0de6 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1213,7 +1213,7 @@ public class RemoteViews implements Parcelable, Filter { BitmapReflectionAction(Parcel in) { viewId = in.readInt(); - methodName = in.readString(); + methodName = in.readString8(); bitmapId = in.readInt(); bitmap = mBitmapCache.getBitmapForId(bitmapId); } @@ -1221,7 +1221,7 @@ public class RemoteViews implements Parcelable, Filter { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(viewId); - dest.writeString(methodName); + dest.writeString8(methodName); dest.writeInt(bitmapId); } @@ -1282,7 +1282,7 @@ public class RemoteViews implements Parcelable, Filter { ReflectionAction(Parcel in) { this.viewId = in.readInt(); - this.methodName = in.readString(); + this.methodName = in.readString8(); this.type = in.readInt(); //noinspection ConstantIfStatement if (false) { @@ -1318,7 +1318,7 @@ public class RemoteViews implements Parcelable, Filter { this.value = (char)in.readInt(); break; case STRING: - this.value = in.readString(); + this.value = in.readString8(); break; case CHAR_SEQUENCE: this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); @@ -1347,7 +1347,7 @@ public class RemoteViews implements Parcelable, Filter { public void writeToParcel(Parcel out, int flags) { out.writeInt(this.viewId); - out.writeString(this.methodName); + out.writeString8(this.methodName); out.writeInt(this.type); //noinspection ConstantIfStatement if (false) { @@ -1383,7 +1383,7 @@ public class RemoteViews implements Parcelable, Filter { out.writeInt((int)((Character)this.value).charValue()); break; case STRING: - out.writeString((String)this.value); + out.writeString8((String)this.value); break; case CHAR_SEQUENCE: TextUtils.writeToParcel((CharSequence)this.value, out, flags); diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 3eb0923363f2..2c4892561ddd 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -50,6 +50,9 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * This is used in conjunction with @@ -74,11 +77,13 @@ public class IntentForwarderActivity extends Activity { private Injector mInjector; private MetricsLogger mMetricsLogger; + protected ExecutorService mExecutorService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mInjector = createInjector(); + mExecutorService = Executors.newSingleThreadExecutor(); Intent intentReceived = getIntent(); String className = intentReceived.getComponent().getClassName(); @@ -118,30 +123,9 @@ public class IntentForwarderActivity extends Activity { mInjector.getIPackageManager(), getContentResolver()); if (newIntent != null) { newIntent.prepareToLeaveUser(callingUserId); - - final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, - targetUserId); - try { - startActivityAsCaller(newIntent, null, null, false, targetUserId); - } catch (RuntimeException e) { - int launchedFromUid = -1; - String launchedFromPackage = "?"; - try { - launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( - getActivityToken()); - launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage( - getActivityToken()); - } catch (RemoteException ignored) { - } - - Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package " - + launchedFromPackage + ", while running in " - + ActivityThread.currentProcessName(), e); - } - - if (shouldShowDisclosure(ri, intentReceived)) { - mInjector.showToast(userMessageId, Toast.LENGTH_LONG); - } + maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId); + CompletableFuture.runAsync(() -> startActivityAsCaller( + newIntent, targetUserId), mExecutorService); } else { Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user " + callingUserId + " to user " + targetUserId); @@ -149,6 +133,44 @@ public class IntentForwarderActivity extends Activity { finish(); } + private void maybeShowDisclosureAsync( + Intent intentReceived, Intent newIntent, int userId, int messageId) { + final CompletableFuture<ResolveInfo> resolveInfoFuture = + mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId); + resolveInfoFuture.thenAcceptAsync(ri -> { + if (shouldShowDisclosure(ri, intentReceived)) { + mInjector.showToast(messageId, Toast.LENGTH_LONG); + } + }, getApplicationContext().getMainExecutor()); + } + + private void startActivityAsCaller(Intent newIntent, int userId) { + try { + startActivityAsCaller( + newIntent, + /* options= */ null, + /* permissionToken= */ null, + /* ignoreTargetSecurity= */ false, + userId); + } catch (RuntimeException e) { + int launchedFromUid = -1; + String launchedFromPackage = "?"; + try { + launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid( + getActivityToken()); + launchedFromPackage = ActivityTaskManager.getService() + .getLaunchedFromPackage(getActivityToken()); + } catch (RemoteException ignored) { + } + + Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package " + + launchedFromPackage + ", while running in " + + ActivityThread.currentProcessName(), e); + } finally { + mExecutorService.shutdown(); + } + } + private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) { // When showing the sharesheet, instead of forwarding to the other profile, // we launch the sharesheet in the current user and select the other tab. @@ -322,8 +344,11 @@ public class IntentForwarderActivity extends Activity { } @Override - public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { - return getPackageManager().resolveActivityAsUser(intent, flags, userId); + @Nullable + public CompletableFuture<ResolveInfo> resolveActivityAsUser( + Intent intent, int flags, int userId) { + return CompletableFuture.supplyAsync( + () -> getPackageManager().resolveActivityAsUser(intent, flags, userId)); } @Override @@ -339,7 +364,7 @@ public class IntentForwarderActivity extends Activity { PackageManager getPackageManager(); - ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId); + CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId); void showToast(@StringRes int messageId, int duration); } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 43bd4a610910..64324756796a 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6093,7 +6093,8 @@ public class BatteryStatsImpl extends BatteryStats { return array; } - public void noteNetworkInterfaceTypeLocked(String iface, int networkType) { + /** @hide */ + public void noteNetworkInterfaceType(String iface, int networkType) { if (TextUtils.isEmpty(iface)) return; synchronized (mModemNetworkLock) { diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index ebfea450af88..56a6db95badc 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -46,6 +46,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver { r.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT), false, this, UserHandle.USER_ALL); + r.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), + false, this, UserHandle.USER_ALL); } public void unregister() { @@ -68,6 +71,11 @@ public class GestureNavigationSettingsObserver extends ContentObserver { return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT); } + public boolean areNavigationButtonForcedVisible() { + return Settings.Secure.getIntForUser(mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0; + } + private int getSensitivity(Resources userRes, String side) { final int inset = userRes.getDimensionPixelSize( com.android.internal.R.dimen.config_backGestureInset); diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java index 7cff90bbf437..adadc5e20549 100644 --- a/core/java/com/android/internal/util/ScreenshotHelper.java +++ b/core/java/com/android/internal/util/ScreenshotHelper.java @@ -1,5 +1,7 @@ package com.android.internal.util; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; @@ -10,11 +12,12 @@ import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; import android.net.Uri; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.util.Log; @@ -23,6 +26,109 @@ import android.view.WindowManager; import java.util.function.Consumer; public class ScreenshotHelper { + + /** + * Describes a screenshot request (to make it easier to pass data through to the handler). + */ + public static class ScreenshotRequest implements Parcelable { + private int mSource; + private boolean mHasStatusBar; + private boolean mHasNavBar; + private Bitmap mBitmap; + private Rect mBoundsInScreen; + private Insets mInsets; + private int mTaskId; + + ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) { + mSource = source; + mHasStatusBar = hasStatus; + mHasNavBar = hasNav; + } + + ScreenshotRequest( + int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) { + mSource = source; + mBitmap = bitmap; + mBoundsInScreen = boundsInScreen; + mInsets = insets; + mTaskId = taskId; + } + + ScreenshotRequest(Parcel in) { + mSource = in.readInt(); + mHasStatusBar = in.readBoolean(); + mHasNavBar = in.readBoolean(); + if (in.readInt() == 1) { + mBitmap = in.readParcelable(Bitmap.class.getClassLoader()); + mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader()); + mInsets = in.readParcelable(Insets.class.getClassLoader()); + mTaskId = in.readInt(); + } + } + + public int getSource() { + return mSource; + } + + public boolean getHasStatusBar() { + return mHasStatusBar; + } + + public boolean getHasNavBar() { + return mHasNavBar; + } + + public Bitmap getBitmap() { + return mBitmap; + } + + public Rect getBoundsInScreen() { + return mBoundsInScreen; + } + + public Insets getInsets() { + return mInsets; + } + + public int getTaskId() { + return mTaskId; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSource); + dest.writeBoolean(mHasStatusBar); + dest.writeBoolean(mHasNavBar); + if (mBitmap == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeParcelable(mBitmap, 0); + dest.writeParcelable(mBoundsInScreen, 0); + dest.writeParcelable(mInsets, 0); + dest.writeInt(mTaskId); + } + } + + public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR = + new Parcelable.Creator<ScreenshotRequest>() { + + @Override + public ScreenshotRequest createFromParcel(Parcel source) { + return new ScreenshotRequest(source); + } + + @Override + public ScreenshotRequest[] newArray(int size) { + return new ScreenshotRequest[size]; + } + }; + } private static final String TAG = "ScreenshotHelper"; // Time until we give up on the screenshot & show an error instead. @@ -36,8 +142,10 @@ public class ScreenshotHelper { mContext = context; } + + /** - * Request a screenshot be taken with a specific timeout. + * Request a screenshot be taken. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -47,6 +155,32 @@ public class ScreenshotHelper { * or * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} * @param hasStatus {@code true} if the status bar is currently showing. {@code false} + * if not. + * @param hasNav {@code true} if the navigation bar is currently showing. {@code + * false} if not. + * @param source The source of the screenshot request. One of + * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD, + * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER} + * @param handler A handler used in case the screenshot times out + * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the + * screenshot was taken. + */ + public void takeScreenshot(final int screenshotType, final boolean hasStatus, + final boolean hasNav, int source, @NonNull Handler handler, + @Nullable Consumer<Uri> completionConsumer) { + ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav); + takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest, + completionConsumer); + } + + /** + * Request a screenshot be taken, with provided reason. + * + * @param screenshotType The type of screenshot, for example either + * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN} + * or + * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param hasStatus {@code true} if the status bar is currently showing. {@code false} * if * not. * @param hasNav {@code true} if the navigation bar is currently showing. {@code @@ -64,7 +198,7 @@ public class ScreenshotHelper { } /** - * Request a screenshot be taken. + * Request a screenshot be taken with a specific timeout. * * Added to support reducing unit test duration; the method variant without a timeout argument * is recommended for general use. @@ -89,9 +223,9 @@ public class ScreenshotHelper { public void takeScreenshot(final int screenshotType, final boolean hasStatus, final boolean hasNav, long timeoutMs, @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { - takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null, - completionConsumer - ); + ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus, + hasNav); + takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer); } /** @@ -106,23 +240,16 @@ public class ScreenshotHelper { * screenshot was taken. */ public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen, - @NonNull Insets insets, int taskId, @NonNull Handler handler, - @Nullable Consumer<Uri> completionConsumer) { - Bundle imageBundle = new Bundle(); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen); - imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets); - imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId); - - takeScreenshot( - WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, - false, false, // ignored when image bundle is set - SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer); + @NonNull Insets insets, int taskId, int source, + @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) { + ScreenshotRequest screenshotRequest = + new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId); + takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS, + handler, screenshotRequest, completionConsumer); } - private void takeScreenshot(final int screenshotType, final boolean hasStatus, - final boolean hasNav, long timeoutMs, @NonNull Handler handler, - @Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) { + private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler, + ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) { synchronized (mScreenshotLock) { if (mScreenshotConnection != null) { return; @@ -157,7 +284,7 @@ public class ScreenshotHelper { return; } Messenger messenger = new Messenger(service); - Message msg = Message.obtain(null, screenshotType); + Message msg = Message.obtain(null, screenshotType, screenshotRequest); final ServiceConnection myConn = this; Handler h = new Handler(handler.getLooper()) { @Override @@ -175,12 +302,6 @@ public class ScreenshotHelper { } }; msg.replyTo = new Messenger(h); - msg.arg1 = hasStatus ? 1 : 0; - msg.arg2 = hasNav ? 1 : 0; - - if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { - msg.setData(providedImage); - } try { messenger.send(msg); diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 483b455965f7..e7a2fb428b50 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -273,7 +273,28 @@ static void android_os_Parcel_writeDouble(JNIEnv* env, jclass clazz, jlong nativ } } -static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val) +static void android_os_Parcel_writeString8(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val) +{ + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); + if (parcel != NULL) { + status_t err = NO_MEMORY; + if (val) { + const size_t len = env->GetStringUTFLength(val); + const char* str = env->GetStringUTFChars(val, 0); + if (str) { + err = parcel->writeString8(str, len); + env->ReleaseStringUTFChars(val, str); + } + } else { + err = parcel->writeString8(NULL, 0); + } + if (err != NO_ERROR) { + signalExceptionForError(env, clazz, err); + } + } +} + +static void android_os_Parcel_writeString16(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel != NULL) { @@ -444,7 +465,21 @@ static jdouble android_os_Parcel_readDouble(jlong nativePtr) return 0; } -static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jlong nativePtr) +static jstring android_os_Parcel_readString8(JNIEnv* env, jclass clazz, jlong nativePtr) +{ + Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); + if (parcel != NULL) { + size_t len; + const char* str = parcel->readString8Inplace(&len); + if (str) { + return env->NewStringUTF(str); + } + return NULL; + } + return NULL; +} + +static jstring android_os_Parcel_readString16(JNIEnv* env, jclass clazz, jlong nativePtr) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel != NULL) { @@ -722,7 +757,9 @@ static const JNINativeMethod gParcelMethods[] = { // @FastNative {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble}, // @FastNative - {"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString}, + {"nativeWriteString8", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString8}, + // @FastNative + {"nativeWriteString16", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString16}, // @FastNative {"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder}, // @FastNative @@ -740,7 +777,9 @@ static const JNINativeMethod gParcelMethods[] = { // @CriticalNative {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble}, // @FastNative - {"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString}, + {"nativeReadString8", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString8}, + // @FastNative + {"nativeReadString16", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString16}, // @FastNative {"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder}, // @FastNative diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 924dc4b3a051..21985f0bb4bb 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -119,6 +119,7 @@ typedef const std::function<void(std::string)>& fail_fn_t; static pid_t gSystemServerPid = 0; +static constexpr const char* kVoldAppDataIsolation = "persist.sys.vold_app_data_isolation_enabled"; static constexpr const char* kPropFuse = "persist.sys.fuse"; static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; static jclass gZygoteClass; @@ -831,6 +832,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn); bool isFuse = GetBoolProperty(kPropFuse, false); + bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false); if (isFuse) { if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) { @@ -840,6 +842,9 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) { const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id); BindMount(installer_source, "/storage", fail_fn); + } else if (isAppDataIsolationEnabled && mount_mode == MOUNT_EXTERNAL_ANDROID_WRITABLE) { + const std::string writable_source = StringPrintf("/mnt/androidwritable/%d", user_id); + BindMount(writable_source, "/storage", fail_fn); } else { BindMount(user_source, "/storage", fail_fn); } diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml index 5890bed11b07..25615d2dc304 100644 --- a/core/res/res/layout/resolver_empty_states.xml +++ b/core/res/res/layout/resolver_empty_states.xml @@ -43,6 +43,7 @@ android:fontFamily="@string/config_headlineFontFamilyMedium" android:textColor="@color/resolver_empty_state_text" android:textSize="14sp" + android:gravity="center_horizontal" android:layout_centerHorizontal="true" /> <TextView android:id="@+id/resolver_empty_state_subtitle" diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 1a9855311518..8f16e530dfe0 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3496,10 +3496,9 @@ <!-- Do not translate. Mcc codes whose existence trigger the presence of emergency affordances--> - <integer-array name="config_emergency_mcc_codes" translatable="false"> - <item>404</item> - <item>405</item> - </integer-array> + <string-array name="config_emergency_iso_country_codes" translatable="false"> + <item>in</item> + </string-array> <!-- Package name for the device provisioning package. --> <string name="config_deviceProvisioningPackage"></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 05c00ce51ee3..ebc3612ca1e6 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3085,7 +3085,7 @@ <java-symbol type="string" name="global_action_emergency" /> <java-symbol type="string" name="config_emergency_call_number" /> <java-symbol type="string" name="config_emergency_dialer_package" /> - <java-symbol type="array" name="config_emergency_mcc_codes" /> + <java-symbol type="array" name="config_emergency_iso_country_codes" /> <java-symbol type="string" name="config_dozeDoubleTapSensorType" /> <java-symbol type="string" name="config_dozeTapSensorType" /> diff --git a/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java new file mode 100644 index 000000000000..daa90c28e0c4 --- /dev/null +++ b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import com.google.caliper.AfterExperiment; +import com.google.caliper.BeforeExperiment; +import com.google.caliper.Param; + +public class ParcelStringBenchmark { + + @Param({"com.example.typical_package_name", "從不喜歡孤單一個 - 蘇永康/吳雨霏"}) + String mValue; + + private Parcel mParcel; + + @BeforeExperiment + protected void setUp() { + mParcel = Parcel.obtain(); + } + + @AfterExperiment + protected void tearDown() { + mParcel.recycle(); + mParcel = null; + } + + public void timeWriteString8(int reps) { + for (int i = 0; i < reps; i++) { + mParcel.setDataPosition(0); + mParcel.writeString8(mValue); + } + } + + public void timeReadString8(int reps) { + mParcel.writeString8(mValue); + + for (int i = 0; i < reps; i++) { + mParcel.setDataPosition(0); + mParcel.readString8(); + } + } + + public void timeWriteString16(int reps) { + for (int i = 0; i < reps; i++) { + mParcel.setDataPosition(0); + mParcel.writeString16(mValue); + } + } + + public void timeReadString16(int reps) { + mParcel.writeString16(mValue); + + for (int i = 0; i < reps; i++) { + mParcel.setDataPosition(0); + mParcel.readString16(); + } + } +} diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java index 46873b9eb70b..dcb3e2f23da8 100644 --- a/core/tests/coretests/src/android/os/ParcelTest.java +++ b/core/tests/coretests/src/android/os/ParcelTest.java @@ -87,4 +87,27 @@ public class ParcelTest { p.recycle(); } + + /** + * Verify that writing/reading UTF-8 and UTF-16 strings works well. + */ + @Test + public void testStrings() { + final String[] strings = { + null, "", "abc\0def", "com.example.typical_package_name", + "從不喜歡孤單一個 - 蘇永康/吳雨霏", "example" + }; + + final Parcel p = Parcel.obtain(); + for (String string : strings) { + p.writeString8(string); + p.writeString16(string); + } + + p.setDataPosition(0); + for (String string : strings) { + assertEquals(string, p.readString8()); + assertEquals(string, p.readString16()); + } + } } diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 5424b6f19038..43590bae6770 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class IntentForwarderActivityTest { @@ -633,6 +635,11 @@ public class IntentForwarderActivityTest { public void onCreate(@Nullable Bundle savedInstanceState) { getIntent().setComponent(sComponentName); super.onCreate(savedInstanceState); + try { + mExecutorService.awaitTermination(/* timeout= */ 30, TimeUnit.SECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } } @Override @@ -671,7 +678,8 @@ public class IntentForwarderActivityTest { } @Override - public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) { + public CompletableFuture<ResolveInfo> resolveActivityAsUser( + Intent intent, int flags, int userId) { ActivityInfo activityInfo = new ActivityInfo(); activityInfo.packageName = sPackageName; activityInfo.name = sActivityName; @@ -680,7 +688,7 @@ public class IntentForwarderActivityTest { ResolveInfo resolveInfo = new ResolveInfo(); resolveInfo.activityInfo = activityInfo; - return resolveInfo; + return CompletableFuture.completedFuture(resolveInfo); } @Override diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java index cd6b3af5fa6d..fe33cd80f735 100644 --- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java +++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java @@ -36,6 +36,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Handler; import android.os.Looper; +import android.view.WindowManager; import androidx.test.runner.AndroidJUnit4; @@ -91,7 +92,8 @@ public final class ScreenshotHelperTest { public void testProvidedImageScreenshot() { mScreenshotHelper.provideScreenshot( Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(), - Insets.of(0, 0, 0, 0), 1, mHandler, null); + Insets.of(0, 0, 0, 0), 1, + WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null); } @Test diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java index 1db2f6357308..b351b3d77430 100644 --- a/identity/java/android/security/identity/IdentityCredential.java +++ b/identity/java/android/security/identity/IdentityCredential.java @@ -95,9 +95,7 @@ public abstract class IdentityCredential { /** * Sets whether to allow using an authentication key which use count has been exceeded if no * other key is available. This must be called prior to calling - * {@link #getEntries(byte[], Map, byte[], byte[])} or using a - * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this - * object. + * {@link #getEntries(byte[], Map, byte[], byte[])}. * * By default this is set to true. * @@ -123,13 +121,14 @@ public abstract class IdentityCredential { * entries. * * <p>It is the responsibility of the calling application to know if authentication is needed - * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user + * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which * references this object. If needed, this must be done before calling * {@link #getEntries(byte[], Map, byte[], byte[])}. * - * <p>If this method returns successfully (i.e. without throwing an exception), it must not be - * called again on this instance. + * <p>It is permissible to call this method multiple times using the same instance but if this + * is done, the {@code sessionTranscript} parameter must be identical for each call. If this is + * not the case, the {@link SessionTranscriptMismatchException} exception is thrown. * * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request * from the verifier. The content can be defined in the way appropriate for the credential, byt @@ -141,6 +140,9 @@ public abstract class IdentityCredential { * the example below.</li> * </ul> * + * <p>If these requirements are not met the {@link InvalidRequestMessageException} exception + * is thrown. + * * <p>Here's an example of CBOR which conforms to this requirement: * <pre> * ItemsRequest = { @@ -149,6 +151,8 @@ public abstract class IdentityCredential { * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide * } * + * DocType = tstr + * * NameSpaces = { * + NameSpace => DataElements ; Requested data elements for each NameSpace * } @@ -172,16 +176,18 @@ public abstract class IdentityCredential { * EReaderKeyBytes * ] * - * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) - * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) + * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) ; Bytes of DeviceEngagement + * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) ; Bytes of EReaderKey.pub + * + * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader * </pre> * - * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part - * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear - * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present + * <p>where a {@code COSE_Key} structure for the public part of the key-pair previously + * generated by {@link #createEphemeralKeyPair()} must appear somewhere in + * {@code DeviceEngagement} and the X and Y coordinates must both be present * in uncompressed form. * - * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1 + * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1} * structure as defined in RFC 8152. For the payload nil shall be used and the * detached payload is the ReaderAuthentication CBOR described below. * <pre> @@ -194,20 +200,23 @@ public abstract class IdentityCredential { * ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest * </pre> * - * <p>The public key corresponding to the key used to made signature, can be - * found in the {@code x5chain} unprotected header element of the COSE_Sign1 - * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at - * least one certificate in said element and there may be more (and if so, + * <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter. + * + * <p>The public key corresponding to the key used to make the signature, can be found in the + * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as + * described in + * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-04">draft-ietf-cose-x509-04</a>). + * There will be at least one certificate in said element and there may be more (and if so, * each certificate must be signed by its successor). * - * <p>Data elements protected by reader authentication is returned if, and only if, they are + * <p>Data elements protected by reader authentication are returned if, and only if, they are * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most - * certificate in {@code readerCertificateChain}, and the data element is configured - * with an {@link AccessControlProfile} with a {@link X509Certificate} in - * {@code readerCertificateChain}. + * certificate in the reader's certificate chain, and the data element is configured + * with an {@link AccessControlProfile} configured with an X.509 certificate which appears + * in the certificate chain. * * <p>Note that only items referenced in {@code entriesToRequest} are returned - the - * {@code requestMessage} parameter is only used to for enforcing reader authentication. + * {@code requestMessage} parameter is used only for enforcing reader authentication. * * <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate * parameters is that the former represents a request from the remote verifier device @@ -219,13 +228,12 @@ public abstract class IdentityCredential { * @param entriesToRequest The entries to request, organized as a map of namespace * names with each value being a collection of data elements * in the given namespace. - * @param readerSignature COSE_Sign1 structure as described above or {@code null} - * if reader authentication is not being used. + * @param readerSignature A {@code COSE_Sign1} structure as described above or + * {@code null} if reader authentication is not being used. * @return A {@link ResultData} object containing entry data organized by namespace and a * cryptographically authenticated representation of the same data. * @throws SessionTranscriptMismatchException Thrown when trying use multiple different - * session transcripts in the same presentation - * session. + * session transcripts. * @throws NoAuthenticationKeyAvailableException if authentication keys were never * provisioned, the method * {@link #setAvailableAuthenticationKeys(int, int)} @@ -255,8 +263,8 @@ public abstract class IdentityCredential { * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain, * and the number of times each should be used. * - * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each - * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s + * <p>The Identity Credential system will select the least-used dynamic authentication key each + * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials * for which this method has not been called behave as though it had been called wit * {@code keyCount} 0 and {@code maxUsesPerKey} 1. * @@ -274,9 +282,10 @@ public abstract class IdentityCredential { * <p>When there aren't enough certified dynamic authentication keys, either because the key * count has been increased or because one or more keys have reached their usage count, this * method will generate replacement keys and certificates and return them for issuer - * certification. The issuer certificates and associated static authentication data must then - * be provided back to the {@code IdentityCredential} using - * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}. + * certification. The issuer certificates and associated static authentication data must then + * be provided back to the Identity Credential using + * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}. The private part of + * each authentication key never leaves secure hardware. * * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey * can be obtained using the {@link #getCredentialKeyCertificateChain()} method. diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java index a1dfc77adb29..4f834d2b87b5 100644 --- a/identity/java/android/security/identity/IdentityCredentialStore.java +++ b/identity/java/android/security/identity/IdentityCredentialStore.java @@ -78,17 +78,21 @@ public abstract class IdentityCredentialStore { /** * Specifies that the cipher suite that will be used to secure communications between the reader - * is: + * and the prover is using the following primitives * * <ul> - * <li>ECDHE with HKDF-SHA-256 for key agreement.</li> - * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one - * for every message).</li> - * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat - * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential} - * for details on reader and prover signing keys.</li> + * <li>ECKA-DH (Elliptic Curve Key Agreement Algorithm - Diffie-Hellman, see BSI TR-03111).</li> + * + * <li>HKDF-SHA-256 (see RFC 5869).</li> + * + * <li>AES-256-GCM (see NIST SP 800-38D).</li> + * + * <li>HMAC-SHA-256 (see RFC 2104).</li> * </ul> * + * <p>The exact way these primitives are combined to derive the session key is specified in + * section 9.2.1.4 of ISO/IEC 18013-5 (see description of cipher suite '1').<p> + * * <p> * At present this is the only supported cipher suite. */ @@ -135,9 +139,20 @@ public abstract class IdentityCredentialStore { /** * Creates a new credential. * + * <p>When a credential is created, a cryptographic key-pair - CredentialKey - is created which + * is used to authenticate the store to the Issuing Authority. The private part of this + * key-pair never leaves secure hardware and the public part can be obtained using + * {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])} on the + * returned object. + * + * <p>In addition, all of the Credential data content is imported and a certificate for the + * CredentialKey and a signature produced with the CredentialKey are created. These latter + * values may be checked by an issuing authority to verify that the data was imported into + * secure hardware and that it was imported unmodified. + * * @param credentialName The name used to identify the credential. * @param docType The document type for the credential. - * @return A @{link WritableIdentityCredential} that can be used to create a new credential. + * @return A {@link WritableIdentityCredential} that can be used to create a new credential. * @throws AlreadyPersonalizedException if a credential with the given name already exists. * @throws DocTypeNotSupportedException if the given document type isn't supported by the store. */ @@ -148,6 +163,10 @@ public abstract class IdentityCredentialStore { /** * Retrieve a named credential. * + * <p>The cipher suite used to communicate with the remote verifier must also be specified. + * Currently only a single cipher-suite is supported. Support for other cipher suites may be + * added in a future version of this API. + * * @param credentialName the name of the credential to retrieve. * @param cipherSuite the cipher suite to use for communicating with the verifier. * @return The named credential, or null if not found. diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java index 13552d619e05..37de2c4a50ea 100644 --- a/identity/java/android/security/identity/ResultData.java +++ b/identity/java/android/security/identity/ResultData.java @@ -34,23 +34,23 @@ public abstract class ResultData { /** Value was successfully retrieved. */ public static final int STATUS_OK = 0; - /** Requested entry does not exist. */ + /** The entry does not exist. */ public static final int STATUS_NO_SUCH_ENTRY = 1; - /** Requested entry was not requested. */ + /** The entry was not requested. */ public static final int STATUS_NOT_REQUESTED = 2; - /** Requested entry wasn't in the request message. */ + /** The entry wasn't in the request message. */ public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3; - /** The requested entry was not retrieved because user authentication wasn't performed. */ + /** The entry was not retrieved because user authentication failed. */ public static final int STATUS_USER_AUTHENTICATION_FAILED = 4; - /** The requested entry was not retrieved because reader authentication wasn't performed. */ + /** The entry was not retrieved because reader authentication failed. */ public static final int STATUS_READER_AUTHENTICATION_FAILED = 5; /** - * The requested entry was not retrieved because it was configured without any access + * The entry was not retrieved because it was configured without any access * control profile. */ public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6; @@ -88,11 +88,10 @@ public abstract class ResultData { * * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) - * * DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces) * </pre> * - * where + * <p>where * * <pre> * DeviceNameSpaces = { @@ -116,15 +115,16 @@ public abstract class ResultData { public abstract @NonNull byte[] getAuthenticatedData(); /** - * Returns a message authentication code over the data returned by - * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted - * credential. + * Returns a message authentication code over the {@code DeviceAuthentication} CBOR + * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data + * is from a trusted credential. * * <p>The MAC proves to the reader that the data is from a trusted credential. This code is * produced by using the key agreement and key derivation function from the ciphersuite * with the authentication private key and the reader ephemeral public key to compute a * shared message authentication code (MAC) key, then using the MAC function from the - * ciphersuite to compute a MAC of the authenticated data. + * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of + * ISO/IEC 18013-5 for details of this operation. * * <p>If the {@code sessionTranscript} parameter passed to * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null} @@ -157,7 +157,7 @@ public abstract class ResultData { /** * Get the names of all entries. * - * This includes the name of entries that wasn't successfully retrieved. + * <p>This includes the name of entries that wasn't successfully retrieved. * * @param namespaceName the namespace name to get entries for. * @return A collection of names or {@code null} if there are no entries for the given @@ -168,7 +168,7 @@ public abstract class ResultData { /** * Get the names of all entries that was successfully retrieved. * - * This only return entries for which {@link #getStatus(String, String)} will return + * <p>This only return entries for which {@link #getStatus(String, String)} will return * {@link #STATUS_OK}. * * @param namespaceName the namespace name to get entries for. @@ -181,16 +181,15 @@ public abstract class ResultData { /** * Gets the status of an entry. * - * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY} + * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY} * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested, * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't - * present in the request message, - * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value + * present in the request message, {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value * wasn't retrieved because the necessary user authentication wasn't performed, - * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain - * didn't match the set of certificates the entry was provisioned with, or - * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any - * access control profiles. + * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain didn't + * match the set of certificates the entry was provisioned with, or + * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any access + * control profiles. * * @param namespaceName the namespace name of the entry. * @param name the name of the entry to get the value for. @@ -201,7 +200,7 @@ public abstract class ResultData { /** * Gets the raw CBOR data for the value of an entry. * - * This should only be called on an entry for which the {@link #getStatus(String, String)} + * <p>This should only be called on an entry for which the {@link #getStatus(String, String)} * method returns {@link #STATUS_OK}. * * @param namespaceName the namespace name of the entry. diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java index e2a389bfd4da..c7aa32855abc 100644 --- a/identity/java/android/security/identity/WritableIdentityCredential.java +++ b/identity/java/android/security/identity/WritableIdentityCredential.java @@ -41,15 +41,16 @@ public abstract class WritableIdentityCredential { * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a> * attestation extension which describes the key and the security hardware in which it lives. * - * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates - * it is an Identity Credential key (which can only sign/MAC very specific messages) and not - * an Android Keystore key (which can be used to sign/MAC anything). + * <p>Additionally, the attestation extension will contain the tag Tag::IDENTITY_CREDENTIAL_KEY + * which indicates it is an Identity Credential key (which can only sign/MAC very specific + * messages) and not an Android Keystore key (which can be used to sign/MAC anything). * * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not - * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is - * present, the passed in challenge is present, the device has verified boot enabled, that each - * certificate in the chain is signed by its successor, that none of the certificates have been - * revoked and so on. + * limited to) checking that the root certificate is well-known, the tag + * Tag::IDENTITY_CREDENTIAL_KEY present, the passed in challenge is present, the tag + * Tag::ATTESTATION_APPLICATION_ID is set to the expected Android application, the device + * has verified boot enabled, each certificate in the chain is signed by its successor, + * none of the certificates have been revoked, and so on. * * <p>It is not strictly necessary to use this method to provision a credential if the issuing * authority doesn't care about the nature of the security hardware. If called, however, this diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java index 14249cbe8945..8e8dfaf24b9a 100644 --- a/media/java/android/media/AudioPortEventHandler.java +++ b/media/java/android/media/AudioPortEventHandler.java @@ -78,7 +78,8 @@ class AudioPortEventHandler { listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj); } } else { - listeners = mListeners; + listeners = (ArrayList<AudioManager.OnAudioPortUpdateListener>) + mListeners.clone(); } } // reset audio port cache if the event corresponds to a change coming diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl index dc06153ffa9f..a8b82ba401eb 100644 --- a/media/java/android/media/IMediaRouter2.aidl +++ b/media/java/android/media/IMediaRouter2.aidl @@ -24,11 +24,15 @@ import android.os.Bundle; * @hide */ oneway interface IMediaRouter2 { - void notifyRestoreRoute(); void notifyRoutesAdded(in List<MediaRoute2Info> routes); void notifyRoutesRemoved(in List<MediaRoute2Info> routes); void notifyRoutesChanged(in List<MediaRoute2Info> routes); void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo); void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo); void notifySessionReleased(in RoutingSessionInfo sessionInfo); + /** + * Gets hints of the new session for the given route. + * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result. + */ + void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 0d87736cfd47..52bac671cc6f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -59,6 +59,8 @@ interface IMediaRouterService { void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, in MediaRoute2Info route, in @nullable Bundle sessionHints); + void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId, + in MediaRoute2Info route, in @nullable Bundle sessionHints); void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java index 97d32d88c7f2..c652628eb425 100644 --- a/media/java/android/media/MediaCas.java +++ b/media/java/android/media/MediaCas.java @@ -388,7 +388,7 @@ public final class MediaCas implements AutoCloseable { @Override public void onReclaimResources() { synchronized (mSessionMap) { - mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close()); + mSessionMap.forEach((casSession, sessionResourceHandle) -> casSession.close()); } mEventHandler.sendMessage(mEventHandler.obtainMessage( EventHandler.MSG_CAS_RESOURCE_LOST)); @@ -868,7 +868,7 @@ public final class MediaCas implements AutoCloseable { } } - private int getSessionResourceId() throws MediaCasException { + private int getSessionResourceHandle() throws MediaCasException { validateInternalStates(); int[] sessionResourceHandle = new int[1]; @@ -881,14 +881,14 @@ public final class MediaCas implements AutoCloseable { "insufficient resource to Open Session"); } } - return sessionResourceHandle[0]; + return sessionResourceHandle[0]; } - private void addSessionToResourceMap(Session session, int sessionResourceId) { + private void addSessionToResourceMap(Session session, int sessionResourceHandle) { - if (sessionResourceId != -1) { + if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) { synchronized (mSessionMap) { - mSessionMap.put(session, sessionResourceId); + mSessionMap.put(session, sessionResourceHandle); } } } @@ -918,13 +918,13 @@ public final class MediaCas implements AutoCloseable { * @throws MediaCasStateException for CAS-specific state exceptions. */ public Session openSession() throws MediaCasException { - int sessionResourceId = getSessionResourceId(); + int sessionResourceHandle = getSessionResourceHandle(); try { OpenSessionCallback cb = new OpenSessionCallback(); mICas.openSession(cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); - addSessionToResourceMap(cb.mSession, sessionResourceId); + addSessionToResourceMap(cb.mSession, sessionResourceHandle); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); @@ -952,7 +952,7 @@ public final class MediaCas implements AutoCloseable { @Nullable public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode) throws MediaCasException { - int sessionResourceId = getSessionResourceId(); + int sessionResourceHandle = getSessionResourceHandle(); if (mICasV12 == null) { Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface"); @@ -963,7 +963,7 @@ public final class MediaCas implements AutoCloseable { OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback(); mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb); MediaCasException.throwExceptionIfNeeded(cb.mStatus); - addSessionToResourceMap(cb.mSession, sessionResourceId); + addSessionToResourceMap(cb.mSession, sessionResourceHandle); return cb.mSession; } catch (RemoteException e) { cleanupAndRethrowIllegalState(); diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index cfe6db9fe3cf..0ea962493164 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -690,6 +690,31 @@ public final class MediaRouter2 { matchingController.releaseInternal(/* shouldReleaseSession= */ false); } + void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId, + MediaRoute2Info route) { + OnGetControllerHintsListener listener = mOnGetControllerHintsListener; + Bundle controllerHints = null; + if (listener != null) { + controllerHints = listener.onGetControllerHints(route); + if (controllerHints != null) { + controllerHints = new Bundle(controllerHints); + } + } + + MediaRouter2Stub stub; + synchronized (sRouterLock) { + stub = mStub; + } + if (stub != null) { + try { + mMediaRouterService.notifySessionHintsForCreatingSession( + stub, uniqueRequestId, route, controllerHints); + } catch (RemoteException ex) { + Log.e(TAG, "getSessionHintsOnHandler: Unable to request.", ex); + } + } + } + private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes, RouteDiscoveryPreference discoveryRequest) { return routes.stream() @@ -820,13 +845,14 @@ public final class MediaRouter2 { */ public interface OnGetControllerHintsListener { /** - * Called when the {@link MediaRouter2} is about to request - * the media route provider service to create a controller with the given route. + * Called when the {@link MediaRouter2} or the system is about to request + * a media route provider service to create a controller with the given route. * The {@link Bundle} returned here will be sent to media route provider service as a hint. * <p> - * To send hints when creating the controller, set the listener before calling - * {@link #transferTo(MediaRoute2Info)}. The method will be called - * on the same thread which calls {@link #transferTo(MediaRoute2Info)}. + * Since controller creation can be requested by the {@link MediaRouter2} and the system, + * set the listener as soon as possible after acquiring {@link MediaRouter2} instance. + * The method will be called on the same thread that calls + * {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system. * * @param route The route to create controller with * @return An optional bundle of app-specific arguments to send to the provider, @@ -1378,9 +1404,6 @@ public final class MediaRouter2 { class MediaRouter2Stub extends IMediaRouter2.Stub { @Override - public void notifyRestoreRoute() throws RemoteException {} - - @Override public void notifyRoutesAdded(List<MediaRoute2Info> routes) { mHandler.sendMessage(obtainMessage(MediaRouter2::addRoutesOnHandler, MediaRouter2.this, routes)); @@ -1415,5 +1438,13 @@ public final class MediaRouter2 { mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler, MediaRouter2.this, sessionInfo)); } + + @Override + public void getSessionHintsForCreatingSession(long uniqueRequestId, + @NonNull MediaRoute2Info route) { + mHandler.sendMessage(obtainMessage( + MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler, + MediaRouter2.this, uniqueRequestId, route)); + } } } diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java index adf4d3dcfa09..022cfeeb4e43 100644 --- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java +++ b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java @@ -80,7 +80,8 @@ public class AudioVolumeGroupChangeHandler { (AudioManager.VolumeGroupCallback) msg.obj); } } else { - listeners = mListeners; + listeners = (ArrayList<AudioManager.VolumeGroupCallback>) + mListeners.clone(); } } if (listeners.isEmpty()) { diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl index 7077cd1b76a0..487b444eb627 100644 --- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl +++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl @@ -218,11 +218,11 @@ interface ITunerResourceManager { * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request. * * @param request {@link CasSessionRequest} information of the current request. - * @param sessionResourceId a one-element array to return the granted cas session id. + * @param casSessionHandle a one-element array to return the granted cas session handle. * * @return true if there is CAS session granted. */ - boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId); + boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle); /* * This API is used by the Tuner framework to request an available Lnb from the TunerHAL. @@ -276,7 +276,7 @@ interface ITunerResourceManager { * * <p>Client must call this whenever it releases a descrambler. * - * @param demuxHandle the handle of the released Tuner Descrambler. + * @param descramblerHandle the handle of the released Tuner Descrambler. * @param clientId the id of the client that is releasing the descrambler. */ void releaseDescrambler(in int descramblerHandle, int clientId); @@ -288,10 +288,10 @@ interface ITunerResourceManager { * * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release. * - * @param sessionResourceId the id of the released CAS session. + * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - void releaseCasSession(in int sessionResourceId, int clientId); + void releaseCasSession(in int casSessionHandle, int clientId); /* * Notifies the TRM that the Lnb with the given handle was released. diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java index b4dcc5d8b3df..be102d8acc10 100644 --- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java +++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java @@ -362,17 +362,16 @@ public class TunerResourceManager { * request. * * @param request {@link CasSessionRequest} information of the current request. - * @param sessionResourceId a one-element array to return the granted cas session id. - * If no CAS granted, this will return - * {@link #INVALID_CAS_SESSION_RESOURCE_ID}. + * @param casSessionHandle a one-element array to return the granted cas session handel. + * If no CAS granted, this will return {@link #INVALID_RESOURCE_HANDLE}. * * @return true if there is CAS session granted. */ public boolean requestCasSession(@NonNull CasSessionRequest request, - @NonNull int[] sessionResourceId) { + @NonNull int[] casSessionHandle) { boolean result = false; try { - result = mService.requestCasSession(request, sessionResourceId); + result = mService.requestCasSession(request, casSessionHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -471,12 +470,12 @@ public class TunerResourceManager { * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this * release. * - * @param sessionResourceId the id of the released CAS session. + * @param casSessionHandle the handle of the released CAS session. * @param clientId the id of the client that is releasing the cas session. */ - public void releaseCasSession(int sessionResourceId, int clientId) { + public void releaseCasSession(int casSessionHandle, int clientId) { try { - mService.releaseCasSession(sessionResourceId, clientId); + mService.releaseCasSession(casSessionHandle, clientId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java index 207534b36f95..6a1e9656cf2c 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java @@ -48,6 +48,7 @@ import android.media.MediaRouter2Manager; import android.media.MediaRouter2Utils; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; +import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -74,6 +75,8 @@ public class MediaRouter2ManagerTest { private static final String TAG = "MediaRouter2ManagerTest"; private static final int WAIT_TIME_MS = 2000; private static final int TIMEOUT_MS = 5000; + private static final String TEST_KEY = "test_key"; + private static final String TEST_VALUE = "test_value"; private Context mContext; private MediaRouter2Manager mManager; @@ -513,6 +516,56 @@ public class MediaRouter2ManagerTest { assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax()); } + @Test + public void testRouter2SetOnGetControllerHintsListener() throws Exception { + Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL); + addRouterCallback(new RouteCallback() {}); + + MediaRoute2Info route = routes.get(ROUTE_ID1); + assertNotNull(route); + + final Bundle controllerHints = new Bundle(); + controllerHints.putString(TEST_KEY, TEST_VALUE); + final CountDownLatch hintLatch = new CountDownLatch(1); + final MediaRouter2.OnGetControllerHintsListener listener = + route1 -> { + hintLatch.countDown(); + return controllerHints; + }; + + final CountDownLatch successLatch = new CountDownLatch(1); + final CountDownLatch failureLatch = new CountDownLatch(1); + + addManagerCallback(new MediaRouter2Manager.Callback() { + @Override + public void onTransferred(RoutingSessionInfo oldSession, + RoutingSessionInfo newSession) { + assertTrue(newSession.getSelectedRoutes().contains(route.getId())); + // The StubMediaRoute2ProviderService is supposed to set control hints + // with the given controllerHints. + Bundle controlHints = newSession.getControlHints(); + assertNotNull(controlHints); + assertTrue(controlHints.containsKey(TEST_KEY)); + assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY)); + + successLatch.countDown(); + } + + @Override + public void onTransferFailed(RoutingSessionInfo session, + MediaRoute2Info requestedRoute) { + failureLatch.countDown(); + } + }); + + mRouter2.setOnGetControllerHintsListener(listener); + mManager.selectRoute(mPackageName, route); + assertTrue(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + + assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)); + } + Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures) throws Exception { CountDownLatch addedLatch = new CountDownLatch(1); diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml index 7004fb64ba06..8b235e6f246e 100644 --- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml +++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml @@ -29,8 +29,6 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" - androidprv:layout_maxWidth="@dimen/keyguard_security_width" - androidprv:layout_maxHeight="@dimen/keyguard_security_height" android:gravity="center"> <include layout="@layout/keyguard_message_area" /> diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml index ce0d31c6cc47..a7347f22f2e5 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml @@ -45,7 +45,7 @@ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" /> - <com.android.systemui.statusbar.hvac.AnimatedTemperatureView + <com.android.systemui.car.hvac.AnimatedTemperatureView android:id="@+id/lefttext" android:layout_width="wrap_content" android:layout_height="match_parent" @@ -127,7 +127,7 @@ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" /> - <com.android.systemui.statusbar.hvac.AnimatedTemperatureView + <com.android.systemui.car.hvac.AnimatedTemperatureView android:id="@+id/righttext" android:layout_width="wrap_content" android:layout_height="match_parent" diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml index a71567c48eaf..aa0a8c584896 100644 --- a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml +++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml @@ -45,7 +45,7 @@ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" /> - <com.android.systemui.statusbar.hvac.AnimatedTemperatureView + <com.android.systemui.car.hvac.AnimatedTemperatureView android:id="@+id/lefttext" android:layout_width="wrap_content" android:layout_height="match_parent" @@ -123,7 +123,7 @@ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end" /> - <com.android.systemui.statusbar.hvac.AnimatedTemperatureView + <com.android.systemui.car.hvac.AnimatedTemperatureView android:id="@+id/righttext" android:layout_width="wrap_content" android:layout_height="match_parent" diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index e6fb501d02e1..aeff35d88e23 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -86,29 +86,29 @@ <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> - <item>com.android.systemui.recents.Recents</item> +<!-- <item>com.android.systemui.recents.Recents</item>--> <item>com.android.systemui.volume.VolumeUI</item> - <item>com.android.systemui.stackdivider.Divider</item> +<!-- <item>com.android.systemui.stackdivider.Divider</item>--> <!-- <item>com.android.systemui.statusbar.phone.StatusBar</item>--> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> - <item>com.android.systemui.keyboard.KeyboardUI</item> - <item>com.android.systemui.pip.PipUI</item> - <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> +<!-- <item>com.android.systemui.keyboard.KeyboardUI</item>--> +<!-- <item>com.android.systemui.pip.PipUI</item>--> +<!-- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>--> <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> - <item>com.android.systemui.LatencyTester</item> - <item>com.android.systemui.globalactions.GlobalActionsComponent</item> +<!-- <item>com.android.systemui.LatencyTester</item>--> +<!-- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>--> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.biometrics.AuthController</item> - <item>com.android.systemui.SliceBroadcastRelayHandler</item> +<!-- <item>com.android.systemui.SliceBroadcastRelayHandler</item>--> <item>com.android.systemui.SizeCompatModeActivityController</item> - <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> +<!-- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>--> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.navigationbar.car.CarNavigationBar</item> <item>com.android.systemui.toast.ToastUI</item> - <item>com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier</item> + <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item> <item>com.android.systemui.window.SystemUIOverlayWindowManager</item> </string-array> </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java index 59fa9d09c9ee..a69c92f0bd05 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java @@ -19,6 +19,7 @@ package com.android.systemui; import com.android.systemui.biometrics.AuthController; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.car.notification.CarNotificationModule; +import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier; import com.android.systemui.globalactions.GlobalActionsComponent; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.dagger.KeyguardModule; @@ -38,7 +39,6 @@ import com.android.systemui.statusbar.tv.TvStatusBar; import com.android.systemui.theme.ThemeOverlayController; import com.android.systemui.toast.ToastUI; import com.android.systemui.util.leak.GarbageMonitor; -import com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier; import com.android.systemui.volume.VolumeUI; import com.android.systemui.window.OverlayWindowModule; import com.android.systemui.window.SystemUIOverlayWindowManager; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java index 908aaad71893..a7294317f46c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.hvac; +package com.android.systemui.car.hvac; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; @@ -35,7 +35,6 @@ import android.widget.TextSwitcher; import android.widget.TextView; import com.android.systemui.R; -import com.android.systemui.navigationbar.car.hvac.TemperatureView; /** * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java index fd9c488278ba..af8ddb6a8180 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.navigationbar.car.hvac; +package com.android.systemui.car.hvac; import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL; import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java index 3c6d623c8ff7..a4c45730a9c2 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,15 +11,15 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.hvac; +package com.android.systemui.car.hvac; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isTop; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isVertical; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java index a40ffaf850c5..9a7b0b9819c5 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,10 +11,10 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.hvac; +package com.android.systemui.car.hvac; import android.graphics.Color; diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java index 8ee5ef6badc3..74d970464108 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,13 +11,13 @@ * 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 + * limitations under the License. */ -package com.android.systemui.statusbar.hvac; +package com.android.systemui.car.hvac; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal; -import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal; +import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft; import android.annotation.NonNull; import android.view.animation.AccelerateDecelerateInterpolator; diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java index ad4fcd9b67da..521a665da5f6 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.navigationbar.car.hvac; +package com.android.systemui.car.hvac; import android.content.Context; import android.content.res.TypedArray; diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java index 963f3184c40d..6b903fad505c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.navigationbar.car.hvac; +package com.android.systemui.car.hvac; /** * Interface for Views that display temperature HVAC properties diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java index 60ee19eec25f..4fde30987e50 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java @@ -205,6 +205,9 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void onCancelClicked() { + getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false); + getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false); + mBouncer.hide(/* destroyView= */ true); mKeyguardCancelClickedListener.onCancelClicked(); } @@ -226,7 +229,8 @@ public class CarKeyguardViewController extends OverlayViewController implements @Override public void setNeedsInput(boolean needsInput) { - getLayout().setFocusable(needsInput); + getOverlayViewGlobalStateController().setWindowFocusable(needsInput); + getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput); } /** diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java index 53e5d9fe91ec..20fcca0d0220 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java @@ -30,7 +30,6 @@ import com.android.car.notification.R; import com.android.car.notification.headsup.CarHeadsUpNotificationContainer; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.car.CarStatusBar; import javax.inject.Inject; import javax.inject.Singleton; @@ -43,7 +42,7 @@ import dagger.Lazy; @Singleton public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer { private final CarDeviceProvisionedController mCarDeviceProvisionedController; - private final Lazy<CarStatusBar> mCarStatusBarLazy; + private final Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy; private final ViewGroup mWindow; private final FrameLayout mHeadsUpContentFrame; @@ -55,10 +54,9 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica @Main Resources resources, CarDeviceProvisionedController deviceProvisionedController, WindowManager windowManager, - // TODO: Remove dependency on CarStatusBar - Lazy<CarStatusBar> carStatusBarLazy) { + Lazy<NotificationPanelViewController> notificationPanelViewControllerLazy) { mCarDeviceProvisionedController = deviceProvisionedController; - mCarStatusBarLazy = carStatusBarLazy; + mNotificationPanelViewControllerLazy = notificationPanelViewControllerLazy; boolean showOnBottom = resources.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom); @@ -87,7 +85,8 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica private void animateShow() { if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen - || !mCarStatusBarLazy.get().isPanelExpanded()) && isCurrentUserSetup()) { + || !mNotificationPanelViewControllerLazy.get().isPanelExpanded()) + && mCarDeviceProvisionedController.isCurrentUserFullySetup()) { mWindow.setVisibility(View.VISIBLE); } } @@ -114,9 +113,4 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica public boolean isVisible() { return mWindow.getVisibility() == View.VISIBLE; } - - private boolean isCurrentUserSetup() { - return mCarDeviceProvisionedController.isCurrentUserSetup() - && !mCarDeviceProvisionedController.isCurrentUserSetupInProgress(); - } } diff --git a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java index c0dbb5879d7d..f145b148eaf7 100644 --- a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.sideloaded.car; +package com.android.systemui.car.sideloaded; import android.annotation.NonNull; import android.app.ActivityManager; diff --git a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java index 2f79f960f951..c054d204af98 100644 --- a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.voicerecognition.car; +package com.android.systemui.car.voicerecognition; import android.bluetooth.BluetoothHeadsetClient; import android.content.BroadcastReceiver; diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java index 9fdfc0fff307..2c2aec21ea4f 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java @@ -52,7 +52,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.statusbar.AutoHideUiElement; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.BarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; @@ -80,7 +79,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks private final Handler mMainHandler; private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy; private final Lazy<NavigationBarController> mNavigationBarControllerLazy; - private final SuperStatusBarViewFactory mSuperStatusBarViewFactory; private final ButtonSelectionStateController mButtonSelectionStateController; private final PhoneStatusBarPolicy mIconPolicy; private final StatusBarIconController mIconController; @@ -127,7 +125,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks @Main Handler mainHandler, Lazy<KeyguardStateController> keyguardStateControllerLazy, Lazy<NavigationBarController> navigationBarControllerLazy, - SuperStatusBarViewFactory superStatusBarViewFactory, ButtonSelectionStateController buttonSelectionStateController, PhoneStatusBarPolicy iconPolicy, StatusBarIconController iconController @@ -143,7 +140,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks mMainHandler = mainHandler; mKeyguardStateControllerLazy = keyguardStateControllerLazy; mNavigationBarControllerLazy = navigationBarControllerLazy; - mSuperStatusBarViewFactory = superStatusBarViewFactory; mButtonSelectionStateController = buttonSelectionStateController; mIconPolicy = iconPolicy; mIconController = iconController; diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java index 37a82255929a..8f3ae1a25441 100644 --- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java +++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java @@ -24,7 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.systemui.R; -import com.android.systemui.navigationbar.car.hvac.HvacController; +import com.android.systemui.car.hvac.HvacController; import javax.inject.Inject; import javax.inject.Singleton; diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java index 402d742cb949..5fe03f17919b 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java @@ -123,6 +123,12 @@ public class OverlayViewGlobalStateController { mSystemUIOverlayWindowController.setWindowFocusable(focusable); } + /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the + * sysui overlay window */ + public void setWindowNeedsInput(boolean needsInput) { + mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput); + } + /** Returns {@code true} if the window is focusable. */ public boolean isWindowFocusable() { return mSystemUIOverlayWindowController.isWindowFocusable(); diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java index 04d69eabaeac..5df5d6e98f18 100644 --- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java +++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java @@ -132,6 +132,16 @@ public class SystemUIOverlayWindowController implements updateWindow(); } + /** Sets the window to enable IME. */ + public void setWindowNeedsInput(boolean needsInput) { + if (needsInput) { + mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } else { + mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + } + updateWindow(); + } + /** Returns {@code true} if the window is visible */ public boolean isWindowVisible() { return mVisible; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java index a71d1db3ee70..7996170ba7d6 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.navigationbar.car.hvac; +package com.android.systemui.car.hvac; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java index 05b8e6a54099..6ac72a681bfe 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java @@ -31,7 +31,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarDeviceProvisionedController; -import com.android.systemui.statusbar.car.CarStatusBar; import org.junit.Before; import org.junit.Test; @@ -48,7 +47,7 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { @Mock private CarDeviceProvisionedController mCarDeviceProvisionedController; @Mock - private CarStatusBar mCarStatusBar; + private NotificationPanelViewController mNotificationPanelViewController; @Mock private WindowManager mWindowManager; @@ -61,7 +60,7 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { public void setUp() { MockitoAnnotations.initMocks(this); - when(mCarStatusBar.isPanelExpanded()).thenReturn(false); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true); when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false); @@ -72,14 +71,14 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext, testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager, - () -> mCarStatusBar); + () -> mNotificationPanelViewController); testableResources.addOverride( R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true); mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext, testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager, - () -> mCarStatusBar); + () -> mNotificationPanelViewController); } @Test @@ -120,14 +119,14 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase { @Test public void testDisplayNotification_notificationPanelExpanded_isInvisible() { - when(mCarStatusBar.isPanelExpanded()).thenReturn(true); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); mDefaultController.displayNotification(mNotificationView); assertThat(mDefaultController.isVisible()).isFalse(); } @Test public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() { - when(mCarStatusBar.isPanelExpanded()).thenReturn(true); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); mOverrideEnabledController.displayNotification(mNotificationView); assertThat(mOverrideEnabledController.isVisible()).isTrue(); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java index aebb0e005019..80f3d1ee5dec 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.sideloaded.car; +package com.android.systemui.car.sideloaded; import static com.google.common.truth.Truth.assertThat; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java index 38b47d0aea5d..eca51e34995c 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.voicerecognition.car; +package com.android.systemui.car.voicerecognition; -import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE; -import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED; +import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE; +import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java index bbcd0d4eff81..28c69c776f17 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java @@ -31,7 +31,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.navigationbar.car.hvac.HvacController; +import com.android.systemui.car.hvac.HvacController; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.statusbar.phone.StatusBarIconController; diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java index 3ecb29f95092..6da34d4dddc0 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java @@ -38,7 +38,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.car.CarDeviceProvisionedController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NavigationBarController; -import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -75,8 +74,6 @@ public class CarNavigationBarTest extends SysuiTestCase { @Mock private NavigationBarController mNavigationBarController; @Mock - private SuperStatusBarViewFactory mSuperStatusBarViewFactory; - @Mock private ButtonSelectionStateController mButtonSelectionStateController; @Mock private PhoneStatusBarPolicy mIconPolicy; @@ -92,8 +89,7 @@ public class CarNavigationBarTest extends SysuiTestCase { mCarNavigationBarController, mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener, mHandler, () -> mKeyguardStateController, () -> mNavigationBarController, - mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy, - mIconController); + mButtonSelectionStateController, mIconPolicy, mIconController); } @Test diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp index 54986a43a870..be790106f42d 100644 --- a/packages/CtsShim/build/Android.bp +++ b/packages/CtsShim/build/Android.bp @@ -70,6 +70,7 @@ android_app { // v2/v3 signature. use_embedded_native_libs: true, apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v1", "com.android.apex.cts.shim.v2", "com.android.apex.cts.shim.v2_no_hashtree", @@ -120,6 +121,7 @@ android_app { }, manifest: "shim/AndroidManifestTargetPSdk.xml", apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p", ], } @@ -140,6 +142,7 @@ android_app { manifest: "shim/AndroidManifest.xml", apex_available: [ + "//apex_available:platform", "com.android.apex.cts.shim.v1", "com.android.apex.cts.shim.v2", "com.android.apex.cts.shim.v2_no_hashtree", diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java index 653c8addba98..deccde593315 100644 --- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java +++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java @@ -33,6 +33,7 @@ import android.util.Log; import android.util.Slog; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -395,8 +396,12 @@ public class EnableZenModeDialog { button1.setAlpha(button1.isEnabled() ? 1f : .5f); button2.setAlpha(button2.isEnabled() ? 1f : .5f); } else { - button1.setVisibility(View.GONE); - button2.setVisibility(View.GONE); + if (button1 != null) { + ((ViewGroup) row).removeView(button1); + } + if (button2 != null) { + ((ViewGroup) row).removeView(button2); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java index c713d7813a54..d7e76a14c768 100644 --- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java +++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java @@ -133,6 +133,35 @@ public class WifiStatusTracker { } } + /** + * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received. + * This replaces the dependency on the initial sticky broadcast. + */ + public void fetchInitialState() { + if (mWifiManager == null) { + return; + } + updateWifiState(); + final NetworkInfo networkInfo = + mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + connected = networkInfo != null && networkInfo.isConnected(); + mWifiInfo = null; + ssid = null; + if (connected) { + mWifiInfo = mWifiManager.getConnectionInfo(); + if (mWifiInfo != null) { + if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) { + ssid = mWifiInfo.getPasspointProviderFriendlyName(); + } else { + ssid = getValidSsid(mWifiInfo); + } + updateRssi(mWifiInfo.getRssi()); + maybeRequestNetworkScore(); + } + } + updateStatusLabel(); + } + public void handleBroadcast(Intent intent) { if (mWifiManager == null) { return; diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml new file mode 100644 index 000000000000..a793680a037d --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <ripple android:color="#99999999" /> + </item> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml new file mode 100644 index 000000000000..129282dae77f --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/rounded_bg_full" + android:elevation="@dimen/bubble_manage_menu_elevation" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/bubble_manage_menu_dismiss_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_remove_no_shadow" + android:tint="@color/global_actions_text"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" + android:text="@string/bubble_dismiss_text" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_manage_menu_dont_bubble_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_stop_bubble" + android:tint="@color/global_actions_text"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" + android:text="@string/bubbles_dont_bubble_conversation" /> + + </LinearLayout> + + <LinearLayout + android:id="@+id/bubble_manage_menu_settings_container" + android:background="@drawable/bubble_manage_menu_row" + android:layout_width="match_parent" + android:layout_height="48dp" + android:gravity="center_vertical" + android:paddingStart="16dp" + android:paddingEnd="16dp" + android:orientation="horizontal"> + + <ImageView + android:id="@+id/bubble_manage_menu_settings_icon" + android:layout_width="24dp" + android:layout_height="24dp" + android:src="@drawable/ic_remove_no_shadow"/> + + <TextView + android:id="@+id/bubble_manage_menu_settings_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml index ae57563cfb09..6da96d10c253 100644 --- a/packages/SystemUI/res/layout/controls_management.xml +++ b/packages/SystemUI/res/layout/controls_management.xml @@ -17,6 +17,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/controls_management_root" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 599ed1696ec9..179f8b8ea9f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1014,6 +1014,9 @@ <!-- Margins at the left and right of the power menu and home controls widgets. --> <dimen name="global_actions_side_margin">16dp</dimen> + <!-- Amount to shift the layout when exiting/entering for controls activities --> + <dimen name="global_actions_controls_y_translation">20dp</dimen> + <!-- The maximum offset in either direction that elements are moved horizontally to prevent burn-in on AOD. --> <dimen name="burn_in_prevention_offset_x">8dp</dimen> @@ -1197,6 +1200,7 @@ snap to the dismiss target. --> <dimen name="bubble_dismiss_target_padding_x">40dp</dimen> <dimen name="bubble_dismiss_target_padding_y">20dp</dimen> + <dimen name="bubble_manage_menu_elevation">4dp</dimen> <dimen name="dismiss_circle_size">52dp</dimen> <dimen name="dismiss_target_x_size">24dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cb20e7a15424..d639ed074240 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2629,6 +2629,8 @@ <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string> <!-- Button text for dismissing the bubble "manage" button tool tip [CHAR LIMIT=20]--> <string name="bubbles_user_education_got_it">Got it</string> + <!-- Label for the button that takes the user to the notification settings for the given app. --> + <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] --> <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 7e24f5dbbd50..4ed819e4925b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -663,8 +663,13 @@ <!-- Controls styles --> <style name="Theme.ControlsManagement" parent="@android:style/Theme.DeviceDefault.NoActionBar"> + <item name="android:windowActivityTransitions">true</item> + <item name="android:windowContentTransitions">false</item> <item name="android:windowIsTranslucent">false</item> - <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item> + <item name="android:windowBackground">@android:color/black</item> + <item name="android:colorBackground">@android:color/black</item> + <item name="android:windowAnimationStyle">@null</item> + <item name="android:statusBarColor">@*android:color/transparent</item> </style> <style name="TextAppearance.Control"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 367058fa58dd..a96ef91850df 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1301,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private FingerprintManager mFpm; private FaceManager mFaceManager; private boolean mFingerprintLockedOut; + private TelephonyManager mTelephonyManager; /** * When we receive a @@ -1728,10 +1729,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } updateAirplaneModeState(); - TelephonyManager telephony = + mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - if (telephony != null) { - telephony.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + if (mTelephonyManager != null) { + mTelephonyManager.listen(mPhoneStateListener, + LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE); + // Set initial sim states values. + for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) { + int state = mTelephonyManager.getSimState(slot); + int[] subIds = mSubscriptionManager.getSubscriptionIds(slot); + if (subIds != null) { + for (int subId : subIds) { + mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state) + .sendToTarget(); + } + } + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index 409ae3f3c7d6..c92174a0d8af 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -164,7 +164,9 @@ public class PasswordTextView extends View { currentDrawPosition = getPaddingLeft(); } } else { - currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2; + float maxRight = getWidth() - getPaddingRight() - totalDrawingWidth; + float center = getWidth() / 2f - totalDrawingWidth / 2f; + currentDrawPosition = center > 0 ? center : maxRight; } int length = mTextChars.size(); Rect bounds = getCharBounds(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java index 7262f8caac89..1f27ae238533 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java @@ -16,6 +16,8 @@ package com.android.systemui.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -282,8 +284,8 @@ public class SystemActions extends SystemUI { private void handleTakeScreenshot() { ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext); - screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null); } private void handleAccessibilityMenu() { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 71f2bc09b983..38bfffbb75d9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -90,6 +90,7 @@ class Bubble implements BubbleViewProvider { } private FlyoutMessage mFlyoutMessage; + private Drawable mBadgedAppIcon; private Bitmap mBadgedImage; private int mDotColor; private Path mDotPath; @@ -133,6 +134,10 @@ class Bubble implements BubbleViewProvider { return mBadgedImage; } + public Drawable getBadgedAppIcon() { + return mBadgedAppIcon; + } + @Override public int getDotColor() { return mDotColor; @@ -239,6 +244,7 @@ class Bubble implements BubbleViewProvider { mAppName = info.appName; mFlyoutMessage = info.flyoutMessage; + mBadgedAppIcon = info.badgedAppIcon; mBadgedImage = info.badgedBubbleImage; mDotColor = info.dotColor; mDotPath = info.dotPath; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c6883c89bd21..e488cf271fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } + + mStackView.setUnbubbleConversationCallback(notificationEntry -> + onUserChangedBubble(notificationEntry, false /* shouldBubble */)); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 3524696dbc79..bb2365559f74 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -43,7 +43,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; import android.os.RemoteException; -import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -55,14 +54,13 @@ import com.android.internal.policy.ScreenDecorationsUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.recents.TriangleShape; -import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.AlphaOptimizedButton; import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and settings icon. */ -public class BubbleExpandedView extends LinearLayout implements View.OnClickListener { +public class BubbleExpandedView extends LinearLayout { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES; private enum ActivityViewStatus { @@ -100,9 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList private int mPointerWidth; private int mPointerHeight; private ShapeDrawable mPointerDrawable; - private Rect mTempRect = new Rect(); - private int[] mTempLoc = new int[2]; - private int mExpandedViewTouchSlop; @Nullable private Bubble mBubble; @@ -224,7 +219,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height); mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); - mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop); } @Override @@ -239,7 +233,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); - mPointerDrawable = new ShapeDrawable(TriangleShape.create( mPointerWidth, mPointerHeight, true /* pointUp */)); mPointerView.setBackground(mPointerDrawable); @@ -248,7 +241,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList mSettingsIconHeight = getContext().getResources().getDimensionPixelSize( R.dimen.bubble_manage_button_height); mSettingsIcon = findViewById(R.id.settings_button); - mSettingsIcon.setOnClickListener(this); mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, true /* singleTaskInstance */); @@ -289,6 +281,19 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList return mBubble != null ? mBubble.getEntry() : null; } + void setManageClickListener(OnClickListener manageClickListener) { + findViewById(R.id.settings_button).setOnClickListener(manageClickListener); + } + + /** + * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which + * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful + * if a view has been added or removed from on top of the ActivityView, such as the manage menu. + */ + void updateObscuredTouchableRegion() { + mActivityView.onLocationChanged(); + } + void applyThemeAttrs() { final TypedArray ta = mContext.obtainStyledAttributes( new int[] { @@ -473,51 +478,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } /** - * Whether the provided x, y values (in raw coordinates) are in a touchable area of the - * expanded view. - * - * The touchable areas are the ActivityView (plus some slop around it) and the manage button. - */ - boolean intersectingTouchableContent(int rawX, int rawY) { - mTempRect.setEmpty(); - if (mActivityView != null) { - mTempLoc = mActivityView.getLocationOnScreen(); - mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop, - mTempLoc[1] - mExpandedViewTouchSlop, - mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop, - mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop); - } - if (mTempRect.contains(rawX, rawY)) { - return true; - } - mTempLoc = mSettingsIcon.getLocationOnScreen(); - mTempRect.set(mTempLoc[0], - mTempLoc[1], - mTempLoc[0] + mSettingsIcon.getWidth(), - mTempLoc[1] + mSettingsIcon.getHeight()); - if (mTempRect.contains(rawX, rawY)) { - return true; - } - return false; - } - - @Override - public void onClick(View view) { - if (mBubble == null) { - return; - } - int id = view.getId(); - if (id == R.id.settings_button) { - Intent intent = mBubble.getSettingsIntent(); - mStackView.collapseStack(() -> { - mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser()); - logBubbleClickEvent(mBubble, - SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); - }); - } - } - - /** * Update appearance of the expanded view being displayed. */ public void updateView() { @@ -547,10 +507,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList * Position of the manage button displayed in the expanded view. Used for placing user * education about the manage button. */ - public Rect getManageButtonLocationOnScreen() { - mTempLoc = mSettingsIcon.getLocationOnScreen(); - return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(), - mTempLoc[1] + mSettingsIcon.getHeight()); + public void getManageButtonBoundsOnScreen(Rect rect) { + mSettingsIcon.getBoundsOnScreen(rect); } /** @@ -611,26 +569,4 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } return INVALID_DISPLAY; } - - /** - * Logs bubble UI click event. - * - * @param bubble the bubble notification entry that user is interacting with. - * @param action the user interaction enum. - */ - private void logBubbleClickEvent(Bubble bubble, int action) { - StatusBarNotification notification = bubble.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - mStackView.getBubbleIndex(mStackView.getExpandedBubble()), - mStackView.getBubbleCount(), - action, - mStackView.getNormalizedXPosition(), - mStackView.getNormalizedYPosition(), - bubble.showInShade(), - bubble.isOngoing(), - false /* isAppForeground (unused) */); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 0aabdff96e1e..c9069316028c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,12 +33,14 @@ import android.animation.ValueAnimator; import android.annotation.SuppressLint; import android.app.Notification; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; +import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Point; import android.graphics.PointF; @@ -47,6 +49,7 @@ import android.graphics.RectF; import android.graphics.Region; import android.os.Bundle; import android.os.Vibrator; +import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.Choreographer; import android.view.DisplayCutout; @@ -55,6 +58,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; @@ -62,6 +66,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.MainThread; @@ -83,6 +88,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -97,6 +103,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; /** * Renders bubbles in a stack and handles animating expanded and collapsed states. @@ -224,7 +231,7 @@ public class BubbleStackView extends FrameLayout { private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; - private BubbleViewProvider mExpandedBubble; + @Nullable private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded; /** Whether the stack is currently on the left side of the screen, or animating there. */ @@ -244,6 +251,10 @@ public class BubbleStackView extends FrameLayout { } private BubbleController.BubbleExpandListener mExpandListener; + + /** Callback to run when we want to unbubble the given notification's conversation. */ + private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private SysUiState mSysUiState; private boolean mViewUpdatedRequested = false; @@ -255,9 +266,7 @@ public class BubbleStackView extends FrameLayout { private LayoutInflater mInflater; - // Used for determining view / touch intersection - int[] mTempLoc = new int[2]; - RectF mTempRect = new RectF(); + private Rect mTempRect = new Rect(); private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect()); @@ -471,6 +480,11 @@ public class BubbleStackView extends FrameLayout { return true; } + // If the manage menu is visible, just hide it. + if (mShowingManage) { + showManageMenu(false /* show */); + } + if (mBubbleData.isExpanded()) { maybeShowManageEducation(false /* show */); @@ -627,6 +641,13 @@ public class BubbleStackView extends FrameLayout { private BubbleManageEducationView mManageEducationView; private boolean mAnimatingManageEducationAway; + private ViewGroup mManageMenu; + private ImageView mManageSettingsIcon; + private TextView mManageSettingsText; + private boolean mShowingManage = false; + private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig( + SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); + @SuppressLint("ClickableViewAccessibility") public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer, @@ -689,6 +710,8 @@ public class BubbleStackView extends FrameLayout { mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); + setUpManageMenu(); + setUpFlyout(); mFlyoutTransitionSpring.setSpring(new SpringForce() .setStiffness(SpringForce.STIFFNESS_LOW) @@ -838,7 +861,9 @@ public class BubbleStackView extends FrameLayout { // ActivityViews, etc.) were touched. Collapse the stack if it's expanded. setOnTouchListener((view, ev) -> { if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (mBubbleData.isExpanded()) { + if (mShowingManage) { + showManageMenu(false /* show */); + } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); } } @@ -847,6 +872,66 @@ public class BubbleStackView extends FrameLayout { }); } + private void setUpManageMenu() { + if (mManageMenu != null) { + removeView(mManageMenu); + } + + mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate( + R.layout.bubble_manage_menu, this, false); + mManageMenu.setVisibility(View.INVISIBLE); + + PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig); + + final TypedArray ta = mContext.obtainStyledAttributes( + new int[] {android.R.attr.dialogCornerRadius}); + final int menuCornerRadius = ta.getDimensionPixelSize(0, 0); + ta.recycle(); + + mManageMenu.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius); + } + }); + mManageMenu.setClipToOutline(true); + + mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + dismissBubbleIfExists(mBubbleData.getSelectedBubble()); + }); + + mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + final Bubble bubble = mBubbleData.getSelectedBubble(); + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + mUnbubbleConversationCallback.accept(bubble.getEntry()); + } + }); + + mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( + view -> { + showManageMenu(false /* show */); + final Bubble bubble = mBubbleData.getSelectedBubble(); + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + final Intent intent = bubble.getSettingsIntent(); + collapseStack(() -> { + mContext.startActivityAsUser( + intent, bubble.getEntry().getSbn().getUser()); + logBubbleClickEvent( + bubble, + SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); + }); + } + }); + + mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon); + mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name); + addView(mManageMenu); + } + private void setUpUserEducation() { if (mUserEducationView != null) { removeView(mUserEducationView); @@ -934,6 +1019,7 @@ public class BubbleStackView extends FrameLayout { setUpFlyout(); setUpOverflow(); setUpUserEducation(); + setUpManageMenu(); } /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */ @@ -960,6 +1046,9 @@ public class BubbleStackView extends FrameLayout { Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation)); addOnLayoutChangeListener(mOrientationChangedListener); hideFlyoutImmediate(); + + mManageMenu.setVisibility(View.INVISIBLE); + mShowingManage = false; } @Override @@ -1100,6 +1189,12 @@ public class BubbleStackView extends FrameLayout { mExpandListener = listener; } + /** Sets the function to call to un-bubble the given conversation. */ + public void setUnbubbleConversationCallback( + Consumer<NotificationEntry> unbubbleConversationCallback) { + mUnbubbleConversationCallback = unbubbleConversationCallback; + } + /** * Whether the stack of bubbles is expanded or not. */ @@ -1361,15 +1456,14 @@ public class BubbleStackView extends FrameLayout { mManageEducationView.setAlpha(0); mManageEducationView.setVisibility(VISIBLE); mManageEducationView.post(() -> { - final Rect position = - mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen(); + mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); final int viewHeight = mManageEducationView.getManageViewHeight(); final int inset = getResources().getDimensionPixelSize( R.dimen.bubbles_manage_education_top_inset); mManageEducationView.bringToFront(); - mManageEducationView.setManageViewPosition(position.left, - position.top - viewHeight + inset); - mManageEducationView.setPointerPosition(position.centerX() - position.left); + mManageEducationView.setManageViewPosition(mTempRect.left, + mTempRect.top - viewHeight + inset); + mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left); mManageEducationView.animate() .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION) .setInterpolator(FAST_OUT_SLOW_IN).alpha(1); @@ -1443,6 +1537,9 @@ public class BubbleStackView extends FrameLayout { } private void animateCollapse() { + // Hide the menu if it's visible. + showManageMenu(false); + mIsExpanded = false; final BubbleViewProvider previouslySelected = mExpandedBubble; beforeExpandedViewAnimation(); @@ -1570,9 +1667,9 @@ public class BubbleStackView extends FrameLayout { */ @Override public void subtractObscuredTouchableRegion(Region touchableRegion, View view) { - // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch - // events from any location. - if (mNotificationShadeWindowController.getPanelExpanded()) { + // If the notification shade is expanded, or the manage menu is open, we shouldn't let the + // ActivityView steal any touch events from any location. + if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) { touchableRegion.setEmpty(); } } @@ -1658,17 +1755,20 @@ public class BubbleStackView extends FrameLayout { private void dismissMagnetizedObject() { if (mIsExpanded) { final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject(); - final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView); + dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView)); - if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) { - mBubbleData.notificationEntryRemoved( - draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); - } } else { mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE); } } + private void dismissBubbleIfExists(@Nullable Bubble bubble) { + if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) { + mBubbleData.notificationEntryRemoved( + bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE); + } + } + /** Prepares and starts the desaturate/darken animation on the bubble stack. */ private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) { mDesaturateAndDarkenTargetView = targetView; @@ -1912,6 +2012,63 @@ public class BubbleStackView extends FrameLayout { invalidate(); } + private void showManageMenu(boolean show) { + mShowingManage = show; + + // This should not happen, since the manage menu is only visible when there's an expanded + // bubble. If we end up in this state, just hide the menu immediately. + if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { + mManageMenu.setVisibility(View.INVISIBLE); + return; + } + + // If available, update the manage menu's settings option with the expanded bubble's app + // name and icon. + if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) { + final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey()); + mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon()); + mManageSettingsText.setText(getResources().getString( + R.string.bubbles_app_settings, bubble.getAppName())); + } + + mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect); + + // When the menu is open, it should be at these coordinates. This will make the menu's + // bottom left corner match up with the button's bottom left corner. + final float targetX = mTempRect.left; + final float targetY = mTempRect.bottom - mManageMenu.getHeight(); + + if (show) { + mManageMenu.setScaleX(0.5f); + mManageMenu.setScaleY(0.5f); + mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4); + mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4); + mManageMenu.setAlpha(0f); + + PhysicsAnimator.getInstance(mManageMenu) + .spring(DynamicAnimation.ALPHA, 1f) + .spring(DynamicAnimation.SCALE_X, 1f) + .spring(DynamicAnimation.SCALE_Y, 1f) + .spring(DynamicAnimation.TRANSLATION_X, targetX) + .spring(DynamicAnimation.TRANSLATION_Y, targetY) + .start(); + + mManageMenu.setVisibility(View.VISIBLE); + } else { + PhysicsAnimator.getInstance(mManageMenu) + .spring(DynamicAnimation.ALPHA, 0f) + .spring(DynamicAnimation.SCALE_X, 0.5f) + .spring(DynamicAnimation.SCALE_Y, 0.5f) + .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4) + .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4) + .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE)) + .start(); + } + + // Update the AV's obscured touchable region for the new menu visibility state. + mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); + } + private void updateExpandedBubble() { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "updateExpandedBubble()"); @@ -1921,6 +2078,7 @@ public class BubbleStackView extends FrameLayout { && mExpandedBubble.getExpandedView() != null) { BubbleExpandedView bev = mExpandedBubble.getExpandedView(); mExpandedViewContainer.addView(bev); + bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); bev.populateExpandedView(); mExpandedViewContainer.setVisibility(VISIBLE); mExpandedViewContainer.setAlpha(1.0f); @@ -2089,4 +2247,26 @@ public class BubbleStackView extends FrameLayout { } return bubbles; } + + /** + * Logs bubble UI click event. + * + * @param bubble the bubble notification entry that user is interacting with. + * @param action the user interaction enum. + */ + private void logBubbleClickEvent(Bubble bubble, int action) { + StatusBarNotification notification = bubble.getEntry().getSbn(); + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + notification.getPackageName(), + notification.getNotification().getChannelId(), + notification.getId(), + getBubbleIndex(getExpandedBubble()), + getBubbleCount(), + action, + getNormalizedXPosition(), + getNormalizedYPosition(), + bubble.showInShade(), + bubble.isOngoing(), + false /* isAppForeground (unused) */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index c96f9a470ca4..8a57a735f6cb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -116,6 +116,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask ShortcutInfo shortcutInfo; String appName; Bitmap badgedBubbleImage; + Drawable badgedAppIcon; int dotColor; Path dotPath; Bubble.FlyoutMessage flyoutMessage; @@ -176,6 +177,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon); + info.badgedAppIcon = badgedIcon; info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable, badgeBitmapInfo).icon; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java index ef84c73b3145..ca3e2e27fa9a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java @@ -20,15 +20,17 @@ import android.graphics.Bitmap; import android.graphics.Path; import android.view.View; +import androidx.annotation.Nullable; + /** * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow. */ interface BubbleViewProvider { - BubbleExpandedView getExpandedView(); + @Nullable BubbleExpandedView getExpandedView(); void setContentVisibility(boolean visible); - View getIconView(); + @Nullable View getIconView(); void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index); diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index 7e8fec716b1f..e84f439c1fe2 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.util.concurrency.DelayableExecutor import dagger.Lazy +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject import javax.inject.Singleton @@ -284,13 +285,19 @@ open class ControlsBindingControllerImpl @Inject constructor( val requestLimit: Long ) : IControlsSubscriber.Stub() { val loadedControls = ArrayList<Control>() - private var isTerminated = false + private var isTerminated = AtomicBoolean(false) private var _loadCancelInternal: (() -> Unit)? = null private lateinit var subscription: IControlsSubscription + /** + * Potentially cancel a subscriber. The subscriber may also have terminated, in which case + * the request is ignored. + */ fun loadCancel() = Runnable { - Log.d(TAG, "Cancel load requested") - _loadCancelInternal?.invoke() + _loadCancelInternal?.let { + Log.d(TAG, "Canceling loadSubscribtion") + it.invoke() + } } override fun onSubscribe(token: IBinder, subs: IControlsSubscription) { @@ -301,7 +308,7 @@ open class ControlsBindingControllerImpl @Inject constructor( override fun onNext(token: IBinder, c: Control) { backgroundExecutor.execute { - if (isTerminated) return@execute + if (isTerminated.get()) return@execute loadedControls.add(c) @@ -325,13 +332,15 @@ open class ControlsBindingControllerImpl @Inject constructor( } private fun maybeTerminateAndRun(postTerminateFn: Runnable) { - if (isTerminated) return + if (isTerminated.get()) return - isTerminated = true _loadCancelInternal = {} currentProvider?.cancelLoadTimeout() - backgroundExecutor.execute(postTerminateFn) + backgroundExecutor.execute { + isTerminated.compareAndSet(false, true) + postTerminateFn.run() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 79a5b0a1ce52..bc97c10756fd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -52,19 +52,17 @@ interface ControlsController : UserAwareController { * Load all available [Control] for a given service. * * @param componentName the [ComponentName] of the [ControlsProviderService] to load from - * @param dataCallback a callback in which to retrieve the result. + * @param dataCallback a callback in which to retrieve the result + * @param cancelWrapper a callback to receive a [Runnable] that can be run to cancel the + * request */ fun loadForComponent( componentName: ComponentName, - dataCallback: Consumer<LoadData> + dataCallback: Consumer<LoadData>, + cancelWrapper: Consumer<Runnable> ) /** - * Cancels a pending load call - */ - fun cancelLoad() - - /** * Request to subscribe for favorited controls per structure * * @param structureInfo structure to limit the subscription to diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index 5626a5de2e3c..8e88756b16fd 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -77,8 +77,6 @@ class ControlsControllerImpl @Inject constructor ( private var userChanging: Boolean = true - private var loadCanceller: Runnable? = null - private var seedingInProgress = false private val seedingCallbacks = mutableListOf<Consumer<Boolean>>() @@ -276,28 +274,29 @@ class ControlsControllerImpl @Inject constructor ( override fun loadForComponent( componentName: ComponentName, - dataCallback: Consumer<ControlsController.LoadData> + dataCallback: Consumer<ControlsController.LoadData>, + cancelWrapper: Consumer<Runnable> ) { if (!confirmAvailability()) { if (userChanging) { // Try again later, userChanging should not last forever. If so, we have bigger // problems. This will return a runnable that allows to cancel the delayed version, // it will not be able to cancel the load if - loadCanceller = executor.executeDelayed( - { loadForComponent(componentName, dataCallback) }, - USER_CHANGE_RETRY_DELAY, - TimeUnit.MILLISECONDS + executor.executeDelayed( + { loadForComponent(componentName, dataCallback, cancelWrapper) }, + USER_CHANGE_RETRY_DELAY, + TimeUnit.MILLISECONDS ) - } else { - dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true)) } - return + + dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true)) } - loadCanceller = bindingController.bindAndLoad( + + cancelWrapper.accept( + bindingController.bindAndLoad( componentName, object : ControlsBindingController.LoadCallback { override fun accept(controls: List<Control>) { - loadCanceller = null executor.execute { val favoritesForComponentKeys = Favorites .getControlsForComponent(componentName).map { it.controlId } @@ -333,7 +332,6 @@ class ControlsControllerImpl @Inject constructor ( } override fun error(message: String) { - loadCanceller = null executor.execute { val controls = Favorites.getStructuresForComponent(componentName) .flatMap { st -> @@ -348,6 +346,7 @@ class ControlsControllerImpl @Inject constructor ( } } } + ) ) } @@ -432,12 +431,6 @@ class ControlsControllerImpl @Inject constructor ( seedingCallbacks.clear() } - override fun cancelLoad() { - loadCanceller?.let { - executor.execute(it) - } - } - private fun createRemovedStatus( componentName: ComponentName, controlInfo: ControlInfo, diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt new file mode 100644 index 000000000000..4ca47d1d41ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.controls.management + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.annotation.IdRes +import android.content.Intent + +import android.transition.Transition +import android.transition.TransitionValues +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.view.Window + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.OnLifecycleEvent + +import com.android.systemui.Interpolators +import com.android.systemui.R + +import com.android.systemui.controls.ui.ControlsUiController + +object ControlsAnimations { + + private const val ALPHA_EXIT_DURATION = 167L + private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION + private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY + + private const val Y_TRANSLATION_EXIT_DURATION = 183L + private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY + private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION + private var translationY: Float = -1f + + /** + * Setup an activity to handle enter/exit animations. [view] should be the root of the content. + * Fade and translate together. + */ + fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver { + return object : LifecycleObserver { + var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false) + + init { + // Must flag the parent group to move it all together, and set the initial + // transitionAlpha to 0.0f. This property is reserved for fade animations. + view.setTransitionGroup(true) + view.transitionAlpha = 0.0f + + if (translationY == -1f) { + translationY = view.context.resources.getDimensionPixelSize( + R.dimen.global_actions_controls_y_translation).toFloat() + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + fun setup() { + with(window) { + allowEnterTransitionOverlap = true + enterTransition = enterWindowTransition(view.getId()) + exitTransition = exitWindowTransition(view.getId()) + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) + fun enterAnimation() { + if (showAnimation) { + ControlsAnimations.enterAnimation(view).start() + showAnimation = false + } + } + } + } + + fun enterAnimation(view: View): Animator { + Log.d(ControlsUiController.TAG, "Enter animation for $view") + + view.transitionAlpha = 0.0f + view.alpha = 1.0f + + view.translationY = translationY + + val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply { + interpolator = Interpolators.DECELERATE_QUINT + startDelay = ALPHA_ENTER_DELAY + duration = ALPHA_ENTER_DURATION + } + + val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply { + interpolator = Interpolators.DECELERATE_QUINT + startDelay = Y_TRANSLATION_ENTER_DURATION + duration = Y_TRANSLATION_ENTER_DURATION + } + + return AnimatorSet().apply { + playTogether(alphaAnimator, yAnimator) + } + } + + /** + * Properly handle animations originating from dialogs. Activity transitions require + * transitioning between two activities, so expose this method for dialogs to animate + * on exit. + */ + @JvmStatic + fun exitAnimation(view: View, onEnd: Runnable? = null): Animator { + Log.d(ControlsUiController.TAG, "Exit animation for $view") + + val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply { + interpolator = Interpolators.ACCELERATE + duration = ALPHA_EXIT_DURATION + } + + view.translationY = 0.0f + val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply { + interpolator = Interpolators.ACCELERATE + duration = Y_TRANSLATION_EXIT_DURATION + } + + return AnimatorSet().apply { + playTogether(alphaAnimator, yAnimator) + onEnd?.let { + addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + it.run() + } + }) + } + } + } + + fun enterWindowTransition(@IdRes id: Int) = + WindowTransition({ view: View -> enterAnimation(view) }).apply { + addTarget(id) + } + + fun exitWindowTransition(@IdRes id: Int) = + WindowTransition({ view: View -> exitAnimation(view) }).apply { + addTarget(id) + } +} + +/** + * In order to animate, at least one property must be marked on each view that should move. + * Setting "item" is just a flag to indicate that it should move by the animator. + */ +class WindowTransition( + val animator: (view: View) -> Animator +) : Transition() { + override fun captureStartValues(tv: TransitionValues) { + tv.values["item"] = 0.0f + } + + override fun captureEndValues(tv: TransitionValues) { + tv.values["item"] = 1.0f + } + + override fun createAnimator( + sceneRoot: ViewGroup, + startValues: TransitionValues?, + endValues: TransitionValues? + ): Animator? = animator(startValues!!.view) +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt index ee1ce7ab3d83..640c90d2ba59 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt @@ -16,11 +16,12 @@ package com.android.systemui.controls.management -import android.app.Activity +import android.app.ActivityOptions import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.View +import android.view.ViewGroup import android.view.ViewStub import android.widget.Button import android.widget.TextView @@ -31,7 +32,9 @@ import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo +import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.settings.CurrentUserTracker +import com.android.systemui.util.LifecycleActivity import javax.inject.Inject /** @@ -39,8 +42,9 @@ import javax.inject.Inject */ class ControlsEditingActivity @Inject constructor( private val controller: ControlsControllerImpl, - broadcastDispatcher: BroadcastDispatcher -) : Activity() { + broadcastDispatcher: BroadcastDispatcher, + private val globalActionsComponent: GlobalActionsComponent +) : LifecycleActivity() { companion object { private const val TAG = "ControlsEditingActivity" @@ -84,14 +88,31 @@ class ControlsEditingActivity @Inject constructor( bindViews() bindButtons() + } + override fun onStart() { + super.onStart() setUpList() currentUserTracker.startTracking() } + override fun onStop() { + super.onStop() + currentUserTracker.stopTracking() + } + private fun bindViews() { setContentView(R.layout.controls_management) + + getLifecycle().addObserver( + ControlsAnimations.observerForAnimations( + requireViewById<ViewGroup>(R.id.controls_management_root), + window, + intent + ) + ) + requireViewById<ViewStub>(R.id.stub).apply { layoutResource = R.layout.controls_management_editing inflate() @@ -113,17 +134,26 @@ class ControlsEditingActivity @Inject constructor( putExtras(this@ControlsEditingActivity.intent) putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true) } - startActivity(intent) - finish() + startActivity(intent, ActivityOptions + .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()) } } + val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) saveButton = requireViewById<Button>(R.id.done).apply { isEnabled = false setText(R.string.save) setOnClickListener { saveFavorites() - finishAffinity() + ControlsAnimations.exitAnimation( + rootView, + object : Runnable { + override fun run() { + finish() + } + } + ).start() + globalActionsComponent.handleShowGlobalActionsMenu() } } } @@ -151,26 +181,38 @@ class ControlsEditingActivity @Inject constructor( val controls = controller.getFavoritesForStructure(component, structure) model = FavoritesModel(component, controls, favoritesModelCallback) val elevation = resources.getFloat(R.dimen.control_card_elevation) - val adapter = ControlAdapter(elevation) - val recycler = requireViewById<RecyclerView>(R.id.list) + val recyclerView = requireViewById<RecyclerView>(R.id.list) + recyclerView.alpha = 0.0f + val adapter = ControlAdapter(elevation).apply { + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + var hasAnimated = false + override fun onChanged() { + if (!hasAnimated) { + hasAnimated = true + ControlsAnimations.enterAnimation(recyclerView).start() + } + } + }) + } + val margin = resources .getDimensionPixelSize(R.dimen.controls_card_margin) val itemDecorator = MarginItemDecorator(margin, margin) - recycler.apply { + recyclerView.apply { this.adapter = adapter - layoutManager = GridLayoutManager(recycler.context, 2).apply { + layoutManager = GridLayoutManager(recyclerView.context, 2).apply { spanSizeLookup = adapter.spanSizeLookup } addItemDecoration(itemDecorator) } adapter.changeModel(model) model.attachAdapter(adapter) - ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler) + ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView) } override fun onDestroy() { currentUserTracker.stopTracking() super.onDestroy() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt index 6f34deeb8547..183bd7bb2b7a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt @@ -16,7 +16,7 @@ package com.android.systemui.controls.management -import android.app.Activity +import android.app.ActivityOptions import android.content.ComponentName import android.content.Intent import android.content.res.Configuration @@ -40,7 +40,9 @@ import com.android.systemui.controls.TooltipManager import com.android.systemui.controls.controller.ControlsControllerImpl import com.android.systemui.controls.controller.StructureInfo import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.globalactions.GlobalActionsComponent import com.android.systemui.settings.CurrentUserTracker +import com.android.systemui.util.LifecycleActivity import java.text.Collator import java.util.concurrent.Executor import java.util.function.Consumer @@ -50,8 +52,9 @@ class ControlsFavoritingActivity @Inject constructor( @Main private val executor: Executor, private val controller: ControlsControllerImpl, private val listingController: ControlsListingController, - broadcastDispatcher: BroadcastDispatcher -) : Activity() { + broadcastDispatcher: BroadcastDispatcher, + private val globalActionsComponent: GlobalActionsComponent +) : LifecycleActivity() { companion object { private const val TAG = "ControlsFavoritingActivity" @@ -81,6 +84,8 @@ class ControlsFavoritingActivity @Inject constructor( private var listOfStructures = emptyList<StructureContainer>() private lateinit var comparator: Comparator<StructureContainer> + private var cancelLoadRunnable: Runnable? = null + private var isPagerLoaded = false private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { private val startingUser = controller.currentUserId @@ -115,6 +120,7 @@ class ControlsFavoritingActivity @Inject constructor( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val collator = Collator.getInstance(resources.configuration.locales[0]) comparator = compareBy(collator) { it.structureName } appName = intent.getCharSequenceExtra(EXTRA_APP) @@ -122,14 +128,6 @@ class ControlsFavoritingActivity @Inject constructor( component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) bindViews() - - setUpPager() - - loadControls() - - listingController.addCallback(listingCallback) - - currentUserTracker.startTracking() } private val controlsModelCallback = object : ControlsModel.ControlsModelCallback { @@ -174,12 +172,17 @@ class ControlsFavoritingActivity @Inject constructor( pageIndicator.setLocation(0f) pageIndicator.visibility = if (listOfStructures.size > 1) View.VISIBLE else View.GONE + + ControlsAnimations.enterAnimation(pageIndicator).start() + ControlsAnimations.enterAnimation(structurePager).start() } - }) + }, Consumer { runnable -> cancelLoadRunnable = runnable }) } } private fun setUpPager() { + structurePager.alpha = 0.0f + pageIndicator.alpha = 0.0f structurePager.apply { adapter = StructureAdapter(emptyList()) registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { @@ -203,6 +206,15 @@ class ControlsFavoritingActivity @Inject constructor( private fun bindViews() { setContentView(R.layout.controls_management) + + getLifecycle().addObserver( + ControlsAnimations.observerForAnimations( + requireViewById<ViewGroup>(R.id.controls_management_root), + window, + intent + ) + ) + requireViewById<ViewStub>(R.id.stub).apply { layoutResource = R.layout.controls_management_favorites inflate() @@ -278,10 +290,12 @@ class ControlsFavoritingActivity @Inject constructor( val i = Intent() i.setComponent( ComponentName(context, ControlsProviderSelectorActivity::class.java)) - context.startActivity(i) + startActivity(i, ActivityOptions + .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()) } } + val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) doneButton = requireViewById<Button>(R.id.done).apply { isEnabled = false setOnClickListener { @@ -292,7 +306,16 @@ class ControlsFavoritingActivity @Inject constructor( StructureInfo(component!!, it.structureName, favoritesForStorage) ) } - finishAffinity() + + ControlsAnimations.exitAnimation( + rootView, + object : Runnable { + override fun run() { + finish() + } + } + ).start() + globalActionsComponent.handleShowGlobalActionsMenu() } } } @@ -302,15 +325,39 @@ class ControlsFavoritingActivity @Inject constructor( mTooltipManager?.hide(false) } + override fun onStart() { + super.onStart() + + listingController.addCallback(listingCallback) + currentUserTracker.startTracking() + } + + override fun onResume() { + super.onResume() + + // only do once, to make sure that any user changes do not get replaces if resume is called + // more than once + if (!isPagerLoaded) { + setUpPager() + loadControls() + isPagerLoaded = true + } + } + + override fun onStop() { + super.onStop() + + listingController.removeCallback(listingCallback) + currentUserTracker.stopTracking() + } + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) mTooltipManager?.hide(false) } override fun onDestroy() { - currentUserTracker.stopTracking() - listingController.removeCallback(listingCallback) - controller.cancelLoad() + cancelLoadRunnable?.run() super.onDestroy() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt index 3be59009f531..80cb96803f24 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt @@ -16,15 +16,18 @@ package com.android.systemui.controls.management +import android.app.ActivityOptions import android.content.ComponentName import android.content.Intent import android.os.Bundle import android.view.LayoutInflater +import android.view.ViewGroup import android.view.ViewStub import android.widget.Button import android.widget.TextView import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import com.android.systemui.R import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.controls.controller.ControlsController @@ -66,12 +69,37 @@ class ControlsProviderSelectorActivity @Inject constructor( super.onCreate(savedInstanceState) setContentView(R.layout.controls_management) + + getLifecycle().addObserver( + ControlsAnimations.observerForAnimations( + requireViewById<ViewGroup>(R.id.controls_management_root), + window, + intent + ) + ) + requireViewById<ViewStub>(R.id.stub).apply { layoutResource = R.layout.controls_management_apps inflate() } recyclerView = requireViewById(R.id.list) + recyclerView.layoutManager = LinearLayoutManager(applicationContext) + + requireViewById<TextView>(R.id.title).apply { + text = resources.getText(R.string.controls_providers_title) + } + + requireViewById<Button>(R.id.done).setOnClickListener { + this@ControlsProviderSelectorActivity.finishAffinity() + } + } + + override fun onStart() { + super.onStart() + currentUserTracker.startTracking() + + recyclerView.alpha = 0.0f recyclerView.adapter = AppAdapter( backExecutor, executor, @@ -80,17 +108,22 @@ class ControlsProviderSelectorActivity @Inject constructor( LayoutInflater.from(this), ::launchFavoritingActivity, FavoritesRenderer(resources, controlsController::countFavoritesForComponent), - resources) - recyclerView.layoutManager = LinearLayoutManager(applicationContext) - - requireViewById<TextView>(R.id.title).text = - resources.getText(R.string.controls_providers_title) - - requireViewById<Button>(R.id.done).setOnClickListener { - this@ControlsProviderSelectorActivity.finishAffinity() + resources).apply { + registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + var hasAnimated = false + override fun onChanged() { + if (!hasAnimated) { + hasAnimated = true + ControlsAnimations.enterAnimation(recyclerView).start() + } + } + }) } + } - currentUserTracker.startTracking() + override fun onStop() { + super.onStop() + currentUserTracker.stopTracking() } /** @@ -98,7 +131,7 @@ class ControlsProviderSelectorActivity @Inject constructor( * @param component a component name for a [ControlsProviderService] */ fun launchFavoritingActivity(component: ComponentName?) { - backExecutor.execute { + executor.execute { component?.let { val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) .apply { @@ -107,7 +140,7 @@ class ControlsProviderSelectorActivity @Inject constructor( putExtra(Intent.EXTRA_COMPONENT_NAME, it) flags = Intent.FLAG_ACTIVITY_SINGLE_TOP } - startActivity(intent) + startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 61a1a986c091..aed7cd316bc7 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -26,9 +26,10 @@ interface ControlsUiController { companion object { public const val TAG = "ControlsUiController" + public const val EXTRA_ANIMATE = "extra_animate" } - fun show(parent: ViewGroup) + fun show(parent: ViewGroup, dismissGlobalActions: Runnable) fun hide() fun onRefreshState(componentName: ComponentName, controls: List<Control>) fun onActionResponse( diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 2adfb1bd6d4f..cfd8df059567 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -85,7 +85,7 @@ class ControlsUiControllerImpl @Inject constructor ( private const val PREF_STRUCTURE = "controls_structure" private const val USE_PANELS = "systemui.controls_use_panel" - private const val FADE_IN_MILLIS = 225L + private const val FADE_IN_MILLIS = 200L private val EMPTY_COMPONENT = ComponentName("", "") private val EMPTY_STRUCTURE = StructureInfo( @@ -104,6 +104,7 @@ class ControlsUiControllerImpl @Inject constructor ( private var popup: ListPopupWindow? = null private var activeDialog: Dialog? = null private var hidden = true + private lateinit var dismissGlobalActions: Runnable override val available: Boolean get() = controlsController.get().available @@ -134,9 +135,10 @@ class ControlsUiControllerImpl @Inject constructor ( } } - override fun show(parent: ViewGroup) { + override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) { Log.d(ControlsUiController.TAG, "show()") this.parent = parent + this.dismissGlobalActions = dismissGlobalActions hidden = false allStructures = controlsController.get().getFavorites() @@ -169,7 +171,7 @@ class ControlsUiControllerImpl @Inject constructor ( fadeAnim.setDuration(FADE_IN_MILLIS) fadeAnim.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { - show(parent) + show(parent, dismissGlobalActions) val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) showAnim.setInterpolator(DecelerateInterpolator(1.0f)) showAnim.setDuration(FADE_IN_MILLIS) @@ -256,9 +258,10 @@ class ControlsUiControllerImpl @Inject constructor ( } private fun startActivity(context: Context, intent: Intent) { - val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS) - context.sendBroadcast(closeDialog) + // Force animations when transitioning from a dialog to an activity + intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) context.startActivity(intent) + dismissGlobalActions.run() } private fun showControlsView(items: List<SelectionItem>) { diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 322660521ee0..a24fede36e4c 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -16,6 +16,8 @@ package com.android.systemui.globalactions; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; @@ -110,6 +112,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.controller.ControlsController; +import com.android.systemui.controls.management.ControlsAnimations; import com.android.systemui.controls.management.ControlsListingController; import com.android.systemui.controls.ui.ControlsUiController; import com.android.systemui.dagger.qualifiers.Background; @@ -126,7 +129,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.EmergencyDialerConstants; import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; -import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator; import java.util.ArrayList; import java.util.List; @@ -479,7 +481,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, */ @VisibleForTesting protected int getMaxShownPowerItems() { - if (shouldUseControlsLayout()) { + // TODO: Overflow disabled on keyguard while we solve for touch blocking issues. + if (shouldUseControlsLayout() && !mKeyguardShowing) { return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns); } else { return Integer.MAX_VALUE; @@ -829,7 +832,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mHandler.postDelayed(new Runnable() { @Override public void run() { - mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null); + mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true, + SCREENSHOT_GLOBAL_ACTIONS, mHandler, null); mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU); mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS); } @@ -1815,7 +1819,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, case MESSAGE_DISMISS: if (mDialog != null) { if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - mDialog.dismissImmediately(); + mDialog.completeDismiss(); } else { mDialog.dismiss(); } @@ -1894,6 +1898,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private ControlsUiController mControlsUiController; private ViewGroup mControlsView; + private ViewGroup mContainer; ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter, GlobalActionsPanelPlugin.PanelViewController plugin, @@ -2046,6 +2051,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); mGlobalActionsLayout.setRotationListener(this::onRotate); mGlobalActionsLayout.setAdapter(mAdapter); + mContainer = findViewById(com.android.systemui.R.id.global_actions_container); + // Some legacy dialog layouts don't have the outer container + if (mContainer == null) { + mContainer = mGlobalActionsLayout; + } mOverflowPopup = createPowerOverflowPopup(); @@ -2172,10 +2182,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi(); mNotificationShadeWindowController.setForceHasTopUi(true); mBackgroundDrawable.setAlpha(0); - mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX()); - mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY()); - mGlobalActionsLayout.setAlpha(0); - mGlobalActionsLayout.animate() + mContainer.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX()); + mContainer.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY()); + mContainer.setAlpha(0); + mContainer.animate() .alpha(1) .translationX(0) .translationY(0) @@ -2200,50 +2210,55 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return WindowInsets.CONSUMED; }); if (mControlsUiController != null) { - mControlsUiController.show(mControlsView); + mControlsUiController.show(mControlsView, this::dismissForControlsActivity); } } @Override public void dismiss() { + dismissWithAnimation(() -> { + mContainer.setTranslationX(0); + mContainer.setTranslationY(0); + mContainer.setAlpha(1); + mContainer.animate() + .alpha(0) + .translationX(mGlobalActionsLayout.getAnimationOffsetX()) + .translationY(mGlobalActionsLayout.getAnimationOffsetY()) + .setDuration(450) + .withEndAction(this::completeDismiss) + .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) + .setUpdateListener(animation -> { + float animatedValue = 1f - animation.getAnimatedFraction(); + int alpha = (int) (animatedValue * mScrimAlpha * 255); + mBackgroundDrawable.setAlpha(alpha); + mDepthController.updateGlobalDialogVisibility(animatedValue, + mGlobalActionsLayout); + }) + .start(); + }); + } + + private void dismissForControlsActivity() { + dismissWithAnimation(() -> { + ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent(); + ControlsAnimations.exitAnimation(root, this::completeDismiss).start(); + }); + } + + void dismissWithAnimation(Runnable animation) { if (!mShowing) { return; } mShowing = false; - if (mControlsUiController != null) mControlsUiController.hide(); - mGlobalActionsLayout.setTranslationX(0); - mGlobalActionsLayout.setTranslationY(0); - mGlobalActionsLayout.setAlpha(1); - mGlobalActionsLayout.animate() - .alpha(0) - .translationX(mGlobalActionsLayout.getAnimationOffsetX()) - .translationY(mGlobalActionsLayout.getAnimationOffsetY()) - .setDuration(550) - .withEndAction(this::completeDismiss) - .setInterpolator(new LogAccelerateInterpolator()) - .setUpdateListener(animation -> { - float animatedValue = 1f - animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - mDepthController.updateGlobalDialogVisibility(animatedValue, - mGlobalActionsLayout); - }) - .start(); - dismissPanel(); - dismissOverflow(); - resetOrientation(); + animation.run(); } - void dismissImmediately() { + private void completeDismiss() { mShowing = false; - if (mControlsUiController != null) mControlsUiController.hide(); + resetOrientation(); dismissPanel(); dismissOverflow(); - resetOrientation(); - completeDismiss(); - } - - private void completeDismiss() { + if (mControlsUiController != null) mControlsUiController.hide(); mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi); mDepthController.updateGlobalDialogVisibility(0, null /* view */); super.dismiss(); @@ -2304,7 +2319,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, initializeLayout(); mGlobalActionsLayout.updateList(); if (mControlsUiController != null) { - mControlsUiController.show(mControlsView); + mControlsUiController.show(mControlsView, this::dismissForControlsActivity); } } @@ -2343,10 +2358,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, && mControlsUiController.getAvailable() && !mControlsServiceInfos.isEmpty(); } - // TODO: Remove legacy layout XML and classes. protected boolean shouldUseControlsLayout() { // always use new controls layout return true; } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index 14e3e9390825..123cf78d74f8 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -61,6 +61,18 @@ public class LogModule { return buffer; } + /** Provides a logging buffer for all logs related to the data layer of notifications. */ + @Provides + @Singleton + @NotifInteractionLog + public static LogBuffer provideNotifInteractionLogBuffer( + LogcatEchoTracker echoTracker, + DumpManager dumpManager) { + LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker); + buffer.attach(dumpManager); + return buffer; + } + /** Provides a logging buffer for all logs related to Quick Settings. */ @Provides @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java new file mode 100644 index 000000000000..20fc6ff445a6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.log.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import com.android.systemui.log.LogBuffer; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Qualifier; + +/** + * A {@link LogBuffer} for messages related to the user interacting with notifications (e.g. + * clicking on them). + */ +@Qualifier +@Documented +@Retention(RUNTIME) +public @interface NotifInteractionLog { +} diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index f25de6a553e8..233d24b17d44 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -26,14 +26,18 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.ColorStateList; import android.graphics.Bitmap; +import android.graphics.ImageDecoder; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.Icon; import android.graphics.drawable.RippleDrawable; import android.media.MediaDescription; import android.media.MediaMetadata; +import android.media.ThumbnailUtils; import android.media.session.MediaController; import android.media.session.MediaSession; import android.media.session.PlaybackState; +import android.net.Uri; import android.service.media.MediaBrowserService; import android.util.Log; import android.view.LayoutInflater; @@ -59,6 +63,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.qs.QSMediaBrowser; import com.android.systemui.util.Assert; +import java.io.IOException; import java.util.List; import java.util.concurrent.Executor; @@ -99,6 +104,13 @@ public class MediaControlPanel { com.android.internal.R.id.action4 }; + // URI fields to try loading album art from + private static final String[] ART_URIS = { + MediaMetadata.METADATA_KEY_ALBUM_ART_URI, + MediaMetadata.METADATA_KEY_ART_URI, + MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI + }; + private final MediaController.Callback mSessionCallback = new MediaController.Callback() { @Override public void onSessionDestroyed() { @@ -205,14 +217,16 @@ public class MediaControlPanel { * Update the media panel view for the given media session * @param token * @param iconDrawable + * @param largeIcon * @param iconColor * @param bgColor * @param contentIntent * @param appNameString * @param key */ - public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, int iconColor, - int bgColor, PendingIntent contentIntent, String appNameString, String key) { + public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon, + int iconColor, int bgColor, PendingIntent contentIntent, String appNameString, + String key) { // Ensure that component names are updated if token has changed if (mToken == null || !mToken.equals(token)) { mToken = token; @@ -303,7 +317,7 @@ public class MediaControlPanel { ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); if (albumView != null) { // Resize art in a background thread - mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView)); + mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView)); } // Song name @@ -396,30 +410,82 @@ public class MediaControlPanel { * @param albumView view to hold the album art */ protected void processAlbumArt(MediaDescription description, ImageView albumView) { - Bitmap albumArt = description.getIconBitmap(); - //TODO check other fields (b/151054111, b/152067055) + Bitmap albumArt = null; + + // First try loading from URI + albumArt = loadBitmapFromUri(description.getIconUri()); + + // Then check bitmap + if (albumArt == null) { + albumArt = description.getIconBitmap(); + } + processAlbumArtInternal(albumArt, albumView); } /** * Process album art for layout * @param metadata media metadata + * @param largeIcon from notification, checked as a fallback if metadata does not have art * @param albumView view to hold the album art */ - private void processAlbumArt(MediaMetadata metadata, ImageView albumView) { - Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - //TODO check other fields (b/151054111, b/152067055) + private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) { + Bitmap albumArt = null; + + // First look in URI fields + for (String field : ART_URIS) { + String uriString = metadata.getString(field); + if (uriString != null) { + albumArt = loadBitmapFromUri(Uri.parse(uriString)); + if (albumArt != null) { + Log.d(TAG, "loaded art from " + field); + break; + } + } + } + + // Then check bitmap field + if (albumArt == null) { + albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); + } + + // Finally try the notification's largeIcon + if (albumArt == null && largeIcon != null) { + albumArt = largeIcon.getBitmap(); + } + processAlbumArtInternal(albumArt, albumView); } - private void processAlbumArtInternal(Bitmap albumArt, ImageView albumView) { - float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); + /** + * Load a bitmap from a URI + * @param uri + * @return bitmap, or null if couldn't be loaded + */ + private Bitmap loadBitmapFromUri(Uri uri) { + ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri); + try { + return ImageDecoder.decodeBitmap(source); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Resize and crop the image if provided and update the control view + * @param albumArt Bitmap of art to display, or null to hide view + * @param albumView View that will hold the art + */ + private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) { + // Resize RoundedBitmapDrawable roundedDrawable = null; if (albumArt != null) { + float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius); Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true); int albumSize = (int) mContext.getResources().getDimension( R.dimen.qs_media_album_size); - Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false); + Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize); roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled); roundedDrawable.setCornerRadius(radius); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java index 0f065661a470..9e574a1fa621 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.media.MediaDescription; import android.media.session.MediaController; import android.media.session.MediaSession; @@ -115,8 +116,8 @@ public class QSMediaPlayer extends MediaControlPanel { } // Set what we can normally - super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName.toString(), - null); + super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent, + appName.toString(), null); // Then add info from MediaDescription ImageView albumView = mMediaNotifView.findViewById(R.id.album_art); @@ -149,6 +150,7 @@ public class QSMediaPlayer extends MediaControlPanel { * Update media panel view for the given media session * @param token token for this media session * @param icon app notification icon + * @param largeIcon notification's largeIcon, used as a fallback for album art * @param iconColor foreground color (for text, icons) * @param bgColor background color * @param actionsContainer a LinearLayout containing the media action buttons @@ -156,11 +158,12 @@ public class QSMediaPlayer extends MediaControlPanel { * @param appName Application title * @param key original notification's key */ - public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, - int bgColor, View actionsContainer, PendingIntent contentIntent, String appName, - String key) { + public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon, + int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent, + String appName, String key) { - super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName, key); + super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName, + key); // Media controls if (actionsContainer != null) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java index 1eb577852a71..1252008755a7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java @@ -32,6 +32,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.media.MediaDescription; import android.media.session.MediaSession; import android.metrics.LogMaker; @@ -225,14 +226,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne * Add or update a player for the associated media session * @param token * @param icon + * @param largeIcon * @param iconColor * @param bgColor * @param actionsContainer * @param notif * @param key */ - public void addMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor, - View actionsContainer, StatusBarNotification notif, String key) { + public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon, + int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif, + String key) { if (!useQsMediaPlayer(mContext)) { // Shouldn't happen, but just in case Log.e(TAG, "Tried to add media session without player!"); @@ -296,7 +299,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne Log.d(TAG, "setting player session"); String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification()) .loadHeaderAppName(); - player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer, + player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer, notif.getNotification().contentIntent, appName, key); if (mMediaPlayers.size() > 0) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java index 7ba7c5fe499e..89b36da0c834 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java @@ -19,6 +19,7 @@ package com.android.systemui.qs; import android.app.PendingIntent; import android.content.Context; import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.media.session.MediaController; import android.media.session.MediaSession; import android.view.View; @@ -58,6 +59,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel { * Update media panel view for the given media session * @param token token for this media session * @param icon app notification icon + * @param largeIcon notification's largeIcon, used as a fallback for album art * @param iconColor foreground color (for text, icons) * @param bgColor background color * @param actionsContainer a LinearLayout containing the media action buttons @@ -66,8 +68,9 @@ public class QuickQSMediaPlayer extends MediaControlPanel { * @param contentIntent Intent to send when user taps on the view * @param key original notification's key */ - public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor, - View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) { + public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon, + int iconColor, int bgColor, View actionsContainer, int[] actionsToShow, + PendingIntent contentIntent, String key) { // Only update if this is a different session and currently playing String oldPackage = ""; if (getController() != null) { @@ -82,7 +85,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel { return; } - super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key); + super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key); LinearLayout parentActionsLayout = (LinearLayout) actionsContainer; int i = 0; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 9cee7e7ccba4..1a6a104387ac 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles; import android.content.Intent; import android.service.quicksettings.Tile; +import android.text.TextUtils; import android.util.Log; +import android.widget.Switch; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSTile; @@ -88,6 +90,10 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord); state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start); } + state.contentDescription = TextUtils.isEmpty(state.secondaryLabel) + ? state.label + : TextUtils.concat(state.label, ", ", state.secondaryLabel); + state.expandedAccessibilityClassName = Switch.class.getName(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 66bc177da81d..fecb7b602012 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR; @@ -380,7 +381,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen, Insets visibleInsets, int taskId) { mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets, - taskId, mHandler, null); + taskId, SCREENSHOT_OVERVIEW, mHandler, null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index e1cc0b0c90c2..1efe663ca6ce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -76,6 +76,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; +import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ static class SaveImageInBackgroundData { public Bitmap image; - public Uri imageUri; public Consumer<Uri> finisher; public GlobalScreenshot.ActionsReadyListener mActionsReadyListener; public int errorMsgResId; @@ -112,13 +112,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset void clearImage() { image = null; - imageUri = null; + } + } + + /** + * Structure returned by the SaveImageInBackgroundTask + */ + static class SavedImageData { + public Uri uri; + public Notification.Action shareAction; + public Notification.Action editAction; + public Notification.Action deleteAction; + public List<Notification.Action> smartActions; + + /** + * Used to reset the return data on error + */ + public void reset() { + uri = null; + shareAction = null; + editAction = null; + deleteAction = null; + smartActions = null; } } abstract static class ActionsReadyListener { - abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions, - List<Notification.Action> actions); + abstract void onActionsReady(SavedImageData imageData); } // These strings are used for communicating the action invoked to @@ -147,6 +167,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private static final int MESSAGE_CORNER_TIMEOUT = 2; private final ScreenshotNotificationsController mNotificationsController; + private final UiEventLogger mUiEventLogger; private final Context mContext; private final WindowManager mWindowManager; @@ -185,6 +206,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_CORNER_TIMEOUT: + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); GlobalScreenshot.this.clearScreenshot("timeout"); break; default: @@ -199,9 +221,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Inject public GlobalScreenshot( Context context, @Main Resources resources, LayoutInflater layoutInflater, - ScreenshotNotificationsController screenshotNotificationsController) { + ScreenshotNotificationsController screenshotNotificationsController, + UiEventLogger uiEventLogger) { mContext = context; mNotificationsController = screenshotNotificationsController; + mUiEventLogger = uiEventLogger; // Inflate the screenshot layout mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); @@ -222,7 +246,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mBackgroundProtection = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_background); mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); - mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button")); + mDismissButton.setOnClickListener(view -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); + clearScreenshot("dismiss_button"); + }); mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash); mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector); @@ -445,12 +472,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> smartActions, - List<Notification.Action> actions) { - if (uri == null) { + void onActionsReady(SavedImageData imageData) { + finisher.accept(imageData.uri); + if (imageData.uri == null) { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED); mScreenshotHandler.post(() -> { if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) { mScreenshotAnimation.addListener( @@ -458,13 +487,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - createScreenshotActionsShadeAnimation( - smartActions, actions).start(); + createScreenshotActionsShadeAnimation(imageData) + .start(); } }); } else { - createScreenshotActionsShadeAnimation(smartActions, - actions).start(); + createScreenshotActionsShadeAnimation(imageData).start(); } }); } @@ -567,8 +595,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset return dropInAnimation; } - private ValueAnimator createScreenshotActionsShadeAnimation( - List<Notification.Action> smartActions, List<Notification.Action> actions) { + private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) { LayoutInflater inflater = LayoutInflater.from(mContext); mActionsView.removeAllViews(); mActionsContainer.setScrollX(0); @@ -583,45 +610,63 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } catch (RemoteException e) { } - for (Notification.Action smartAction : smartActions) { + for (Notification.Action smartAction : imageData.smartActions) { ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); actionChip.setText(smartAction.title); actionChip.setIcon(smartAction.getIcon(), false); actionChip.setPendingIntent(smartAction.actionIntent, - () -> clearScreenshot("chip tapped")); + () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); + clearScreenshot("chip tapped"); + }); mActionsView.addView(actionChip); } - for (Notification.Action action : actions) { - ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate( - R.layout.global_screenshot_action_chip, mActionsView, false); - actionChip.setText(action.title); - actionChip.setIcon(action.getIcon(), true); - actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped")); - if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) { - mScreenshotView.setOnClickListener(v -> { - try { - action.actionIntent.send(); - clearScreenshot("screenshot preview tapped"); - } catch (PendingIntent.CanceledException e) { - Log.e(TAG, "Intent cancelled", e); - } - }); - mScreenshotView.setContentDescription(action.title); + ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + shareChip.setText(imageData.shareAction.title); + shareChip.setIcon(imageData.shareAction.getIcon(), true); + shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(shareChip); + + ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate( + R.layout.global_screenshot_action_chip, mActionsView, false); + editChip.setText(imageData.editAction.title); + editChip.setIcon(imageData.editAction.getIcon(), true); + editChip.setPendingIntent(imageData.editAction.actionIntent, () -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED); + clearScreenshot("chip tapped"); + }); + mActionsView.addView(editChip); + + mScreenshotView.setOnClickListener(v -> { + try { + imageData.editAction.actionIntent.send(); + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); + clearScreenshot("screenshot preview tapped"); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Intent cancelled", e); } - mActionsView.addView(actionChip); - } + }); + mScreenshotView.setContentDescription(imageData.editAction.title); + if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate( R.layout.global_screenshot_action_chip, mActionsView, false); Toast scrollNotImplemented = Toast.makeText( mContext, "Not implemented", Toast.LENGTH_SHORT); - scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate + scrollChip.setText("Extend"); // TODO : add resource and translate scrollChip.setIcon( Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true); - scrollChip.setOnClickListener(v -> scrollNotImplemented.show()); + scrollChip.setOnClickListener(v -> { + mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED); + scrollNotImplemented.show(); + }); mActionsView.addView(scrollChip); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java index f3614ffbdb1b..095c32f4a2ce 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java @@ -24,7 +24,6 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.Nullable; -import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; @@ -53,7 +52,6 @@ import android.widget.Toast; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; -import java.util.List; import java.util.function.Consumer; import javax.inject.Inject; @@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy { // Save the screenshot once we have a bit of time now saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() { @Override - void onActionsReady(Uri uri, List<Notification.Action> smartActions, - List<Notification.Action> actions) { - if (uri == null) { + void onActionsReady(GlobalScreenshot.SavedImageData actionData) { + if (actionData.uri == null) { mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_capture_text); } else { mNotificationsController - .showScreenshotActionsNotification(uri, smartActions, actions); + .showScreenshotActionsNotification(actionData); } } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index c828c4cccce5..170174deaeb3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { private final Context mContext; private final GlobalScreenshot.SaveImageInBackgroundData mParams; + private final GlobalScreenshot.SavedImageData mImageData; private final String mImageFileName; private final long mImageTime; private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider; @@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) { mContext = context; + mImageData = new GlobalScreenshot.SavedImageData(); // Prepare all the output metadata mParams = data; @@ -145,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { values.put(MediaColumns.IS_PENDING, 1); final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); + try { // First, write the actual data for our screenshot try (OutputStream out = resolver.openOutputStream(uri)) { @@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { throw e; } - List<Notification.Action> actions = - populateNotificationActions(mContext, r, uri); List<Notification.Action> smartActions = new ArrayList<>(); if (mSmartActionsEnabled) { int timeoutMs = DeviceConfig.getInt( @@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mSmartActionsProvider), mContext)); } - mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions); - mParams.imageUri = uri; + + mImageData.uri = uri; + mImageData.smartActions = smartActions; + mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri); + mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri); + mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri); + + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.image = null; mParams.errorMsgResId = 0; } catch (Exception e) { @@ -216,29 +223,26 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Slog.e(TAG, "unable to save screenshot", e); mParams.clearImage(); mParams.errorMsgResId = R.string.screenshot_failed_to_save_text; - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); } return null; } @Override - protected void onPostExecute(Void params) { - mParams.finisher.accept(mParams.imageUri); - } - - @Override protected void onCancelled(Void params) { // If we are cancelled while the task is running in the background, we may get null // params. The finisher is expected to always be called back, so just use the baked-in // params from the ctor in any case. - mParams.mActionsReadyListener.onActionsReady(null, null, null); + mImageData.reset(); + mParams.mActionsReadyListener.onActionsReady(mImageData); mParams.finisher.accept(null); mParams.clearImage(); } @VisibleForTesting - List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) { + Notification.Action createShareAction(Context context, Resources r, Uri uri) { // Note: Both the share and edit actions are proxied through ActionProxyReceiver in // order to do some common work like dismissing the keyguard and sending // closeSystemWindows @@ -263,8 +267,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // by setting the (otherwise unused) request code to the current user id. int requestCode = context.getUserId(); - ArrayList<Notification.Action> actions = new ArrayList<>(); - PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode, new Intent(context, GlobalScreenshot.TargetChosenReceiver.class), PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); @@ -288,7 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_share), r.getString(com.android.internal.R.string.share), shareAction); - actions.add(shareActionBuilder.build()); + + return shareActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createEditAction(Context context, Resources r, Uri uri) { + // Note: Both the share and edit actions are proxied through ActionProxyReceiver in + // order to do some common work like dismissing the keyguard and sending + // closeSystemWindows // Create an edit intent, if a specific package is provided as the editor, then // launch that directly @@ -302,6 +312,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + // Create a edit action PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode, new Intent(context, GlobalScreenshot.ActionProxyReceiver.class) @@ -317,24 +331,30 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Notification.Action.Builder editActionBuilder = new Notification.Action.Builder( Icon.createWithResource(r, R.drawable.ic_screenshot_edit), r.getString(com.android.internal.R.string.screenshot_edit), editAction); - actions.add(editActionBuilder.build()); - - if (mCreateDeleteAction) { - // Create a delete action for the notification - PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, - new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) - .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) - .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) - .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, - mSmartActionsEnabled) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); - Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( - Icon.createWithResource(r, R.drawable.ic_screenshot_delete), - r.getString(com.android.internal.R.string.delete), deleteAction); - actions.add(deleteActionBuilder.build()); - } - return actions; + + return editActionBuilder.build(); + } + + @VisibleForTesting + Notification.Action createDeleteAction(Context context, Resources r, Uri uri) { + // Make sure pending intents for the system user are still unique across users + // by setting the (otherwise unused) request code to the current user id. + int requestCode = mContext.getUserId(); + + // Create a delete action for the notification + PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode, + new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class) + .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString()) + .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId) + .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, + mSmartActionsEnabled) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND), + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder( + Icon.createWithResource(r, R.drawable.ic_screenshot_delete), + r.getString(com.android.internal.R.string.delete), deleteAction); + + return deleteActionBuilder.build(); } private int getUserHandleOfForegroundApplication(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java new file mode 100644 index 000000000000..20fa991dcc1f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot; + +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; + +public enum ScreenshotEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "screenshot requested from global actions") + SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302), + @UiEvent(doc = "screenshot requested from key chord") + SCREENSHOT_REQUESTED_KEY_CHORD(303), + @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)") + SCREENSHOT_REQUESTED_KEY_OTHER(384), + @UiEvent(doc = "screenshot requested from overview") + SCREENSHOT_REQUESTED_OVERVIEW(304), + @UiEvent(doc = "screenshot requested from accessibility actions") + SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382), + @UiEvent(doc = "screenshot requested (other)") + SCREENSHOT_REQUESTED_OTHER(305), + @UiEvent(doc = "screenshot was saved") + SCREENSHOT_SAVED(306), + @UiEvent(doc = "screenshot failed to save") + SCREENSHOT_NOT_SAVED(336), + @UiEvent(doc = "screenshot preview tapped") + SCREENSHOT_PREVIEW_TAPPED(307), + @UiEvent(doc = "screenshot edit button tapped") + SCREENSHOT_EDIT_TAPPED(308), + @UiEvent(doc = "screenshot share button tapped") + SCREENSHOT_SHARE_TAPPED(309), + @UiEvent(doc = "screenshot smart action chip tapped") + SCREENSHOT_SMART_ACTION_TAPPED(374), + @UiEvent(doc = "screenshot scroll tapped") + SCREENSHOT_SCROLL_TAPPED(373), + @UiEvent(doc = "screenshot interaction timed out") + SCREENSHOT_INTERACTION_TIMEOUT(310), + @UiEvent(doc = "screenshot explicitly dismissed") + SCREENSHOT_EXPLICIT_DISMISSAL(311); + + private final int mId; + + ScreenshotEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + + static ScreenshotEvent getScreenshotSource(int source) { + switch (source) { + case SCREENSHOT_GLOBAL_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS; + case SCREENSHOT_KEY_CHORD: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD; + case SCREENSHOT_KEY_OTHER: + return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER; + case SCREENSHOT_OVERVIEW: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW; + case SCREENSHOT_ACCESSIBILITY_ACTIONS: + return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS; + case SCREENSHOT_OTHER: + default: + return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java index 811a8d936b77..fbcd6ba0ff47 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java @@ -32,7 +32,6 @@ import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; -import android.net.Uri; import android.os.UserHandle; import android.util.DisplayMetrics; import android.view.WindowManager; @@ -42,8 +41,6 @@ import com.android.systemui.R; import com.android.systemui.SystemUI; import com.android.systemui.util.NotificationChannels; -import java.util.List; - import javax.inject.Inject; /** @@ -185,23 +182,20 @@ public class ScreenshotNotificationsController { /** * Shows a notification with the saved screenshot and actions that can be taken with it. * - * @param imageUri URI for the saved image - * @param actions a list of notification actions which can be taken + * @param actionData SavedImageData struct with image URI and actions */ public void showScreenshotActionsNotification( - Uri imageUri, - List<Notification.Action> smartActions, - List<Notification.Action> actions) { - for (Notification.Action action : actions) { - mNotificationBuilder.addAction(action); - } - for (Notification.Action smartAction : smartActions) { + GlobalScreenshot.SavedImageData actionData) { + mNotificationBuilder.addAction(actionData.shareAction); + mNotificationBuilder.addAction(actionData.editAction); + mNotificationBuilder.addAction(actionData.deleteAction); + for (Notification.Action smartAction : actionData.smartActions) { mNotificationBuilder.addAction(smartAction); } // Create the intent to show the screenshot in gallery Intent launchIntent = new Intent(Intent.ACTION_VIEW); - launchIntent.setDataAndType(imageUri, "image/png"); + launchIntent.setDataAndType(actionData.uri, "image/png"); launchIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 8b8b6f8071e1..a6e083b04ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -32,6 +32,9 @@ import android.os.UserManager; import android.util.Log; import android.view.WindowManager; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.ScreenshotHelper; + import java.util.function.Consumer; import javax.inject.Inject; @@ -42,6 +45,7 @@ public class TakeScreenshotService extends Service { private final GlobalScreenshot mScreenshot; private final GlobalScreenshotLegacy mScreenshotLegacy; private final UserManager mUserManager; + private final UiEventLogger mUiEventLogger; private Handler mHandler = new Handler(Looper.myLooper()) { @Override @@ -64,14 +68,22 @@ public class TakeScreenshotService extends Service { return; } - // TODO (mkephart): clean up once notifications flow is fully deprecated + // TODO: clean up once notifications flow is fully deprecated boolean useCornerFlow = true; + + ScreenshotHelper.ScreenshotRequest screenshotRequest = + (ScreenshotHelper.ScreenshotRequest) msg.obj; + + mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource())); + switch (msg.what) { case WindowManager.TAKE_SCREENSHOT_FULLSCREEN: if (useCornerFlow) { mScreenshot.takeScreenshot(finisher); } else { - mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0); + mScreenshotLegacy.takeScreenshot( + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION: @@ -79,17 +91,15 @@ public class TakeScreenshotService extends Service { mScreenshot.takeScreenshotPartial(finisher); } else { mScreenshotLegacy.takeScreenshotPartial( - finisher, msg.arg1 > 0, msg.arg2 > 0); + finisher, screenshotRequest.getHasStatusBar(), + screenshotRequest.getHasNavBar()); } break; case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE: - Bitmap screenshot = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP); - Rect screenBounds = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS); - Insets insets = msg.getData().getParcelable( - WindowManager.PARCEL_KEY_SCREENSHOT_INSETS); - int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID); + Bitmap screenshot = screenshotRequest.getBitmap(); + Rect screenBounds = screenshotRequest.getBoundsInScreen(); + Insets insets = screenshotRequest.getInsets(); + int taskId = screenshotRequest.getTaskId(); if (useCornerFlow) { mScreenshot.handleImageAsScreenshot( screenshot, screenBounds, insets, taskId, finisher); @@ -106,10 +116,12 @@ public class TakeScreenshotService extends Service { @Inject public TakeScreenshotService(GlobalScreenshot globalScreenshot, - GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) { + GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager, + UiEventLogger uiEventLogger) { mScreenshot = globalScreenshot; mScreenshotLegacy = globalScreenshotLegacy; mUserManager = userManager; + mUiEventLogger = uiEventLogger; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 1b7524521d76..8c24c540e743 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.NavigationBarFragment; import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.policy.BatteryController; import javax.inject.Inject; @@ -139,7 +140,8 @@ public class NavigationBarController implements Callbacks { ? Dependency.get(LightBarController.class) : new LightBarController(context, Dependency.get(DarkIconDispatcher.class), - Dependency.get(BatteryController.class)); + Dependency.get(BatteryController.class), + Dependency.get(NavigationModeController.class)); navBar.setLightBarController(lightBarController); // TODO(b/118592525): to support multi-display, we start to add something which is diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index 4e6df0ad1ba4..d364689a65d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -23,11 +23,14 @@ import android.view.View; import com.android.systemui.DejankUtils; import com.android.systemui.bubbles.BubbleController; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.phone.StatusBar; import java.util.Optional; +import javax.inject.Inject; + /** * Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret, * app ops icon, etc) are handled elsewhere. @@ -35,15 +38,19 @@ import java.util.Optional; public final class NotificationClicker implements View.OnClickListener { private static final String TAG = "NotificationClicker"; - private final Optional<StatusBar> mStatusBar; private final BubbleController mBubbleController; + private final NotificationClickerLogger mLogger; + private final Optional<StatusBar> mStatusBar; private final NotificationActivityStarter mNotificationActivityStarter; - public NotificationClicker(Optional<StatusBar> statusBar, + private NotificationClicker( BubbleController bubbleController, + NotificationClickerLogger logger, + Optional<StatusBar> statusBar, NotificationActivityStarter notificationActivityStarter) { - mStatusBar = statusBar; mBubbleController = bubbleController; + mLogger = logger; + mStatusBar = statusBar; mNotificationActivityStarter = notificationActivityStarter; } @@ -58,25 +65,26 @@ public final class NotificationClicker implements View.OnClickListener { SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK")); final ExpandableNotificationRow row = (ExpandableNotificationRow) v; - final StatusBarNotification sbn = row.getEntry().getSbn(); - if (sbn == null) { - Log.e(TAG, "NotificationClicker called on an unclickable notification,"); - return; - } + final NotificationEntry entry = row.getEntry(); + mLogger.logOnClick(entry); // Check if the notification is displaying the menu, if so slide notification back if (isMenuVisible(row)) { + mLogger.logMenuVisible(entry); row.animateTranslateNotification(0); return; } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) { + mLogger.logParentMenuVisible(entry); row.getNotificationParent().animateTranslateNotification(0); return; } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) { // We never want to open the app directly if the user clicks in between // the notifications. + mLogger.logChildrenExpanded(entry); return; } else if (row.areGutsExposed()) { // ignore click if guts are exposed + mLogger.logGutsExposed(entry); return; } @@ -88,7 +96,7 @@ public final class NotificationClicker implements View.OnClickListener { mBubbleController.collapseStack(); } - mNotificationActivityStarter.onNotificationClicked(sbn, row); + mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row); } private boolean isMenuVisible(ExpandableNotificationRow row) { @@ -107,4 +115,30 @@ public final class NotificationClicker implements View.OnClickListener { row.setOnClickListener(null); } } + + /** Daggerized builder for NotificationClicker. */ + public static class Builder { + private final BubbleController mBubbleController; + private final NotificationClickerLogger mLogger; + + @Inject + public Builder( + BubbleController bubbleController, + NotificationClickerLogger logger) { + mBubbleController = bubbleController; + mLogger = logger; + } + + /** Builds an instance. */ + public NotificationClicker build( + Optional<StatusBar> statusBar, + NotificationActivityStarter notificationActivityStarter + ) { + return new NotificationClicker( + mBubbleController, + mLogger, + statusBar, + notificationActivityStarter); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt new file mode 100644 index 000000000000..fbf033bd2291 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel +import com.android.systemui.log.dagger.NotifInteractionLog +import com.android.systemui.statusbar.notification.collection.NotificationEntry +import javax.inject.Inject + +class NotificationClickerLogger @Inject constructor( + @NotifInteractionLog private val buffer: LogBuffer +) { + fun logOnClick(entry: NotificationEntry) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.key + str2 = entry.ranking.channel.id + }, { + "CLICK $str1 (channel=$str2)" + }) + } + + fun logMenuVisible(entry: NotificationEntry) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.key + }, { + "Ignoring click on $str1; menu is visible" + }) + } + + fun logParentMenuVisible(entry: NotificationEntry) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.key + }, { + "Ignoring click on $str1; parent menu is visible" + }) + } + + fun logChildrenExpanded(entry: NotificationEntry) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.key + }, { + "Ignoring click on $str1; children are expanded" + }) + } + + fun logGutsExposed(entry: NotificationEntry) { + buffer.log(TAG, LogLevel.DEBUG, { + str1 = entry.key + }, { + "Ignoring click on $str1; guts are exposed" + }) + } +} + +private const val TAG = "NotificationClicker" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 7f2f898565d8..c9754048e1d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.init import android.service.notification.StatusBarNotification -import com.android.systemui.bubbles.BubbleController import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.FeatureFlags import com.android.systemui.statusbar.NotificationListener @@ -62,12 +61,12 @@ class NotificationsControllerImpl @Inject constructor( private val deviceProvisionedController: DeviceProvisionedController, private val notificationRowBinder: NotificationRowBinderImpl, private val remoteInputUriController: RemoteInputUriController, - private val bubbleController: BubbleController, private val groupManager: NotificationGroupManager, private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper, private val headsUpManager: HeadsUpManager, private val headsUpController: HeadsUpController, - private val headsUpViewBinder: HeadsUpViewBinder + private val headsUpViewBinder: HeadsUpViewBinder, + private val clickerBuilder: NotificationClicker.Builder ) : NotificationsController { override fun initialize( @@ -87,10 +86,7 @@ class NotificationsControllerImpl @Inject constructor( listController.bind() notificationRowBinder.setNotificationClicker( - NotificationClicker( - Optional.of(statusBar), - bubbleController, - notificationActivityStarter)) + clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter)) notificationRowBinder.setUpWithPresenter( presenter, listContainer, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java index 796f22cbb3cb..b96cff830f31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java @@ -191,6 +191,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext); panel.getMediaPlayer().setMediaSession(token, iconDrawable, + notif.getLargeIcon(), tintColor, mBackgroundColor, mActions, @@ -201,6 +202,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi com.android.systemui.R.id.quick_settings_panel); bigPanel.addMediaSession(token, iconDrawable, + notif.getLargeIcon(), tintColor, mBackgroundColor, mActions, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 7d422e3c15a2..f9119c7a010f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -53,11 +53,13 @@ import android.view.WindowManagerGlobal; import com.android.internal.policy.GestureNavigationSettingsObserver; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.NavigationEdgeBackPlugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.shared.plugins.PluginManager; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.QuickStepContract; @@ -74,7 +76,7 @@ import java.util.concurrent.Executor; /** * Utility class to handle edge swipes for back gesture */ -public class EdgeBackGestureHandler implements DisplayListener, +public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener, PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> { private static final String TAG = "EdgeBackGestureHandler"; @@ -165,6 +167,7 @@ public class EdgeBackGestureHandler implements DisplayListener, private boolean mIsGesturalModeEnabled; private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; + private boolean mIsBackGestureAllowed; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -200,7 +203,7 @@ public class EdgeBackGestureHandler implements DisplayListener, public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService, SysUiState sysUiFlagContainer, PluginManager pluginManager) { - final Resources res = context.getResources(); + super(Dependency.get(BroadcastDispatcher.class)); mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = context.getMainExecutor(); @@ -216,20 +219,30 @@ public class EdgeBackGestureHandler implements DisplayListener, ViewConfiguration.getLongPressTimeout()); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( - mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res)); + mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources); - updateCurrentUserResources(res); + updateCurrentUserResources(); sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags); } - public void updateCurrentUserResources(Resources res) { + public void updateCurrentUserResources() { + Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext() + .getResources(); mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res); mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res); + mIsBackGestureAllowed = + !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); mBottomGestureHeight = res.getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_gesture_height); } + @Override + public void onUserSwitched(int newUserId) { + updateIsEnabled(); + updateCurrentUserResources(); + } + /** * @see NavigationBarView#onAttachedToWindow() */ @@ -243,6 +256,7 @@ public class EdgeBackGestureHandler implements DisplayListener, Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME), false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL); updateIsEnabled(); + startTracking(); } /** @@ -255,6 +269,7 @@ public class EdgeBackGestureHandler implements DisplayListener, } mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver); updateIsEnabled(); + stopTracking(); } private void setRotationCallbacks(boolean enable) { @@ -269,10 +284,13 @@ public class EdgeBackGestureHandler implements DisplayListener, } } - public void onNavigationModeChanged(int mode, Context currentUserContext) { + /** + * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged + */ + public void onNavigationModeChanged(int mode) { mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode); updateIsEnabled(); - updateCurrentUserResources(currentUserContext.getResources()); + updateCurrentUserResources(); } public void onNavBarTransientStateChanged(boolean isTransient) { @@ -363,6 +381,10 @@ public class EdgeBackGestureHandler implements DisplayListener, updateDisplaySize(); } + public boolean isHandlingGestures() { + return mIsEnabled && mIsBackGestureAllowed; + } + private WindowManager.LayoutParams createLayoutParams() { Resources resources = mContext.getResources(); WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams( @@ -469,9 +491,9 @@ public class EdgeBackGestureHandler implements DisplayListener, mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset; mLogGesture = false; mInRejectedExclusion = false; - mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags) - && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()) - && !mDisabledForQuickstep; + mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) + && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge); mEdgeBackPlugin.onMotionEvent(ev); @@ -599,6 +621,7 @@ public class EdgeBackGestureHandler implements DisplayListener, public void dump(PrintWriter pw) { pw.println("EdgeBackGestureHandler:"); pw.println(" mIsEnabled=" + mIsEnabled); + pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed); pw.println(" mAllowGesture=" + mAllowGesture); pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep); pw.println(" mInRejectedExclusion" + mInRejectedExclusion); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 90bc075b399d..ae7867d68af4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -85,6 +85,8 @@ import com.android.systemui.statusbar.policy.PreviewInflater; import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory; import com.android.systemui.tuner.TunerService; +import java.util.concurrent.Executor; + /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. @@ -553,7 +555,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } }; if (!mKeyguardStateController.canDismissLockScreen()) { - AsyncTask.execute(runnable); + Dependency.get(Executor.class).execute(runnable); } else { boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr) && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java index d35e1e1e176a..3e5eb5fba8f2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java @@ -33,6 +33,7 @@ import com.android.internal.view.AppearanceRegion; import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.plugins.DarkIconDispatcher; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.policy.BatteryController; import java.io.FileDescriptor; @@ -58,6 +59,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0]; private int mStatusBarMode; private int mNavigationBarMode; + private int mNavigationMode; private final Color mDarkModeColor; /** @@ -84,11 +86,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC @Inject public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher, - BatteryController batteryController) { + BatteryController batteryController, NavigationModeController navModeController) { mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone)); mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher; mBatteryController = batteryController; mBatteryController.addCallback(this); + mNavigationMode = navModeController.addListener((mode) -> { + mNavigationMode = mode; + }); } public void setNavigationBar(LightBarTransitionsController navigationBar) { @@ -234,7 +239,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC } private void updateNavigation() { - if (mNavigationBarController != null) { + if (mNavigationBarController != null + && !QuickStepContract.isGesturalMode(mNavigationMode)) { mNavigationBarController.setIconsDark(mNavigationLight, animateChange()); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 84aecd4e0759..2978772cac5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -123,7 +123,7 @@ public class NavigationBarView extends FrameLayout implements private KeyButtonDrawable mRecentIcon; private KeyButtonDrawable mDockedIcon; - private final EdgeBackGestureHandler mEdgeBackGestureHandler; + private EdgeBackGestureHandler mEdgeBackGestureHandler; private final DeadZone mDeadZone; private boolean mDeadZoneConsuming = false; private final NavigationBarTransitions mBarTransitions; @@ -244,7 +244,7 @@ public class NavigationBarView extends FrameLayout implements private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> { // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully // gestural mode, the entire nav bar should be touchable. - if (!isGesturalMode(mNavBarMode) || mImeVisible) { + if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) { info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } @@ -296,8 +296,6 @@ public class NavigationBarView extends FrameLayout implements R.style.RotateButtonCCWStart90, isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton); - final ContextualButton backButton = new ContextualButton(R.id.back, 0); - mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); mConfiguration.updateFrom(context.getResources().getConfiguration()); @@ -305,7 +303,7 @@ public class NavigationBarView extends FrameLayout implements mScreenPinningNotify = new ScreenPinningNotify(mContext); mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class)); - mButtonDispatchers.put(R.id.back, backButton); + mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle)); mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); @@ -659,7 +657,7 @@ public class NavigationBarView extends FrameLayout implements boolean disableHomeHandle = disableRecent && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); - boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode) + boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures() || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0)); // When screen pinning, don't hide back and home when connected service or back and @@ -686,9 +684,9 @@ public class NavigationBarView extends FrameLayout implements } } - getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); - getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); - getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); + getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); + getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); + getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE); } @@ -838,10 +836,9 @@ public class NavigationBarView extends FrameLayout implements @Override public void onNavigationModeChanged(int mode) { - Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext(); mNavBarMode = mode; mBarTransitions.onNavigationModeChanged(mNavBarMode); - mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx); + mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode); mRecentsOnboarding.onNavigationModeChanged(mNavBarMode); getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode); @@ -864,6 +861,7 @@ public class NavigationBarView extends FrameLayout implements @Override public void onFinishInflate() { + super.onFinishInflate(); mNavigationInflaterView = findViewById(R.id.navigation_inflater); mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java index d24ccf343a3a..6061b1e73d1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java @@ -17,9 +17,6 @@ package com.android.systemui.statusbar.phone; import static android.content.Intent.ACTION_OVERLAY_CHANGED; -import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED; -import static android.os.UserHandle.USER_CURRENT; -import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY; @@ -38,17 +35,14 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Secure; import android.util.Log; -import android.util.SparseBooleanArray; import com.android.systemui.Dumpable; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.statusbar.policy.DeviceProvisionedController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -70,104 +64,34 @@ public class NavigationModeController implements Dumpable { private final Context mContext; private Context mCurrentUserContext; private final IOverlayManager mOverlayManager; - private final DeviceProvisionedController mDeviceProvisionedController; private final Executor mUiBgExecutor; - private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray(); - private ArrayList<ModeChangedListener> mListeners = new ArrayList<>(); private BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - switch (intent.getAction()) { - case ACTION_OVERLAY_CHANGED: if (DEBUG) { Log.d(TAG, "ACTION_OVERLAY_CHANGED"); } updateCurrentInteractionMode(true /* notify */); - break; - } } }; - private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback = - new DeviceProvisionedController.DeviceProvisionedListener() { - @Override - public void onDeviceProvisionedChanged() { - if (DEBUG) { - Log.d(TAG, "onDeviceProvisionedChanged: " - + mDeviceProvisionedController.isDeviceProvisioned()); - } - // Once the device has been provisioned, check if we can restore gestural nav - restoreGesturalNavOverlayIfNecessary(); - } - - @Override - public void onUserSetupChanged() { - if (DEBUG) { - Log.d(TAG, "onUserSetupChanged: " - + mDeviceProvisionedController.isCurrentUserSetup()); - } - // Once the user has been setup, check if we can restore gestural nav - restoreGesturalNavOverlayIfNecessary(); - } - - @Override - public void onUserSwitched() { - if (DEBUG) { - Log.d(TAG, "onUserSwitched: " - + ActivityManagerWrapper.getInstance().getCurrentUserId()); - } - - // Update the nav mode for the current user - updateCurrentInteractionMode(true /* notify */); - - // When switching users, defer enabling the gestural nav overlay until the user - // is all set up - deferGesturalNavOverlayIfNecessary(); - } - }; - @Inject - public NavigationModeController(Context context, - DeviceProvisionedController deviceProvisionedController, - @UiBackground Executor uiBgExecutor) { + public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) { mContext = context; mCurrentUserContext = context; mOverlayManager = IOverlayManager.Stub.asInterface( ServiceManager.getService(Context.OVERLAY_SERVICE)); mUiBgExecutor = uiBgExecutor; - mDeviceProvisionedController = deviceProvisionedController; - mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback); IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED); overlayFilter.addDataScheme("package"); overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL); mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null); - IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED); - mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null, - null); - updateCurrentInteractionMode(false /* notify */); - - // Check if we need to defer enabling gestural nav - deferGesturalNavOverlayIfNecessary(); - } - - private boolean setGestureModeOverlayForMainLauncher() { - if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) { - // Already in gesture mode - return true; - } - - Log.d(TAG, "Switching system navigation to full-gesture mode:" - + " contextUser=" - + mCurrentUserContext.getUserId()); - - setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT); - return true; } public void updateCurrentInteractionMode(boolean notify) { @@ -176,10 +100,9 @@ public class NavigationModeController implements Dumpable { if (mode == NAV_BAR_MODE_GESTURAL) { switchToDefaultGestureNavOverlayIfNecessary(); } - mUiBgExecutor.execute(() -> { + mUiBgExecutor.execute(() -> Settings.Secure.putString(mCurrentUserContext.getContentResolver(), - Secure.NAVIGATION_MODE, String.valueOf(mode)); - }); + Secure.NAVIGATION_MODE, String.valueOf(mode))); if (DEBUG) { Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode); dumpAssetPaths(mCurrentUserContext); @@ -230,61 +153,11 @@ public class NavigationModeController implements Dumpable { } } - private void deferGesturalNavOverlayIfNecessary() { - final int userId = mDeviceProvisionedController.getCurrentUser(); - mRestoreGesturalNavBarMode.put(userId, false); - if (mDeviceProvisionedController.isDeviceProvisioned() - && mDeviceProvisionedController.isCurrentUserSetup()) { - // User is already setup and device is provisioned, nothing to do - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is " - + "setup"); - } - return; - } - - ArrayList<String> defaultOverlays = new ArrayList<>(); - try { - defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages())); - } catch (RemoteException e) { - Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays"); - } - if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) { - // No default gesture nav overlay - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, " - + "default=" + defaultOverlays); - } - return; - } - - // If the default is gestural, force-enable three button mode until the device is - // provisioned - setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT); - mRestoreGesturalNavBarMode.put(userId, true); - - if (DEBUG) { - Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode"); - } - } - - private void restoreGesturalNavOverlayIfNecessary() { - if (DEBUG) { - Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore=" - + mRestoreGesturalNavBarMode); - } - final int userId = mDeviceProvisionedController.getCurrentUser(); - if (mRestoreGesturalNavBarMode.get(userId)) { - // Restore the gestural state if necessary - setGestureModeOverlayForMainLauncher(); - mRestoreGesturalNavBarMode.put(userId, false); - } - } - private void switchToDefaultGestureNavOverlayIfNecessary() { final int userId = mCurrentUserContext.getUserId(); try { - final IOverlayManager om = mOverlayManager; + final IOverlayManager om = IOverlayManager.Stub.asInterface( + ServiceManager.getService(Context.OVERLAY_SERVICE)); final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId); if (info != null && !info.isEnabled()) { // Enable the default gesture nav overlay, and move the back gesture inset scale to @@ -309,20 +182,6 @@ public class NavigationModeController implements Dumpable { } } - public void setModeOverlay(String overlayPkg, int userId) { - mUiBgExecutor.execute(() -> { - try { - mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId); - if (DEBUG) { - Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg - + " userId=" + userId); - } - } catch (SecurityException | IllegalStateException | RemoteException e) { - Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId); - } - }); - } - @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("NavigationModeController:"); @@ -334,11 +193,6 @@ public class NavigationModeController implements Dumpable { defaultOverlays = "failed_to_fetch"; } pw.println(" defaultOverlays=" + defaultOverlays); - pw.println(" restoreGesturalNavMode:"); - for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) { - pw.println(" userId=" + mRestoreGesturalNavBarMode.keyAt(i) - + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i)); - } dumpAssetPaths(mCurrentUserContext); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java index 1a6b415f87db..bf52a7ae2bf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java @@ -148,11 +148,6 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener, updateSamplingRect(); } - private void postUpdateSamplingListener() { - mHandler.removeCallbacks(mUpdateSamplingListener); - mHandler.post(mUpdateSamplingListener); - } - private void updateSamplingListener() { boolean isSamplingEnabled = mSamplingEnabled && !mSamplingRequestBounds.isEmpty() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 53fa2630a9c3..fbe3e9b19248 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -40,7 +40,6 @@ import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.EventLog; -import android.util.Log; import android.view.RemoteAnimationAdapter; import android.view.View; @@ -91,92 +90,119 @@ import dagger.Lazy; */ public class StatusBarNotificationActivityStarter implements NotificationActivityStarter { - private static final String TAG = "NotifActivityStarter"; - protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + private final Context mContext; + + private final CommandQueue mCommandQueue; + private final Handler mMainThreadHandler; + private final Handler mBackgroundHandler; + private final Executor mUiBgExecutor; + private final NotificationEntryManager mEntryManager; + private final NotifPipeline mNotifPipeline; + private final NotifCollection mNotifCollection; + private final HeadsUpManagerPhone mHeadsUpManager; + private final ActivityStarter mActivityStarter; + private final IStatusBarService mBarService; + private final StatusBarStateController mStatusBarStateController; + private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final KeyguardManager mKeyguardManager; + private final IDreamManager mDreamManager; + private final BubbleController mBubbleController; private final Lazy<AssistManager> mAssistManagerLazy; - private final NotificationGroupManager mGroupManager; - private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback; private final NotificationRemoteInputManager mRemoteInputManager; + private final NotificationGroupManager mGroupManager; private final NotificationLockscreenUserManager mLockscreenUserManager; private final ShadeController mShadeController; - private final StatusBar mStatusBar; private final KeyguardStateController mKeyguardStateController; - private final ActivityStarter mActivityStarter; - private final NotificationEntryManager mEntryManager; - private final NotifPipeline mNotifPipeline; - private final NotifCollection mNotifCollection; - private final FeatureFlags mFeatureFlags; - private final StatusBarStateController mStatusBarStateController; private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; + private final LockPatternUtils mLockPatternUtils; + private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback; + private final ActivityIntentHelper mActivityIntentHelper; + + private final FeatureFlags mFeatureFlags; private final MetricsLogger mMetricsLogger; - private final Context mContext; - private final NotificationPanelViewController mNotificationPanel; + private final StatusBarNotificationActivityStarterLogger mLogger; + + private final StatusBar mStatusBar; private final NotificationPresenter mPresenter; - private final LockPatternUtils mLockPatternUtils; - private final HeadsUpManagerPhone mHeadsUpManager; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - private final KeyguardManager mKeyguardManager; + private final NotificationPanelViewController mNotificationPanel; private final ActivityLaunchAnimator mActivityLaunchAnimator; - private final IStatusBarService mBarService; - private final CommandQueue mCommandQueue; - private final IDreamManager mDreamManager; - private final Handler mMainThreadHandler; - private final Handler mBackgroundHandler; - private final ActivityIntentHelper mActivityIntentHelper; - private final BubbleController mBubbleController; - private final Executor mUiBgExecutor; private boolean mIsCollapsingToShowActivityOverLockscreen; - private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue, - Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel, - NotificationPresenter presenter, NotificationEntryManager entryManager, - HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter, - ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService, + private StatusBarNotificationActivityStarter( + Context context, + CommandQueue commandQueue, + Handler mainThreadHandler, + Handler backgroundHandler, + Executor uiBgExecutor, + NotificationEntryManager entryManager, + NotifPipeline notifPipeline, + NotifCollection notifCollection, + HeadsUpManagerPhone headsUpManager, + ActivityStarter activityStarter, + IStatusBarService statusBarService, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, KeyguardManager keyguardManager, - IDreamManager dreamManager, NotificationRemoteInputManager remoteInputManager, - StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager, + IDreamManager dreamManager, + BubbleController bubbleController, + Lazy<AssistManager> assistManagerLazy, + NotificationRemoteInputManager remoteInputManager, + NotificationGroupManager groupManager, NotificationLockscreenUserManager lockscreenUserManager, - ShadeController shadeController, StatusBar statusBar, + ShadeController shadeController, KeyguardStateController keyguardStateController, NotificationInterruptStateProvider notificationInterruptStateProvider, - MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, - Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor, - ActivityIntentHelper activityIntentHelper, BubbleController bubbleController, - FeatureFlags featureFlags, NotifPipeline notifPipeline, - NotifCollection notifCollection) { + LockPatternUtils lockPatternUtils, + StatusBarRemoteInputCallback remoteInputCallback, + ActivityIntentHelper activityIntentHelper, + + FeatureFlags featureFlags, + MetricsLogger metricsLogger, + StatusBarNotificationActivityStarterLogger logger, + + StatusBar statusBar, + NotificationPresenter presenter, + NotificationPanelViewController panel, + ActivityLaunchAnimator activityLaunchAnimator) { mContext = context; - mNotificationPanel = panel; - mPresenter = presenter; + mCommandQueue = commandQueue; + mMainThreadHandler = mainThreadHandler; + mBackgroundHandler = backgroundHandler; + mUiBgExecutor = uiBgExecutor; + mEntryManager = entryManager; + mNotifPipeline = notifPipeline; + mNotifCollection = notifCollection; mHeadsUpManager = headsUpManager; - mActivityLaunchAnimator = activityLaunchAnimator; + mActivityStarter = activityStarter; mBarService = statusBarService; - mCommandQueue = commandQueue; + mStatusBarStateController = statusBarStateController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardManager = keyguardManager; mDreamManager = dreamManager; + mBubbleController = bubbleController; + mAssistManagerLazy = assistManagerLazy; mRemoteInputManager = remoteInputManager; + mGroupManager = groupManager; mLockscreenUserManager = lockscreenUserManager; mShadeController = shadeController; - // TODO: use KeyguardStateController#isOccluded to remove this dependency - mStatusBar = statusBar; mKeyguardStateController = keyguardStateController; - mActivityStarter = activityStarter; - mEntryManager = entryManager; - mStatusBarStateController = statusBarStateController; mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mMetricsLogger = metricsLogger; - mAssistManagerLazy = assistManagerLazy; - mGroupManager = groupManager; mLockPatternUtils = lockPatternUtils; - mBackgroundHandler = backgroundHandler; - mUiBgExecutor = uiBgExecutor; + mStatusBarRemoteInputCallback = remoteInputCallback; + mActivityIntentHelper = activityIntentHelper; + mFeatureFlags = featureFlags; - mNotifPipeline = notifPipeline; - mNotifCollection = notifCollection; + mMetricsLogger = metricsLogger; + mLogger = logger; + + // TODO: use KeyguardStateController#isOccluded to remove this dependency + mStatusBar = statusBar; + mPresenter = presenter; + mNotificationPanel = panel; + mActivityLaunchAnimator = activityLaunchAnimator; + if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) { mEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override @@ -192,11 +218,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } }); } - - mStatusBarRemoteInputCallback = remoteInputCallback; - mMainThreadHandler = mainThreadHandler; - mActivityIntentHelper = activityIntentHelper; - mBubbleController = bubbleController; } /** @@ -207,6 +228,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit */ @Override public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) { + mLogger.logStartingActivityFromClick(sbn.getKey()); + RemoteInputController controller = mRemoteInputManager.getController(); if (controller.isRemoteInputActive(row.getEntry()) && !TextUtils.isEmpty(row.getActiveRemoteInputText())) { @@ -225,7 +248,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit // The only valid case is Bubble notifications. Guard against other cases // entering here. if (intent == null && !isBubble) { - Log.e(TAG, "onNotificationClicked called for non-clickable notification!"); + mLogger.logNonClickableNotification(sbn.getKey()); return; } @@ -258,6 +281,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit boolean isActivityIntent, boolean wasOccluded, boolean showOverLockscreen) { + mLogger.logHandleClickAfterKeyguardDismissed(sbn.getKey()); + // TODO: Some of this code may be able to move to NotificationEntryManager. if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(sbn.getKey())) { // Release the HUN notification to the shade. @@ -304,6 +329,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit boolean isActivityIntent, boolean wasOccluded, NotificationEntry parentToCancelFinal) { + mLogger.logHandleClickAfterPanelCollapsed(sbn.getKey()); + String notificationKey = sbn.getKey(); try { // The intent we are sending is for the application, which @@ -343,9 +370,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit remoteInputText.toString()); } if (isBubble) { + mLogger.logExpandingBubble(notificationKey); expandBubbleStackOnMainThread(notificationKey); } else { - startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent); + startNotificationIntent( + intent, fillInIntent, entry, row, wasOccluded, isActivityIntent); } if (isActivityIntent || isBubble) { mAssistManagerLazy.get().hideAssist(); @@ -392,10 +421,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } } - private void startNotificationIntent(PendingIntent intent, Intent fillInIntent, - View row, boolean wasOccluded, boolean isActivityIntent) { + private void startNotificationIntent( + PendingIntent intent, + Intent fillInIntent, + NotificationEntry entry, + View row, + boolean wasOccluded, + boolean isActivityIntent) { RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row, wasOccluded); + mLogger.logStartNotificationIntent(entry.getKey(), intent); try { if (adapter != null) { ActivityTaskManager.getService() @@ -408,7 +443,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } catch (RemoteException | PendingIntent.CanceledException e) { // the stack trace isn't very helpful here. // Just log the exception message. - Log.w(TAG, "Sending contentIntent failed: " + e); + mLogger.logSendingIntentFailed(e); // TODO: Dismiss Keyguard. } } @@ -438,13 +473,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private void handleFullScreenIntent(NotificationEntry entry) { if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) { if (shouldSuppressFullScreenIntent(entry)) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey()); - } + mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey()); } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) { - if (DEBUG) { - Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.getKey()); - } + mLogger.logFullScreenIntentNotImportantEnough(entry.getKey()); } else { // Stop screensaver if the notification has a fullscreen intent. // (like an incoming phone call) @@ -457,13 +488,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit }); // not immersive & a fullscreen alert should be shown - if (DEBUG) { - Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent"); - } + final PendingIntent fullscreenIntent = + entry.getSbn().getNotification().fullScreenIntent; + mLogger.logSendingFullScreenIntent(entry.getKey(), fullscreenIntent); try { EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION, entry.getKey()); - entry.getSbn().getNotification().fullScreenIntent.send(); + fullscreenIntent.send(); entry.notifyFullScreenIntentLaunched(); mMetricsLogger.count("note_fullscreen", 1); } catch (PendingIntent.CanceledException e) { @@ -578,9 +609,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit public static class Builder { private final Context mContext; private final CommandQueue mCommandQueue; - private final Lazy<AssistManager> mAssistManagerLazy; + private final Handler mMainThreadHandler; + private final Handler mBackgroundHandler; + private final Executor mUiBgExecutor; private final NotificationEntryManager mEntryManager; - private final FeatureFlags mFeatureFlags; private final NotifPipeline mNotifPipeline; private final NotifCollection mNotifCollection; private final HeadsUpManagerPhone mHeadsUpManager; @@ -590,30 +622,37 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final KeyguardManager mKeyguardManager; private final IDreamManager mDreamManager; + private final BubbleController mBubbleController; + private final Lazy<AssistManager> mAssistManagerLazy; private final NotificationRemoteInputManager mRemoteInputManager; - private final StatusBarRemoteInputCallback mRemoteInputCallback; private final NotificationGroupManager mGroupManager; private final NotificationLockscreenUserManager mLockscreenUserManager; + private final ShadeController mShadeController; private final KeyguardStateController mKeyguardStateController; - private final MetricsLogger mMetricsLogger; + private final NotificationInterruptStateProvider mNotificationInterruptStateProvider; private final LockPatternUtils mLockPatternUtils; - private final Handler mMainThreadHandler; - private final Handler mBackgroundHandler; - private final Executor mUiBgExecutor; + private final StatusBarRemoteInputCallback mRemoteInputCallback; private final ActivityIntentHelper mActivityIntentHelper; - private final BubbleController mBubbleController; - private NotificationPanelViewController mNotificationPanelViewController; - private NotificationInterruptStateProvider mNotificationInterruptStateProvider; - private final ShadeController mShadeController; + + private final FeatureFlags mFeatureFlags; + private final MetricsLogger mMetricsLogger; + private final StatusBarNotificationActivityStarterLogger mLogger; + + private StatusBar mStatusBar; private NotificationPresenter mNotificationPresenter; + private NotificationPanelViewController mNotificationPanelViewController; private ActivityLaunchAnimator mActivityLaunchAnimator; - private StatusBar mStatusBar; @Inject - public Builder(Context context, + public Builder( + Context context, CommandQueue commandQueue, - Lazy<AssistManager> assistManagerLazy, + @Main Handler mainThreadHandler, + @Background Handler backgroundHandler, + @UiBackground Executor uiBgExecutor, NotificationEntryManager entryManager, + NotifPipeline notifPipeline, + NotifCollection notifCollection, HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter, IStatusBarService statusBarService, @@ -621,27 +660,30 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit StatusBarKeyguardViewManager statusBarKeyguardViewManager, KeyguardManager keyguardManager, IDreamManager dreamManager, + BubbleController bubbleController, + Lazy<AssistManager> assistManagerLazy, NotificationRemoteInputManager remoteInputManager, - StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager, NotificationLockscreenUserManager lockscreenUserManager, + ShadeController shadeController, KeyguardStateController keyguardStateController, NotificationInterruptStateProvider notificationInterruptStateProvider, - MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils, - @Main Handler mainThreadHandler, - @Background Handler backgroundHandler, - @UiBackground Executor uiBgExecutor, + StatusBarRemoteInputCallback remoteInputCallback, ActivityIntentHelper activityIntentHelper, - BubbleController bubbleController, - ShadeController shadeController, + FeatureFlags featureFlags, - NotifPipeline notifPipeline, - NotifCollection notifCollection) { + MetricsLogger metricsLogger, + StatusBarNotificationActivityStarterLogger logger) { + mContext = context; mCommandQueue = commandQueue; - mAssistManagerLazy = assistManagerLazy; + mMainThreadHandler = mainThreadHandler; + mBackgroundHandler = backgroundHandler; + mUiBgExecutor = uiBgExecutor; mEntryManager = entryManager; + mNotifPipeline = notifPipeline; + mNotifCollection = notifCollection; mHeadsUpManager = headsUpManager; mActivityStarter = activityStarter; mStatusBarService = statusBarService; @@ -649,23 +691,21 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mKeyguardManager = keyguardManager; mDreamManager = dreamManager; + mBubbleController = bubbleController; + mAssistManagerLazy = assistManagerLazy; mRemoteInputManager = remoteInputManager; - mRemoteInputCallback = remoteInputCallback; mGroupManager = groupManager; mLockscreenUserManager = lockscreenUserManager; + mShadeController = shadeController; mKeyguardStateController = keyguardStateController; mNotificationInterruptStateProvider = notificationInterruptStateProvider; - mMetricsLogger = metricsLogger; mLockPatternUtils = lockPatternUtils; - mMainThreadHandler = mainThreadHandler; - mBackgroundHandler = backgroundHandler; - mUiBgExecutor = uiBgExecutor; + mRemoteInputCallback = remoteInputCallback; mActivityIntentHelper = activityIntentHelper; - mBubbleController = bubbleController; - mShadeController = shadeController; + mFeatureFlags = featureFlags; - mNotifPipeline = notifPipeline; - mNotifCollection = notifCollection; + mMetricsLogger = metricsLogger; + mLogger = logger; } /** Sets the status bar to use as {@link StatusBar}. */ @@ -692,37 +732,42 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } public StatusBarNotificationActivityStarter build() { - return new StatusBarNotificationActivityStarter(mContext, - mCommandQueue, mAssistManagerLazy, - mNotificationPanelViewController, - mNotificationPresenter, + return new StatusBarNotificationActivityStarter( + mContext, + mCommandQueue, + mMainThreadHandler, + mBackgroundHandler, + mUiBgExecutor, mEntryManager, + mNotifPipeline, + mNotifCollection, mHeadsUpManager, mActivityStarter, - mActivityLaunchAnimator, mStatusBarService, mStatusBarStateController, mStatusBarKeyguardViewManager, mKeyguardManager, mDreamManager, + mBubbleController, + mAssistManagerLazy, mRemoteInputManager, - mRemoteInputCallback, mGroupManager, mLockscreenUserManager, mShadeController, - mStatusBar, mKeyguardStateController, mNotificationInterruptStateProvider, - mMetricsLogger, mLockPatternUtils, - mMainThreadHandler, - mBackgroundHandler, - mUiBgExecutor, + mRemoteInputCallback, mActivityIntentHelper, - mBubbleController, + mFeatureFlags, - mNotifPipeline, - mNotifCollection); + mMetricsLogger, + mLogger, + + mStatusBar, + mNotificationPresenter, + mNotificationPanelViewController, + mActivityLaunchAnimator); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt new file mode 100644 index 000000000000..d118747a0365 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.phone + +import android.app.PendingIntent +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.dagger.NotifInteractionLog +import javax.inject.Inject + +class StatusBarNotificationActivityStarterLogger @Inject constructor( + @NotifInteractionLog private val buffer: LogBuffer +) { + fun logStartingActivityFromClick(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "(1/4) onNotificationClicked: $str1" + }) + } + + fun logHandleClickAfterKeyguardDismissed(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1" + }) + } + + fun logHandleClickAfterPanelCollapsed(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "(3/4) handleNotificationClickAfterPanelCollapsed: $str1" + }) + } + + fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) { + buffer.log(TAG, INFO, { + str1 = key + str2 = pendingIntent.intent.toString() + }, { + "(4/4) Starting $str2 for notification $str1" + }) + } + + fun logExpandingBubble(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "Expanding bubble for $str1 (rather than firing intent)" + }) + } + + fun logSendingIntentFailed(e: Exception) { + buffer.log(TAG, WARNING, { + str1 = e.toString() + }, { + "Sending contentIntentFailed: $str1" + }) + } + + fun logNonClickableNotification(key: String) { + buffer.log(TAG, ERROR, { + str1 = key + }, { + "onNotificationClicked called for non-clickable notification! $str1" + }) + } + + fun logFullScreenIntentSuppressedByDnD(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "No Fullscreen intent: suppressed by DND: $str1" + }) + } + + fun logFullScreenIntentNotImportantEnough(key: String) { + buffer.log(TAG, DEBUG, { + str1 = key + }, { + "No Fullscreen intent: not important enough: $str1" + }) + } + + fun logSendingFullScreenIntent(key: String, pendingIntent: PendingIntent) { + buffer.log(TAG, INFO, { + str1 = key + str2 = pendingIntent.intent.toString() + }, { + "Notification $str1 has fullScreenIntent; sending fullScreenIntent $str2" + }) + } +} + +private const val TAG = "NotifActivityStarter" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 051bd29bc323..a284335c972e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -357,7 +357,18 @@ public class NetworkControllerImpl extends BroadcastReceiver mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler); mListening = true; + // Initial setup of connectivity. Handled as if we had received a sticky broadcast of + // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION. + mReceiverHandler.post(this::updateConnectivity); + + // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast + // of WifiManager.WIFI_STATE_CHANGED_ACTION or WifiManager.NETWORK_STATE_CHANGED_ACTION + mReceiverHandler.post(mWifiSignalController::fetchInitialState); updateMobileControllers(); + + // Initial setup of emergency information. Handled as if we had received a sticky broadcast + // of TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED. + mReceiverHandler.post(this::recalculateEmergency); } private void unregisterListeners() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index b258fd47871a..5257ce4c6bd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -102,6 +102,20 @@ public class WifiSignalController extends } /** + * Fetches wifi initial state replacing the initial sticky broadcast. + */ + public void fetchInitialState() { + mWifiTracker.fetchInitialState(); + mCurrentState.enabled = mWifiTracker.enabled; + mCurrentState.connected = mWifiTracker.connected; + mCurrentState.ssid = mWifiTracker.ssid; + mCurrentState.rssi = mWifiTracker.rssi; + mCurrentState.level = mWifiTracker.level; + mCurrentState.statusLabel = mWifiTracker.statusLabel; + notifyListenersIfNecessary(); + } + + /** * Extract wifi state directly from broadcasts about changes in wifi state. */ public void handleBroadcast(Intent intent) { diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt index d65b285adb0c..8880df9959c1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt +++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt @@ -115,7 +115,9 @@ abstract class RelativeTouchListener : View.OnTouchListener { performedLongClick = false handler.postDelayed({ - performedLongClick = v.performLongClick() + if (v.isLongClickable) { + performedLongClick = v.performLongClick() + } }, ViewConfiguration.getLongPressTimeout().toLong()) } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index 8625d63a3c7e..db08d64acc10 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -61,14 +61,17 @@ internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>() /** * Default spring configuration to use for animations where stiffness and/or damping ratio - * were not provided. + * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig]. */ -private val defaultSpring = PhysicsAnimator.SpringConfig( +private val globalDefaultSpring = PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY) -/** Default fling configuration to use for animations where friction was not provided. */ -private val defaultFling = PhysicsAnimator.FlingConfig( +/** + * Default fling configuration to use for animations where friction was not provided, and a default + * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig]. + */ +private val globalDefaultFling = PhysicsAnimator.FlingConfig( friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE) /** Whether to log helpful debug information about animations. */ @@ -111,6 +114,12 @@ class PhysicsAnimator<T> private constructor (val target: T) { /** End actions to run when all animations have completed. */ private val endActions = ArrayList<EndAction>() + /** SpringConfig to use by default for properties whose springs were not provided. */ + private var defaultSpring: SpringConfig = globalDefaultSpring + + /** FlingConfig to use by default for properties whose fling configs were not provided. */ + private var defaultFling: FlingConfig = globalDefaultFling + /** * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add @@ -204,6 +213,19 @@ class PhysicsAnimator<T> private constructor (val target: T) { } /** + * Springs a property to a given value using the provided configuration options, and a start + * velocity of 0f. + * + * @see spring + */ + fun spring( + property: FloatPropertyCompat<in T>, + toPosition: Float + ): PhysicsAnimator<T> { + return spring(property, toPosition, 0f) + } + + /** * Flings a property using the given start velocity, using a [FlingAnimation] configured using * the provided configuration settings. * @@ -392,6 +414,14 @@ class PhysicsAnimator<T> private constructor (val target: T) { return this } + fun setDefaultSpringConfig(defaultSpring: SpringConfig) { + this.defaultSpring = defaultSpring + } + + fun setDefaultFlingConfig(defaultFling: FlingConfig) { + this.defaultFling = defaultFling + } + /** Starts the animations! */ fun start() { startAction() @@ -752,7 +782,7 @@ class PhysicsAnimator<T> private constructor (val target: T) { ) { constructor() : - this(defaultSpring.stiffness, defaultSpring.dampingRatio) + this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio) constructor(stiffness: Float, dampingRatio: Float) : this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f) @@ -782,10 +812,10 @@ class PhysicsAnimator<T> private constructor (val target: T) { internal var startVelocity: Float ) { - constructor() : this(defaultFling.friction) + constructor() : this(globalDefaultFling.friction) constructor(friction: Float) : - this(friction, defaultFling.min, defaultFling.max) + this(friction, globalDefaultFling.min, globalDefaultFling.max) constructor(friction: Float, min: Float, max: Float) : this(friction, min, max, startVelocity = 0f) diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java index cc6d607a60cf..8acfbf2b6996 100644 --- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java @@ -137,6 +137,36 @@ public abstract class ConcurrencyModule { } /** + * Provide a Background-Thread Executor by default. + */ + @Provides + @Singleton + public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** + * Provide a Background-Thread Executor. + */ + @Provides + @Singleton + @Background + public static RepeatableExecutor provideBackgroundRepeatableExecutor( + @Background DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** + * Provide a Main-Thread Executor. + */ + @Provides + @Singleton + @Main + public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) { + return new RepeatableExecutorImpl(exec); + } + + /** * Provide an Executor specifically for running UI operations on a separate thread. * * Keep submitted runnables short and to the point, just as with any other UI code. diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java new file mode 100644 index 000000000000..aefdc992e831 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +/** + * A sub-class of {@link Executor} that allows scheduling commands to execute periodically. + */ +public interface RepeatableExecutor extends Executor { + + /** + * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with + * the given delay between the termination of one execution and the commencement of the next. + * + * Each invocation of the supplied Runnable will be scheduled after the previous invocation + * completes. For example, if you schedule the Runnable with a 60 second delay, and the Runnable + * itself takes 1 second, the effective delay will be 61 seconds between each invocation. + * + * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable, + * long, long)} + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + default Runnable executeRepeatedly(Runnable r, long initialDelayMillis, long delayMillis) { + return executeRepeatedly(r, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS); + } + + /** + * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with + * the given delay between the termination of one execution and the commencement of the next.. + * + * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable, + * long, long)} + * + * @return A Runnable that, when run, removes the supplied argument from the Executor queue. + */ + Runnable executeRepeatedly(Runnable r, long initialDelay, long delay, TimeUnit unit); +} diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java new file mode 100644 index 000000000000..c03e10e5c981 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import java.util.concurrent.TimeUnit; + +/** + * Implementation of {@link RepeatableExecutor} for SystemUI. + */ +class RepeatableExecutorImpl implements RepeatableExecutor { + + private final DelayableExecutor mExecutor; + + RepeatableExecutorImpl(DelayableExecutor executor) { + mExecutor = executor; + } + + @Override + public void execute(Runnable command) { + mExecutor.execute(command); + } + + @Override + public Runnable executeRepeatedly(Runnable r, long initDelay, long delay, TimeUnit unit) { + ExecutionToken token = new ExecutionToken(r, delay, unit); + token.start(initDelay, unit); + return token::cancel; + } + + private class ExecutionToken implements Runnable { + private final Runnable mCommand; + private final long mDelay; + private final TimeUnit mUnit; + private final Object mLock = new Object(); + private Runnable mCancel; + + ExecutionToken(Runnable r, long delay, TimeUnit unit) { + mCommand = r; + mDelay = delay; + mUnit = unit; + } + + @Override + public void run() { + mCommand.run(); + synchronized (mLock) { + if (mCancel != null) { + mCancel = mExecutor.executeDelayed(this, mDelay, mUnit); + } + } + } + + /** Starts execution that will repeat the command until {@link cancel}. */ + public void start(long startDelay, TimeUnit unit) { + synchronized (mLock) { + mCancel = mExecutor.executeDelayed(this, startDelay, unit); + } + } + + /** Cancel repeated execution of command. */ + public void cancel() { + synchronized (mLock) { + if (mCancel != null) { + mCancel.run(); + mCancel = null; + } + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 7403a11fecbf..6c00ecacf97d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -61,6 +61,7 @@ import android.os.UserManager; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; @@ -133,20 +134,23 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock - private Executor mBackgroundExecutor; - @Mock private RingerModeTracker mRingerModeTracker; @Mock private LiveData<Integer> mRingerModeLiveData; + @Mock + private TelephonyManager mTelephonyManager; + // Direct executor + private Executor mBackgroundExecutor = Runnable::run; private TestableLooper mTestableLooper; private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor; + private TestableContext mSpiedContext; @Before public void setup() { MockitoAnnotations.initMocks(this); - TestableContext context = spy(mContext); + mSpiedContext = spy(mContext); when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true); - when(context.getPackageManager()).thenReturn(mPackageManager); + when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager); doAnswer(invocation -> { IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0); callback.onChanged(BiometricSourceType.FACE, true /* enabled */, @@ -161,19 +165,20 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { when(mStrongAuthTracker .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */)) .thenReturn(true); - context.addMockSystemService(TrustManager.class, mTrustManager); - context.addMockSystemService(FingerprintManager.class, mFingerprintManager); - context.addMockSystemService(BiometricManager.class, mBiometricManager); - context.addMockSystemService(FaceManager.class, mFaceManager); - context.addMockSystemService(UserManager.class, mUserManager); - context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); - context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager); + mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager); + mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager); + mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager); + mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager); + mSpiedContext.addMockSystemService(UserManager.class, mUserManager); + mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager); + mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager); + mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager); when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); mTestableLooper = TestableLooper.get(this); allowTestableLooperAsMainThread(); - mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context); + mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext); } @After @@ -192,6 +197,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @Test + public void testSimStateInitialized() { + final int subId = 3; + final int state = TelephonyManager.SIM_STATE_ABSENT; + + when(mTelephonyManager.getActiveModemCount()).thenReturn(1); + when(mTelephonyManager.getSimState(anyInt())).thenReturn(state); + when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[] { subId }); + + KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext); + + mTestableLooper.processAllMessages(); + + assertThat(testKUM.getSimState(subId)).isEqualTo(state); + } + + @Test public void testIgnoresSimStateCallback_rebroadcast() { Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index 9e18e61229c3..3ef693a1ec98 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -85,6 +85,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -183,6 +184,8 @@ public class BubbleControllerTest extends SysuiTestCase { private DumpManager mDumpManager; @Mock private LockscreenLockIconController mLockIconController; + @Mock + private NotificationShadeWindowView mNotificationShadeWindowView; private SuperStatusBarViewFactory mSuperStatusBarViewFactory; private BubbleData mBubbleData; @@ -219,8 +222,7 @@ public class BubbleControllerTest extends SysuiTestCase { mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardBypassController, mColorExtractor, mDumpManager); - mNotificationShadeWindowController.setNotificationShadeView( - mSuperStatusBarViewFactory.getNotificationShadeWindowView()); + mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); // Need notifications for bubbles diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 49236e0275e0..8e6fc8a24b31 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -79,6 +79,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.NotificationShadeWindowController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -134,6 +135,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { private KeyguardBypassController mKeyguardBypassController; @Mock private FloatingContentCoordinator mFloatingContentCoordinator; + @Mock + private NotificationShadeWindowView mNotificationShadeWindowView; private SysUiState mSysUiState = new SysUiState(); @@ -206,8 +209,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mKeyguardBypassController, mColorExtractor, mDumpManager); - mNotificationShadeWindowController.setNotificationShadeView( - mSuperStatusBarViewFactory.getNotificationShadeWindowView()); + mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); // Need notifications for bubbles diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index f6ee46b0303a..e3f25c6b0c72 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -89,6 +89,7 @@ class ControlsControllerImplTest : SysuiTestCase() { @Captor private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<ControlsBindingController.LoadCallback> + @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> @Captor @@ -97,6 +98,7 @@ class ControlsControllerImplTest : SysuiTestCase() { private lateinit var delayableExecutor: FakeExecutor private lateinit var controller: ControlsControllerImpl + private lateinit var canceller: DidRunRunnable companion object { fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() @@ -146,6 +148,9 @@ class ControlsControllerImplTest : SysuiTestCase() { } } + canceller = DidRunRunnable() + `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller) + controller = ControlsControllerImpl( wrapper, delayableExecutor, @@ -266,7 +271,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertTrue(favorites.isEmpty()) assertFalse(data.errorOnLoad) - }) + }, Consumer {}) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -301,7 +306,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, favorites.size) assertEquals(TEST_CONTROL_ID, favorites[0]) assertFalse(data.errorOnLoad) - }) + }, Consumer {}) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -332,7 +337,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, favorites.size) assertEquals(TEST_CONTROL_ID, favorites[0]) assertFalse(data.errorOnLoad) - }) + }, Consumer {}) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -363,7 +368,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(1, favorites.size) assertEquals(TEST_CONTROL_ID, favorites[0]) assertTrue(data.errorOnLoad) - }) + }, Consumer {}) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -377,22 +382,15 @@ class ControlsControllerImplTest : SysuiTestCase() { @Test fun testCancelLoad() { - val canceller = object : Runnable { - var ran = false - override fun run() { - ran = true - } - } - `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller) - var loaded = false + var cancelRunnable: Runnable? = null controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) delayableExecutor.runAllReady() controller.loadForComponent(TEST_COMPONENT, Consumer { loaded = true - }) + }, Consumer { runnable -> cancelRunnable = runnable }) - controller.cancelLoad() + cancelRunnable?.run() delayableExecutor.runAllReady() assertFalse(loaded) @@ -400,61 +398,47 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test - fun testCancelLoad_noCancelAfterSuccessfulLoad() { - val canceller = object : Runnable { - var ran = false - override fun run() { - ran = true - } - } - `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller) - + fun testCancelLoad_afterSuccessfulLoad() { var loaded = false + var cancelRunnable: Runnable? = null controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) delayableExecutor.runAllReady() controller.loadForComponent(TEST_COMPONENT, Consumer { loaded = true - }) + }, Consumer { runnable -> cancelRunnable = runnable }) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.accept(emptyList()) - controller.cancelLoad() + cancelRunnable?.run() delayableExecutor.runAllReady() assertTrue(loaded) - assertFalse(canceller.ran) + assertTrue(canceller.ran) } @Test - fun testCancelLoad_noCancelAfterErrorLoad() { - val canceller = object : Runnable { - var ran = false - override fun run() { - ran = true - } - } - `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller) - + fun testCancelLoad_afterErrorLoad() { var loaded = false + var cancelRunnable: Runnable? = null controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO) delayableExecutor.runAllReady() controller.loadForComponent(TEST_COMPONENT, Consumer { loaded = true - }) + }, Consumer { runnable -> cancelRunnable = runnable }) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) controlLoadCallbackCaptor.value.error("") - controller.cancelLoad() + cancelRunnable?.run() delayableExecutor.runAllReady() assertTrue(loaded) - assertFalse(canceller.ran) + assertTrue(canceller.ran) } @Test @@ -465,7 +449,7 @@ class ControlsControllerImplTest : SysuiTestCase() { val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2) val control = statelessBuilderFromInfo(newControlInfo).build() - controller.loadForComponent(TEST_COMPONENT, Consumer {}) + controller.loadForComponent(TEST_COMPONENT, Consumer {}, Consumer {}) verify(bindingController).bindAndLoad(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) @@ -963,3 +947,10 @@ class ControlsControllerImplTest : SysuiTestCase() { assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty()) } } + +private class DidRunRunnable() : Runnable { + var ran = false + override fun run() { + ran = true + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index e8e98b49c47f..4ac5912d0690 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -133,4 +133,12 @@ public class ScreenRecordTileTest extends SysuiTestCase { verify(mController, times(1)).stopRecording(); } + + @Test + public void testContentDescriptionHasTileName() { + mTile.refreshState(); + mTestableLooper.processAllMessages(); + + assertTrue(mTile.getState().contentDescription.toString().contains(mTile.getState().label)); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java index 11649ca34709..ffe3cd5bd504 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java @@ -16,6 +16,10 @@ package com.android.systemui.screenshot; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; @@ -40,7 +44,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,9 +85,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { CompletableFuture<List<Notification.Action>> smartActionsFuture = ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, smartActionsProvider, true, false); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests any exception thrown in waiting for smart actions future to complete does @@ -99,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { RuntimeException.class); List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions( "", "", smartActionsFuture, timeoutMs, mSmartActionsProvider); - Assert.assertEquals(Collections.emptyList(), actions); + assertEquals(Collections.emptyList(), actions); } // Tests any exception thrown in notifying feedback does not affect regular screenshot flow. @@ -123,9 +126,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { mSmartActionsProvider, true, true); verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), eq(false)); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(Collections.emptyList(), smartActions); + assertEquals(Collections.emptyList(), smartActions); } // Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once. @@ -152,14 +155,14 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap, actionsProvider, true, true); - Assert.assertNotNull(smartActionsFuture); + assertNotNull(smartActionsFuture); List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS); - Assert.assertEquals(smartActions.size(), 0); + assertEquals(smartActions.size(), 0); } - // Tests for notification action extras. + // Tests for share action extras @Test - public void testNotificationActionExtras() { + public void testShareActionExtras() { if (Looper.myLooper() == null) { Looper.prepare(); } @@ -169,35 +172,70 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); data.finisher = null; data.mActionsReadyListener = null; - data.createDeleteAction = true; SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); - List<Notification.Action> actions = task.populateNotificationActions( - mContext, mContext.getResources(), + + Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(), Uri.parse("Screenshot_123.png")); - Assert.assertEquals(actions.size(), 3); - boolean isShareFound = false; - boolean isEditFound = false; - boolean isDeleteFound = false; - for (Notification.Action action : actions) { - Intent intent = action.actionIntent.getIntent(); - Assert.assertNotNull(intent); - Bundle bundle = intent.getExtras(); - Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); - Assert.assertTrue( - bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); - - if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) { - isDeleteFound = intent.getAction() == null; - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) { - isEditFound = Intent.ACTION_EDIT.equals(intent.getAction()); - } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) { - isShareFound = Intent.ACTION_SEND.equals(intent.getAction()); - } + Intent intent = shareAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title); + assertEquals(Intent.ACTION_SEND, intent.getAction()); + } + + // Tests for edit action extras + @Test + public void testEditActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); } - Assert.assertTrue(isEditFound); - Assert.assertTrue(isDeleteFound); - Assert.assertTrue(isShareFound); + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = editAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(GlobalScreenshot.ACTION_TYPE_EDIT, editAction.title); + assertEquals(Intent.ACTION_EDIT, intent.getAction()); + } + + // Tests for share action extras + @Test + public void testDeleteActionExtras() { + if (Looper.myLooper() == null) { + Looper.prepare(); + } + + GlobalScreenshot.SaveImageInBackgroundData + data = new GlobalScreenshot.SaveImageInBackgroundData(); + data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888); + data.finisher = null; + data.mActionsReadyListener = null; + SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data); + + Notification.Action deleteAction = task.createDeleteAction(mContext, + mContext.getResources(), + Uri.parse("Screenshot_123.png")); + + Intent intent = deleteAction.actionIntent.getIntent(); + assertNotNull(intent); + Bundle bundle = intent.getExtras(); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID)); + assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED)); + assertEquals(deleteAction.title, GlobalScreenshot.ACTION_TYPE_DELETE); + assertNull(intent.getAction()); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt index 2b091f297184..210744eb30cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt @@ -6,11 +6,18 @@ import android.view.LayoutInflater import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.assist.AssistManager +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.policy.AccessibilityController +import com.android.systemui.statusbar.policy.FlashlightController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.tuner.TunerService import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations +import java.util.concurrent.Executor @SmallTest @RunWith(AndroidTestingRunner::class) @@ -24,6 +31,15 @@ class KeyguardBottomAreaTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) + // Mocked dependencies + mDependency.injectMockDependency(AccessibilityController::class.java) + mDependency.injectMockDependency(ActivityStarter::class.java) + mDependency.injectMockDependency(AssistManager::class.java) + mDependency.injectTestDependency(Executor::class.java, Executor { it.run() }) + mDependency.injectMockDependency(FlashlightController::class.java) + mDependency.injectMockDependency(KeyguardStateController::class.java) + mDependency.injectMockDependency(TunerService::class.java) + mKeyguardBottomArea = LayoutInflater.from(mContext).inflate( R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView mKeyguardBottomArea.setStatusBar(mStatusBar) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 8ab660c3fca3..3e469073694f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -56,7 +56,7 @@ public class LightBarControllerTest extends SysuiTestCase { when(mStatusBarIconController.getTransitionsController()).thenReturn( mLightBarTransitionsController); mLightBarController = new LightBarController(mContext, mStatusBarIconController, - mock(BatteryController.class)); + mock(BatteryController.class), mock(NavigationModeController.class)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java index 27a50027cfe2..14c6e9f9624d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -50,9 +51,11 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { mDependency.injectMockDependency(IWindowManager.class); mDependency.injectMockDependency(AssistManager.class); mDependency.injectMockDependency(OverviewProxyService.class); - mDependency.injectMockDependency(NavigationModeController.class); mDependency.injectMockDependency(StatusBarStateController.class); mDependency.injectMockDependency(KeyguardStateController.class); + doReturn(mContext) + .when(mDependency.injectMockDependency(NavigationModeController.class)) + .getCurrentUserContext(); NavigationBarView navBar = spy(new NavigationBarView(mContext, null)); when(navBar.getCurrentView()).thenReturn(navBar); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index dd28687e749c..1afe132f00a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -176,25 +176,43 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false); - mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder( - getContext(), mock(CommandQueue.class), () -> mAssistManager, - mEntryManager, mock(HeadsUpManagerPhone.class), - mActivityStarter, mStatusBarService, - mock(StatusBarStateController.class), mStatusBarKeyguardViewManager, - mock(KeyguardManager.class), - mock(IDreamManager.class), mRemoteInputManager, - mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class), - mock(NotificationLockscreenUserManager.class), - mKeyguardStateController, - mock(NotificationInterruptStateProvider.class), mock(MetricsLogger.class), - mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor, - mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags, - mNotifPipeline, mNotifCollection) + mNotificationActivityStarter = + new StatusBarNotificationActivityStarter.Builder( + getContext(), + mock(CommandQueue.class), + mHandler, + mHandler, + mUiBgExecutor, + mEntryManager, + mNotifPipeline, + mNotifCollection, + mock(HeadsUpManagerPhone.class), + mActivityStarter, + mStatusBarService, + mock(StatusBarStateController.class), + mStatusBarKeyguardViewManager, + mock(KeyguardManager.class), + mock(IDreamManager.class), + mBubbleController, + () -> mAssistManager, + mRemoteInputManager, + mock(NotificationGroupManager.class), + mock(NotificationLockscreenUserManager.class), + mShadeController, + mKeyguardStateController, + mock(NotificationInterruptStateProvider.class), + mock(LockPatternUtils.class), + mock(StatusBarRemoteInputCallback.class), + mActivityIntentHelper, + + mFeatureFlags, + mock(MetricsLogger.class), + mock(StatusBarNotificationActivityStarterLogger.class)) .setStatusBar(mStatusBar) - .setNotificationPanelViewController(mock(NotificationPanelViewController.class)) .setNotificationPresenter(mock(NotificationPresenter.class)) - .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))) - .build(); + .setNotificationPanelViewController(mock(NotificationPanelViewController.class)) + .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)) + .build(); // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java index 962d77366875..aef454fc1374 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Instrumentation; @@ -222,7 +223,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackArg = ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class); - Mockito.verify(mMockCm, atLeastOnce()) + verify(mMockCm, atLeastOnce()) .registerDefaultNetworkCallback(callbackArg.capture(), isA(Handler.class)); mNetworkCallback = callbackArg.getValue(); assertNotNull(mNetworkCallback); @@ -384,7 +385,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { } protected void verifyHasNoSims(boolean hasNoSimsVisible) { - Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims( + verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims( eq(hasNoSimsVisible), eq(false)); } @@ -395,7 +396,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class); ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class); - Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( + verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( any(), iconArg.capture(), anyInt(), @@ -429,7 +430,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class); // TODO: Verify all fields. - Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( + verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( iconArg.capture(), any(), typeIconArg.capture(), @@ -475,7 +476,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { ArgumentCaptor<CharSequence> typeContentDescriptionHtmlArg = ArgumentCaptor.forClass(CharSequence.class); - Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( + verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators( iconArg.capture(), qsIconArg.capture(), typeIconArg.capture(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index 9c250c5e8e16..988e02203843 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -2,12 +2,14 @@ package com.android.systemui.statusbar.policy; import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Intent; +import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.net.wifi.WifiInfo; @@ -171,6 +173,32 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]); } + @Test + public void testFetchInitialData() { + mNetworkController.mWifiSignalController.fetchInitialState(); + Mockito.verify(mMockWm).getWifiState(); + Mockito.verify(mMockCm).getNetworkInfo(ConnectivityManager.TYPE_WIFI); + } + + @Test + public void testFetchInitialData_correctValues() { + String testSsid = "TEST"; + + when(mMockWm.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED); + NetworkInfo networkInfo = mock(NetworkInfo.class); + when(networkInfo.isConnected()).thenReturn(true); + when(mMockCm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)).thenReturn(networkInfo); + WifiInfo wifiInfo = mock(WifiInfo.class); + when(wifiInfo.getSSID()).thenReturn(testSsid); + when(mMockWm.getConnectionInfo()).thenReturn(wifiInfo); + + mNetworkController.mWifiSignalController.fetchInitialState(); + + assertTrue(mNetworkController.mWifiSignalController.mCurrentState.enabled); + assertTrue(mNetworkController.mWifiSignalController.mCurrentState.connected); + assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid); + } + protected void setWifiActivity(int activity) { // TODO: Not this, because this variable probably isn't sticking around. mNetworkController.mWifiSignalController.setActivity(activity); diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java new file mode 100644 index 000000000000..00f37ae6f6cb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class RepeatableExecutorTest extends SysuiTestCase { + + private static final int DELAY = 100; + + private FakeSystemClock mFakeClock; + private FakeExecutor mFakeExecutor; + private RepeatableExecutor mExecutor; + private CountingTask mCountingTask; + + @Before + public void setUp() throws Exception { + mFakeClock = new FakeSystemClock(); + mFakeExecutor = new FakeExecutor(mFakeClock); + mCountingTask = new CountingTask(); + mExecutor = new RepeatableExecutorImpl(mFakeExecutor); + } + + /** + * Test FakeExecutor that receives non-delayed items to execute. + */ + @Test + public void testExecute() { + mExecutor.execute(mCountingTask); + mFakeExecutor.runAllReady(); + assertThat(mCountingTask.getCount()).isEqualTo(1); + } + + @Test + public void testRepeats() { + // GIVEN that a command is queued to repeat + mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + // WHEN The clock advances and the task is run + mFakeExecutor.advanceClockToNext(); + mFakeExecutor.runAllReady(); + // THEN another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testNoExecutionBeforeStartDelay() { + // WHEN a command is queued with a start delay + mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY); + mFakeExecutor.runAllReady(); + // THEN then it doesn't run immediately + assertThat(mCountingTask.getCount()).isEqualTo(0); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testExecuteAfterStartDelay() { + // GIVEN that a command is queued to repeat with a longer start delay + mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY); + // WHEN the clock advances the start delay + mFakeClock.advanceTime(2 * DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testExecuteWithZeroStartDelay() { + // WHEN a command is queued with no start delay + mExecutor.executeRepeatedly(mCountingTask, 0L, DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testAdvanceTimeTwice() { + // GIVEN that a command is queued to repeat + mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + // WHEN the clock advances the time DELAY twice + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // THEN the command has run twice and another task is queued + assertThat(mCountingTask.getCount()).isEqualTo(2); + assertThat(mFakeExecutor.numPending()).isEqualTo(1); + } + + @Test + public void testCancel() { + // GIVEN that a scheduled command has been cancelled + Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + cancel.run(); + // WHEN the clock advances the time DELAY + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // THEN the comamnd has not run and no further tasks are queued + assertThat(mCountingTask.getCount()).isEqualTo(0); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); + } + + @Test + public void testCancelAfterStart() { + // GIVEN that a command has reapeated a few times + Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY); + mFakeClock.advanceTime(DELAY); + mFakeExecutor.runAllReady(); + // WHEN cancelled and time advances + cancel.run(); + // THEN the command has only run the first time + assertThat(mCountingTask.getCount()).isEqualTo(1); + assertThat(mFakeExecutor.numPending()).isEqualTo(0); + } + + /** + * Runnable used for testing that counts the number of times run() is invoked. + */ + private static class CountingTask implements Runnable { + + private int mRunCount; + + @Override + public void run() { + mRunCount++; + } + + /** Gets the run count. */ + public int getCount() { + return mRunCount; + } + } +} diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 132b6927badd..da9bdf3262d5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1023,8 +1023,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ try { mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> { final ScreenshotGraphicBuffer screenshotBuffer = LocalServices - .getService(DisplayManagerInternal.class) - .screenshotWithoutSecureLayer(displayId); + .getService(DisplayManagerInternal.class).userScreenshot(displayId); if (screenshotBuffer != null) { sendScreenshotSuccess(screenshotBuffer, callback); } else { diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java index b9e3050275ad..a1fc3fa1857d 100644 --- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java +++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.app.RemoteAction; @@ -399,7 +401,8 @@ public class SystemActionPerformer { ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null) ? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext); screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN, - true, true, new Handler(Looper.getMainLooper()), null); + true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS, + new Handler(Looper.getMainLooper()), null); return true; } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index b27c5d54a6fb..35089d6f5de7 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -117,6 +117,7 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Function; /** * A session for a given activity. @@ -3079,19 +3080,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final boolean isWhitelisted = mService .isWhitelistedForAugmentedAutofillLocked(mComponentName); - final String historyItem = - "aug:id=" + id + " u=" + uid + " m=" + mode - + " a=" + ComponentName.flattenToShortString(mComponentName) - + " f=" + mCurrentViewId - + " s=" + remoteService.getComponentName() - + " w=" + isWhitelisted; - mService.getMaster().logRequestLocked(historyItem); - if (!isWhitelisted) { if (sVerbose) { Slog.v(TAG, "triggerAugmentedAutofillLocked(): " + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); } + logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), + mCurrentViewId, isWhitelisted, /*isInline*/null); return null; } @@ -3116,24 +3111,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId focusedId = mCurrentViewId; + final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback = + response -> { + synchronized (mLock) { + return mInlineSessionController.onInlineSuggestionsResponseLocked( + focusedId, response); + } + }; final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill = (inlineSuggestionsRequest) -> { - remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, - AutofillId.withoutSession(focusedId), - currentValue, inlineSuggestionsRequest, - /*inlineSuggestionsCallback=*/ - response -> { - synchronized (mLock) { - return mInlineSessionController - .onInlineSuggestionsResponseLocked( - focusedId, response); - } - }, - /*onErrorCallback=*/ () -> { - synchronized (mLock) { - cancelAugmentedAutofillLocked(); - } - }, mService.getRemoteInlineSuggestionRenderServiceLocked()); + synchronized (mLock) { + logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), + focusedId, isWhitelisted, inlineSuggestionsRequest != null); + remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, + AutofillId.withoutSession(focusedId), currentValue, + inlineSuggestionsRequest, inlineSuggestionsResponseCallback, + /*onErrorCallback=*/ () -> { + synchronized (mLock) { + cancelAugmentedAutofillLocked(); + } + }, mService.getRemoteInlineSuggestionRenderServiceLocked()); + } }; // When the inline suggestion render service is available, there are 2 cases when @@ -3150,9 +3148,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( (extras) -> { - mInlineSessionController.onCreateInlineSuggestionsRequestLocked( - focusedId, /*requestConsumer=*/ requestAugmentedAutofill, - extras); + synchronized (mLock) { + mInlineSessionController.onCreateInlineSuggestionsRequestLocked( + focusedId, /*requestConsumer=*/ requestAugmentedAutofill, + extras); + } }, mHandler)); } else { requestAugmentedAutofill.accept( @@ -3165,6 +3165,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") + private void logAugmentedAutofillRequestLocked(int mode, + ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, + Boolean isInline) { + final String historyItem = + "aug:id=" + id + " u=" + uid + " m=" + mode + + " a=" + ComponentName.flattenToShortString(mComponentName) + + " f=" + focusedId + + " s=" + augmentedRemoteServiceName + + " w=" + isWhitelisted + + " i=" + isInline; + mService.getMaster().logRequestLocked(historyItem); + } + + @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { final RemoteAugmentedAutofillService remoteService = mService .getRemoteAugmentedAutofillServiceLocked(); diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 63b56e0f92d4..81de29c4ee4d 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -983,4 +983,9 @@ public abstract class PackageManagerInternal { * Returns if a package name is a valid system package. */ public abstract boolean isSystemPackage(@NonNull String packageName); + + /** + * Unblocks uninstall for all packages for the user. + */ + public abstract void clearBlockUninstallForUser(@UserIdInt int userId); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index ce651100699f..4009cafd04fb 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -603,8 +603,7 @@ class AlarmManagerService extends SystemService { } pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); pw.print("="); - TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_QUOTA, pw); - pw.println(); + pw.println(APP_STANDBY_RESTRICTED_QUOTA); pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("="); TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 2bbf27849005..97a5cfe6006d 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -1146,7 +1146,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { && registrationLimit >= 1 && numRecordsForPid >= registrationLimit) { String errorMsg = "Pid " + callingPid + " has exceeded the number of permissible" - + "registered listeners. Ignoring request to add."; + + " registered listeners. Ignoring request to add."; loge(errorMsg); if (mConfigurationProvider .isRegistrationLimitEnabledInPlatformCompat(callingUid)) { @@ -1157,7 +1157,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { // Log the warning independently of the dynamically set limit -- apps shouldn't be // doing this regardless of whether we're throwing them an exception for it. Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible" - + "registered listeners. Now at " + numRecordsForPid); + + " registered listeners. Now at " + numRecordsForPid); } r = new Record(); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index dbad562c0271..b647818e3f7a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -1013,9 +1013,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override public void noteNetworkInterfaceType(String iface, int networkType) { enforceCallingPermission(); - synchronized (mStats) { - mStats.noteNetworkInterfaceTypeLocked(iface, networkType); - } + mStats.noteNetworkInterfaceType(iface, networkType); } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index b4f7cdbd5694..02d499fbd81f 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -28,6 +28,8 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOUL import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL; import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import android.Manifest; import android.annotation.NonNull; @@ -1363,8 +1365,7 @@ public final class DisplayManagerService extends SystemService { return null; } - private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId, - boolean captureSecureLayer) { + private SurfaceControl.ScreenshotGraphicBuffer systemScreenshotInternal(int displayId) { synchronized (mSyncRoot) { final IBinder token = getDisplayToken(displayId); if (token == null) { @@ -1376,15 +1377,42 @@ public final class DisplayManagerService extends SystemService { } final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); - if (captureSecureLayer) { - return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), - displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), - false /* useIdentityTransform */, 0 /* rotation */); - } else { - return SurfaceControl.screenshotToBuffer(token, new Rect(), - displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), - false /* useIdentityTransform */, 0 /* rotation */); + return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), + false /* useIdentityTransform */, 0 /* rotation */); + } + } + + private SurfaceControl.ScreenshotGraphicBuffer userScreenshotInternal(int displayId) { + synchronized (mSyncRoot) { + final IBinder token = getDisplayToken(displayId); + if (token == null) { + return null; + } + final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId); + if (logicalDisplay == null) { + return null; + } + + final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked(); + // Takes screenshot based on current device orientation. + final Display display = DisplayManagerGlobal.getInstance() + .getRealDisplay(displayId); + if (display == null) { + return null; } + final Point displaySize = new Point(); + display.getRealSize(displaySize); + + int rotation = displayInfo.rotation; + // TODO (b/153382624) : This workaround solution would be removed after + // SurfaceFlinger fixes the inconsistency with rotation direction issue. + if (rotation == ROTATION_90 || rotation == ROTATION_270) { + rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90; + } + + return SurfaceControl.screenshotToBuffer(token, new Rect(), displaySize.x, + displaySize.y, false /* useIdentityTransform */, rotation /* rotation */); } } @@ -2502,13 +2530,13 @@ public final class DisplayManagerService extends SystemService { } @Override - public SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId) { - return screenshotInternal(displayId, true); + public SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId) { + return systemScreenshotInternal(displayId); } @Override - public SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(int displayId) { - return screenshotInternal(displayId, false); + public SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId) { + return userScreenshotInternal(displayId); } @Override diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java index 1cf27ffd1903..cc7915cc3534 100644 --- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java +++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java @@ -20,90 +20,80 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.provider.Settings; -import android.telephony.CellInfo; -import android.telephony.CellInfoGsm; -import android.telephony.CellInfoLte; -import android.telephony.CellInfoWcdma; -import android.telephony.CellLocation; -import android.telephony.PhoneStateListener; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; +import android.util.Slog; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.SystemService; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** - * A service that listens to connectivity and SIM card changes and determines if the emergency mode - * should be enabled + * A service that listens to connectivity and SIM card changes and determines if the emergency + * affordance should be enabled. */ public class EmergencyAffordanceService extends SystemService { private static final String TAG = "EmergencyAffordanceService"; + private static final boolean DBG = false; - private static final int NUM_SCANS_UNTIL_ABORT = 4; + private static final String SERVICE_NAME = "emergency_affordance"; private static final int INITIALIZE_STATE = 1; - private static final int CELL_INFO_STATE_CHANGED = 2; - private static final int SUBSCRIPTION_CHANGED = 3; - /** - * Global setting, whether the last scan of the sim cards reveal that a sim was inserted that - * requires the emergency affordance. The value is a boolean (1 or 0). - * @hide + * @param arg1 slot Index + * @param arg2 0 + * @param obj ISO country code */ - private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before"; - - private final Context mContext; - private final ArrayList<Integer> mEmergencyCallMccNumbers; + private static final int NETWORK_COUNTRY_CHANGED = 2; + private static final int SUBSCRIPTION_CHANGED = 3; + private static final int UPDATE_AIRPLANE_MODE_STATUS = 4; - private final Object mLock = new Object(); + // Global Settings to override emergency affordance country ISO for debugging. + // Available only on debug build. The value is a country ISO string in lower case (eg. "us"). + private static final String EMERGENCY_AFFORDANCE_OVERRIDE_ISO = + "emergency_affordance_override_iso"; - private TelephonyManager mTelephonyManager; + private final Context mContext; + // Country ISOs that require affordance + private final ArrayList<String> mEmergencyCallCountryIsos; private SubscriptionManager mSubscriptionManager; - private boolean mEmergencyAffordanceNeeded; + private TelephonyManager mTelephonyManager; private MyHandler mHandler; - private int mScansCompleted; - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onCellInfoChanged(List<CellInfo> cellInfo) { - if (!isEmergencyAffordanceNeeded()) { - requestCellScan(); - } - } + private boolean mAnySimNeedsEmergencyAffordance; + private boolean mAnyNetworkNeedsEmergencyAffordance; + private boolean mEmergencyAffordanceNeeded; + private boolean mAirplaneModeEnabled; + private boolean mVoiceCapable; - @Override - public void onCellLocationChanged(CellLocation location) { - if (!isEmergencyAffordanceNeeded()) { - requestCellScan(); - } - } - }; - private BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() { + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (Settings.Global.getInt(context.getContentResolver(), - Settings.Global.AIRPLANE_MODE_ON, 0) == 0) { - startScanning(); - requestCellScan(); + if (TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED.equals(intent.getAction())) { + String countryCode = intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY); + int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, + SubscriptionManager.INVALID_SIM_SLOT_INDEX); + mHandler.obtainMessage( + NETWORK_COUNTRY_CHANGED, slotId, 0, countryCode).sendToTarget(); + } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) { + mHandler.obtainMessage(UPDATE_AIRPLANE_MODE_STATUS).sendToTarget(); } } }; - private boolean mSimNeedsEmergencyAffordance; - private boolean mNetworkNeedsEmergencyAffordance; - private boolean mVoiceCapable; - - private void requestCellScan() { - mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget(); - } private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener = new SubscriptionManager.OnSubscriptionsChangedListener() { @@ -116,207 +106,200 @@ public class EmergencyAffordanceService extends SystemService { public EmergencyAffordanceService(Context context) { super(context); mContext = context; - int[] numbers = context.getResources().getIntArray( - com.android.internal.R.array.config_emergency_mcc_codes); - mEmergencyCallMccNumbers = new ArrayList<>(numbers.length); - for (int i = 0; i < numbers.length; i++) { - mEmergencyCallMccNumbers.add(numbers[i]); + String[] isos = context.getResources().getStringArray( + com.android.internal.R.array.config_emergency_iso_country_codes); + mEmergencyCallCountryIsos = new ArrayList<>(isos.length); + for (String iso : isos) { + mEmergencyCallCountryIsos.add(iso); } - } - private void updateEmergencyAffordanceNeeded() { - synchronized (mLock) { - mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance || - mNetworkNeedsEmergencyAffordance); - Settings.Global.putInt(mContext.getContentResolver(), - Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, - mEmergencyAffordanceNeeded ? 1 : 0); - if (mEmergencyAffordanceNeeded) { - stopScanning(); + if (Build.IS_DEBUGGABLE) { + String overrideIso = Settings.Global.getString( + mContext.getContentResolver(), EMERGENCY_AFFORDANCE_OVERRIDE_ISO); + if (!TextUtils.isEmpty(overrideIso)) { + if (DBG) Slog.d(TAG, "Override ISO to " + overrideIso); + mEmergencyCallCountryIsos.clear(); + mEmergencyCallCountryIsos.add(overrideIso); } } } - private void stopScanning() { - synchronized (mLock) { - mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); - mScansCompleted = 0; - } - } - - private boolean isEmergencyAffordanceNeeded() { - synchronized (mLock) { - return mEmergencyAffordanceNeeded; - } - } - @Override public void onStart() { + if (DBG) Slog.i(TAG, "onStart"); + publishBinderService(SERVICE_NAME, new BinderService()); } @Override public void onBootPhase(int phase) { if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - mTelephonyManager = mContext.getSystemService(TelephonyManager.class); - mVoiceCapable = mTelephonyManager.isVoiceCapable(); - if (!mVoiceCapable) { - updateEmergencyAffordanceNeeded(); - return; - } - mSubscriptionManager = SubscriptionManager.from(mContext); - HandlerThread thread = new HandlerThread(TAG); - thread.start(); - mHandler = new MyHandler(thread.getLooper()); - mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget(); - startScanning(); - IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); - mContext.registerReceiver(mAirplaneModeReceiver, filter); - mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener); + if (DBG) Slog.i(TAG, "onBootPhase"); + handleThirdPartyBootPhase(); } } - private void startScanning() { - mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO - | PhoneStateListener.LISTEN_CELL_LOCATION); - } - /** Handler to do the heavier work on */ private class MyHandler extends Handler { - public MyHandler(Looper l) { super(l); } @Override public void handleMessage(Message msg) { + if (DBG) Slog.d(TAG, "handleMessage: " + msg.what); switch (msg.what) { case INITIALIZE_STATE: handleInitializeState(); break; - case CELL_INFO_STATE_CHANGED: - handleUpdateCellInfo(); + case NETWORK_COUNTRY_CHANGED: + final String countryIso = (String) msg.obj; + final int slotId = msg.arg1; + handleNetworkCountryChanged(countryIso, slotId); break; case SUBSCRIPTION_CHANGED: handleUpdateSimSubscriptionInfo(); break; + case UPDATE_AIRPLANE_MODE_STATUS: + handleUpdateAirplaneModeStatus(); + break; + default: + Slog.e(TAG, "Unexpected message received: " + msg.what); } } } private void handleInitializeState() { - if (handleUpdateSimSubscriptionInfo()) { - return; - } - if (handleUpdateCellInfo()) { + if (DBG) Slog.d(TAG, "handleInitializeState"); + handleUpdateAirplaneModeStatus(); + handleUpdateSimSubscriptionInfo(); + updateNetworkCountry(); + updateEmergencyAffordanceNeeded(); + } + + private void handleThirdPartyBootPhase() { + if (DBG) Slog.d(TAG, "handleThirdPartyBootPhase"); + mTelephonyManager = mContext.getSystemService(TelephonyManager.class); + mVoiceCapable = mTelephonyManager.isVoiceCapable(); + if (!mVoiceCapable) { + updateEmergencyAffordanceNeeded(); return; } - updateEmergencyAffordanceNeeded(); + + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = new MyHandler(thread.getLooper()); + + mSubscriptionManager = SubscriptionManager.from(mContext); + mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener); + + IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); + filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter); + + mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget(); + } + + private void handleUpdateAirplaneModeStatus() { + mAirplaneModeEnabled = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + if (DBG) Slog.d(TAG, "APM status updated to " + mAirplaneModeEnabled); } - private boolean handleUpdateSimSubscriptionInfo() { - boolean neededBefore = simNeededAffordanceBefore(); - boolean neededNow = neededBefore; + private void handleUpdateSimSubscriptionInfo() { List<SubscriptionInfo> activeSubscriptionInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); + if (DBG) Slog.d(TAG, "handleUpdateSimSubscriptionInfo: " + activeSubscriptionInfoList); if (activeSubscriptionInfoList == null) { - setSimNeedsEmergencyAffordance(neededNow); - return neededNow; + return; } + + boolean needsAffordance = false; for (SubscriptionInfo info : activeSubscriptionInfoList) { - int mcc = info.getMcc(); - if (mccRequiresEmergencyAffordance(mcc)) { - neededNow = true; + if (isoRequiresEmergencyAffordance(info.getCountryIso())) { + needsAffordance = true; break; - } else if (mcc != 0 && mcc != Integer.MAX_VALUE){ - // a Sim with a different mcc code was found - neededNow = false; - } - String simOperator = mTelephonyManager - .createForSubscriptionId(info.getSubscriptionId()).getSimOperator(); - mcc = 0; - if (simOperator != null && simOperator.length() >= 3) { - mcc = Integer.parseInt(simOperator.substring(0, 3)); - } - if (mcc != 0) { - if (mccRequiresEmergencyAffordance(mcc)) { - neededNow = true; - break; - } else { - // a Sim with a different mcc code was found - neededNow = false; - } } } - setSimNeedsEmergencyAffordance(neededNow); - return neededNow; + + mAnySimNeedsEmergencyAffordance = needsAffordance; + updateEmergencyAffordanceNeeded(); } - private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) { - if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) { - Settings.Global.putInt(mContext.getContentResolver(), - EMERGENCY_SIM_INSERTED_SETTING, - simNeedsEmergencyAffordance ? 1 : 0); + private void handleNetworkCountryChanged(String countryIso, int slotId) { + if (DBG) { + Slog.d(TAG, "handleNetworkCountryChanged: countryIso=" + countryIso + + ", slotId=" + slotId); } - if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) { - mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance; - updateEmergencyAffordanceNeeded(); + + if (TextUtils.isEmpty(countryIso) && mAirplaneModeEnabled) { + Slog.w(TAG, "Ignore empty countryIso report when APM is on."); + return; } - } - private boolean simNeededAffordanceBefore() { - return Settings.Global.getInt(mContext.getContentResolver(), - EMERGENCY_SIM_INSERTED_SETTING, 0) != 0; + updateNetworkCountry(); + + updateEmergencyAffordanceNeeded(); } - private boolean handleUpdateCellInfo() { - List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo(); - if (cellInfos == null) { - return false; - } - boolean stopScanningAfterScan = false; - for (CellInfo cellInfo : cellInfos) { - int mcc = 0; - if (cellInfo instanceof CellInfoGsm) { - mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc(); - } else if (cellInfo instanceof CellInfoLte) { - mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc(); - } else if (cellInfo instanceof CellInfoWcdma) { - mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc(); - } - if (mccRequiresEmergencyAffordance(mcc)) { - setNetworkNeedsEmergencyAffordance(true); - return true; - } else if (mcc != 0 && mcc != Integer.MAX_VALUE) { - // we found an mcc that isn't in the list, abort - stopScanningAfterScan = true; + private void updateNetworkCountry() { + boolean needsAffordance = false; + + final int activeModems = mTelephonyManager.getActiveModemCount(); + for (int i = 0; i < activeModems; i++) { + String countryIso = mTelephonyManager.getNetworkCountryIso(i); + if (DBG) Slog.d(TAG, "UpdateNetworkCountry: slotId=" + i + " countryIso=" + countryIso); + if (isoRequiresEmergencyAffordance(countryIso)) { + needsAffordance = true; + break; } } - if (stopScanningAfterScan) { - stopScanning(); - } else { - onCellScanFinishedUnsuccessful(); - } - setNetworkNeedsEmergencyAffordance(false); - return false; + + mAnyNetworkNeedsEmergencyAffordance = needsAffordance; + + updateEmergencyAffordanceNeeded(); } - private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) { - synchronized (mLock) { - mNetworkNeedsEmergencyAffordance = needsAffordance; - updateEmergencyAffordanceNeeded(); - } + private boolean isoRequiresEmergencyAffordance(String iso) { + return mEmergencyCallCountryIsos.contains(iso); } - private void onCellScanFinishedUnsuccessful() { - synchronized (mLock) { - mScansCompleted++; - if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) { - stopScanning(); - } + private void updateEmergencyAffordanceNeeded() { + if (DBG) { + Slog.d(TAG, "updateEmergencyAffordanceNeeded: mEmergencyAffordanceNeeded=" + + mEmergencyAffordanceNeeded + ", mVoiceCapable=" + mVoiceCapable + + ", mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance + + ", mAnyNetworkNeedsEmergencyAffordance=" + + mAnyNetworkNeedsEmergencyAffordance); + } + boolean lastAffordanceNeeded = mEmergencyAffordanceNeeded; + + mEmergencyAffordanceNeeded = mVoiceCapable + && (mAnySimNeedsEmergencyAffordance || mAnyNetworkNeedsEmergencyAffordance); + + if (lastAffordanceNeeded != mEmergencyAffordanceNeeded) { + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, + mEmergencyAffordanceNeeded ? 1 : 0); } } - private boolean mccRequiresEmergencyAffordance(int mcc) { - return mEmergencyCallMccNumbers.contains(mcc); + private void dumpInternal(IndentingPrintWriter ipw) { + ipw.println("EmergencyAffordanceService (dumpsys emergency_affordance) state:\n"); + ipw.println("mEmergencyAffordanceNeeded=" + mEmergencyAffordanceNeeded); + ipw.println("mVoiceCapable=" + mVoiceCapable); + ipw.println("mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance); + ipw.println("mAnyNetworkNeedsEmergencyAffordance=" + mAnyNetworkNeedsEmergencyAffordance); + ipw.println("mEmergencyCallCountryIsos=" + String.join(",", mEmergencyCallCountryIsos)); + } + + private final class BinderService extends Binder { + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + + dumpInternal(new IndentingPrintWriter(pw, " ")); + } } } diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 0d899974cf93..d8bf9edee2b7 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -16,6 +16,8 @@ package com.android.server.media; +import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE; +import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE; import static android.media.MediaRouter2Utils.getOriginalId; import static android.media.MediaRouter2Utils.getProviderId; @@ -247,6 +249,22 @@ class MediaRouter2ServiceImpl { } } + public void notifySessionHintsForCreatingSession(IMediaRouter2 router, + long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) { + Objects.requireNonNull(router, "router must not be null"); + Objects.requireNonNull(route, "route must not be null"); + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + notifySessionHintsForCreatingSessionLocked(uniqueRequestId, + router, route, sessionHints); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, MediaRoute2Info route) { Objects.requireNonNull(router, "router must not be null"); @@ -265,7 +283,6 @@ class MediaRouter2ServiceImpl { } } - public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId, MediaRoute2Info route) { Objects.requireNonNull(router, "router must not be null"); @@ -634,12 +651,30 @@ class MediaRouter2ServiceImpl { long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId); routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestCreateSessionOnHandler, + obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler, routerRecord.mUserRecord.mHandler, - uniqueRequestId, routerRecord, /* managerRecord= */ null, route, + uniqueRequestId, routerRecord, route, sessionHints)); } + private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId, + @NonNull IMediaRouter2 router, + @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) { + final IBinder binder = router.asBinder(); + final RouterRecord routerRecord = mAllRouterRecords.get(binder); + + if (routerRecord == null) { + Slog.w(TAG, "requestCreateSessionWithRouter2ByManagerRequestLocked: " + + "Ignoring unknown router."); + return; + } + + routerRecord.mUserRecord.mHandler.sendMessage( + obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler, + routerRecord.mUserRecord.mHandler, + uniqueRequestId, routerRecord, route, sessionHints)); + } + private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { final IBinder binder = router.asBinder(); @@ -826,12 +861,13 @@ class MediaRouter2ServiceImpl { } long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId); - //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints. + + // Before requesting to the provider, get session hints from the media router. + // As a return, media router will request to create a session. routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::requestCreateSessionOnHandler, + obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler, routerRecord.mUserRecord.mHandler, - uniqueRequestId, routerRecord, managerRecord, route, - /* sessionHints= */ null)); + uniqueRequestId, routerRecord, managerRecord, route)); } private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager, @@ -1149,7 +1185,6 @@ class MediaRouter2ServiceImpl { this, provider, uniqueRequestId, sessionInfo)); } - @Override public void onSessionUpdated(@NonNull MediaRoute2Provider provider, @NonNull RoutingSessionInfo sessionInfo) { @@ -1267,8 +1302,26 @@ class MediaRouter2ServiceImpl { return -1; } - private void requestCreateSessionOnHandler(long uniqueRequestId, - @NonNull RouterRecord routerRecord, @Nullable ManagerRecord managerRecord, + private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId, + @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord, + @NonNull MediaRoute2Info route) { + SessionCreationRequest request = + new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord); + mSessionCreationRequests.add(request); + + try { + routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route); + } catch (RemoteException ex) { + Slog.w(TAG, "requestGetSessionHintsOnHandler: " + + "Failed to request. Router probably died."); + mSessionCreationRequests.remove(request); + notifyRequestFailedToManager(managerRecord.mManager, + toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR); + } + } + + private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId, + @NonNull RouterRecord routerRecord, @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) { final MediaRoute2Provider provider = findProvider(route.getProviderId()); @@ -1281,13 +1334,50 @@ class MediaRouter2ServiceImpl { } SessionCreationRequest request = - new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord); + new SessionCreationRequest(routerRecord, uniqueRequestId, route, null); mSessionCreationRequests.add(request); provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName, route.getOriginalId(), sessionHints); } + private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId, + @NonNull RouterRecord routerRecord, + @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) { + SessionCreationRequest matchingRequest = null; + for (SessionCreationRequest request : mSessionCreationRequests) { + if (request.mUniqueRequestId == uniqueRequestId) { + matchingRequest = request; + break; + } + } + if (matchingRequest == null) { + Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: " + + "Ignoring an unknown request."); + return; + } + + if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) { + Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: " + + "The given route is different from the requested route."); + return; + } + + final MediaRoute2Provider provider = findProvider(route.getProviderId()); + if (provider == null) { + Slog.w(TAG, "Ignoring session creation request since no provider found for" + + " given route=" + route); + + mSessionCreationRequests.remove(matchingRequest); + notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager, + toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE); + return; + } + + provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName, + route.getOriginalId(), sessionHints); + } + // routerRecord can be null if the session is system's or RCN. private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord, @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index d6bf9fb4d9b5..bf2cc5e68fac 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -487,6 +487,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override + public void notifySessionHintsForCreatingSession(IMediaRouter2 router, + long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) { + mService2.notifySessionHintsForCreatingSession(router, + uniqueRequestId, route, sessionHints); + } + + // Binder call + @Override public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, MediaRoute2Info route) { mService2.selectRouteWithRouter2(router, sessionId, route); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index d432fc83b52a..3e6d6f5fe192 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -819,7 +819,9 @@ public class PreferencesHelper implements RankingConfig { } if (fromTargetApp) { channel.setLockscreenVisibility(r.visibility); - channel.setAllowBubbles(existing != null && existing.canBubble()); + channel.setAllowBubbles(existing != null + ? existing.getAllowBubbles() + : NotificationChannel.DEFAULT_ALLOW_BUBBLE); } clearLockedFieldsLocked(channel); channel.setImportanceLockedByOEM(r.oemLockedImportance); @@ -1704,7 +1706,7 @@ public class PreferencesHelper implements RankingConfig { if (original.canShowBadge() != update.canShowBadge()) { update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE); } - if (original.canBubble() != update.canBubble()) { + if (original.getAllowBubbles() != update.getAllowBubbles()) { update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE); } } diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java index 1d4843822931..96da649350b0 100644 --- a/services/core/java/com/android/server/notification/ShortcutHelper.java +++ b/services/core/java/com/android/server/notification/ShortcutHelper.java @@ -21,7 +21,6 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED; import android.annotation.NonNull; -import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; @@ -41,9 +40,18 @@ import java.util.List; /** * Helper for querying shortcuts. */ -class ShortcutHelper { +public class ShortcutHelper { private static final String TAG = "ShortcutHelper"; + private static final IntentFilter SHARING_FILTER = new IntentFilter(); + static { + try { + SHARING_FILTER.addDataType("*/*"); + } catch (IntentFilter.MalformedMimeTypeException e) { + Slog.e(TAG, "Bad mime type", e); + } + } + /** * Listener to call when a shortcut we're tracking has been removed. */ @@ -54,7 +62,6 @@ class ShortcutHelper { private LauncherApps mLauncherAppsService; private ShortcutListener mShortcutListener; private ShortcutServiceInternal mShortcutServiceInternal; - private IntentFilter mSharingFilter; // Key: packageName Value: <shortcutId, notifId> private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>(); @@ -122,12 +129,6 @@ class ShortcutHelper { ShortcutServiceInternal shortcutServiceInternal) { mLauncherAppsService = launcherApps; mShortcutListener = listener; - mSharingFilter = new IntentFilter(); - try { - mSharingFilter.addDataType("*/*"); - } catch (IntentFilter.MalformedMimeTypeException e) { - Slog.e(TAG, "Bad mime type", e); - } mShortcutServiceInternal = shortcutServiceInternal; } @@ -142,7 +143,21 @@ class ShortcutHelper { } /** - * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}. + * Returns whether the given shortcut info is a conversation shortcut. + */ + public static boolean isConversationShortcut( + ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal, + int callingUserId) { + if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) { + return false; + } + return mShortcutServiceInternal.isSharingShortcut(callingUserId, "android", + shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(), + SHARING_FILTER); + } + + /** + * Only returns shortcut info if it's found and if it's a conversation shortcut. */ ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) { if (mLauncherAppsService == null) { @@ -161,11 +176,7 @@ class ShortcutHelper { ShortcutInfo info = shortcuts != null && shortcuts.size() > 0 ? shortcuts.get(0) : null; - if (info == null || !info.isLongLived() || !info.isEnabled()) { - return null; - } - if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(), - "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) { + if (isConversationShortcut(info, mShortcutServiceInternal, user.getIdentifier())) { return info; } return null; diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 7c47cf0450d4..d36d038058f1 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -612,7 +612,7 @@ public class AppsFilter { } final int insert = ~loc; System.arraycopy(appIds, insert, buffer, 0, whitelistSize - insert); - appIds[insert] = existingUid; + appIds[insert] = existingAppId; System.arraycopy(buffer, 0, appIds, insert + 1, whitelistSize - insert); whitelistSize++; } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5447bcb246e0..7adafe3ed658 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -24721,6 +24721,14 @@ public class PackageManagerService extends IPackageManager.Stub return packageName.equals( PackageManagerService.this.ensureSystemPackageName(packageName)); } + + @Override + public void clearBlockUninstallForUser(@UserIdInt int userId) { + synchronized (mLock) { + mSettings.clearBlockUninstallLPw(userId); + mSettings.writePackageRestrictionsLPr(userId); + } + } } @GuardedBy("mLock") diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 44a61d895be5..ddeab29c5b78 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -1833,6 +1833,10 @@ public final class Settings { } } + void clearBlockUninstallLPw(int userId) { + mBlockUninstallPackages.remove(userId); + } + boolean getBlockUninstallLPr(int userId, String packageName) { ArraySet<String> packages = mBlockUninstallPackages.get(userId); if (packages == null) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index fc70af4e7bd4..4561d2e7a8b2 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -104,6 +104,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.FrameworkStatsLog; +import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; @@ -3153,13 +3154,17 @@ public class UserManagerService extends IUserManager.Stub { /** * Removes the app restrictions file for a specific package and user id, if it exists. + * + * @return whether there were any restrictions. */ - private static void cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) { - File dir = Environment.getUserSystemDirectory(userId); - File resFile = new File(dir, packageToRestrictionsFileName(pkg)); + private static boolean cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) { + final File dir = Environment.getUserSystemDirectory(userId); + final File resFile = new File(dir, packageToRestrictionsFileName(pkg)); if (resFile.exists()) { resFile.delete(); + return true; } + return false; } /** @@ -4003,17 +4008,24 @@ public class UserManagerService extends IUserManager.Stub { if (restrictions != null) { restrictions.setDefusable(true); } + final boolean changed; synchronized (mAppRestrictionsLock) { if (restrictions == null || restrictions.isEmpty()) { - cleanAppRestrictionsForPackageLAr(packageName, userId); + changed = cleanAppRestrictionsForPackageLAr(packageName, userId); } else { // Write the restrictions to XML writeApplicationRestrictionsLAr(packageName, restrictions, userId); + // TODO(b/154323615): avoid unnecessary broadcast when there is no change. + changed = true; } } + if (!changed) { + return; + } + // Notify package of changes via an intent - only sent to explicitly registered receivers. - Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + final Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); changeIntent.setPackage(packageName); changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId)); @@ -4505,6 +4517,8 @@ public class UserManagerService extends IUserManager.Stub { switch(cmd) { case "list": return runList(pw, shell); + case "list-missing-system-packages": + return runListMissingSystemPackages(pw, shell); default: return shell.handleDefaultCommands(cmd); } @@ -4571,6 +4585,30 @@ public class UserManagerService extends IUserManager.Stub { } } + private int runListMissingSystemPackages(PrintWriter pw, Shell shell) { + boolean verbose = false; + boolean force = false; + String opt; + while ((opt = shell.getNextOption()) != null) { + switch (opt) { + case "-v": + verbose = true; + break; + case "--force": + force = true; + break; + default: + pw.println("Invalid option: " + opt); + return -1; + } + } + + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose); + } + return 0; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -5143,6 +5181,9 @@ public class UserManagerService extends IUserManager.Stub { pw.println(""); pw.println(" list [-v] [-all]"); pw.println(" Prints all users on the system."); + pw.println(" list-missing-system-packages [-v] [--force]"); + pw.println(" Prints all system packages that were not explicitly configured to be " + + "installed."); } } diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java index 85c2306f6d89..cd1087f5fcd7 100644 --- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java +++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java @@ -22,15 +22,16 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; -import android.content.pm.PackageParser; import android.content.res.Resources; import android.os.SystemProperties; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.pm.parsing.pkg.AndroidPackage; @@ -38,7 +39,9 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Set; @@ -263,37 +266,85 @@ class UserSystemPackageInstaller { if (!isLogMode(mode) && !isEnforceMode(mode)) { return; } - Slog.v(TAG, "Checking that all system packages are whitelisted."); + final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); + final int size = warnings.size(); + if (size == 0) { + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings"); + return; + } + + if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { + // Only shows whether all whitelisted packages are indeed on the system. + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final boolean isSevere = pair.first; + if (!isSevere) { + final String msg = pair.second; + Slog.w(TAG, msg); + } + } + return; + } + + Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings"); + boolean doWtf = !isImplicitWhitelistMode(mode); + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final boolean isSevere = pair.first; + final String msg = pair.second; + if (isSevere) { + if (doWtf) { + Slog.wtf(TAG, msg); + } else { + Slog.e(TAG, msg); + } + } else { + Slog.w(TAG, msg); + } + } + } + + // TODO: method below was created to refactor the one-time logging logic so it can be used on + // dump / cmd as well. It could to be further refactored (for example, creating a new + // structure for the warnings so it doesn't need a Pair). + /** + * Gets warnings for system user whitelisting. + * + * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF, + * {@code false} for WARN) and {@code Pair.second} the message. + */ + @NonNull + private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings( + @PackageWhitelistMode int mode) { final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages(); - PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); + final List<Pair<Boolean, String>> warnings = new ArrayList<>(); + final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class); // Check whether all whitelisted packages are indeed on the system. + final String notPresentFmt = "%s is whitelisted but not present."; + final String notSystemFmt = "%s is whitelisted and present but not a system package."; for (String pkgName : allWhitelistedPackages) { - AndroidPackage pkg = pmInt.getPackage(pkgName); + final AndroidPackage pkg = pmInt.getPackage(pkgName); if (pkg == null) { - Slog.w(TAG, pkgName + " is whitelisted but not present."); + warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName))); } else if (!pkg.isSystem()) { - Slog.w(TAG, pkgName + " is whitelisted and present but not a system package."); + warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName))); } } // Check whether all system packages are indeed whitelisted. - if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) { - return; - } - final boolean doWtf = isEnforceMode(mode); + final String logMessageFmt = "System package %s is not whitelisted using " + + "'install-in-user-type' in SystemConfig for any user types!"; + final boolean isSevere = isEnforceMode(mode); pmInt.forEachPackage(pkg -> { - if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.getManifestPackageName())) { - final String msg = "System package " + pkg.getManifestPackageName() - + " is not whitelisted using 'install-in-user-type' in SystemConfig " - + "for any user types!"; - if (doWtf) { - Slog.wtf(TAG, msg); - } else { - Slog.e(TAG, msg); - } + if (!pkg.isSystem()) return; + final String pkgName = pkg.getManifestPackageName(); + if (!allWhitelistedPackages.contains(pkgName)) { + warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName))); } }); + + return warnings; } /** Whether to only install system packages in new users for which they are whitelisted. */ @@ -602,32 +653,45 @@ class UserSystemPackageInstaller { } void dump(PrintWriter pw) { - final String prefix = " "; - final String prefix2 = prefix + prefix; + try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) { + dumpIndented(ipw); + } + } + + private void dumpIndented(IndentingPrintWriter pw) { final int mode = getWhitelistMode(); pw.println("Whitelisted packages per user type"); - pw.print(prefix); pw.print("Mode: "); + + pw.increaseIndent(); + pw.print("Mode: "); pw.print(mode); pw.print(isEnforceMode(mode) ? " (enforced)" : ""); pw.print(isLogMode(mode) ? " (logged)" : ""); pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : ""); pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : ""); pw.println(); + pw.decreaseIndent(); - pw.print(prefix); pw.println("Legend"); + pw.increaseIndent(); + pw.println("Legend"); + pw.increaseIndent(); for (int idx = 0; idx < mUserTypes.length; idx++) { - pw.print(prefix2); pw.println(idx + " -> " + mUserTypes[idx]); + pw.println(idx + " -> " + mUserTypes[idx]); } + pw.decreaseIndent(); pw.decreaseIndent(); + pw.increaseIndent(); final int size = mWhitelistedPackagesForUserTypes.size(); if (size == 0) { - pw.print(prefix); pw.println("No packages"); + pw.println("No packages"); + pw.decreaseIndent(); return; } - pw.print(prefix); pw.print(size); pw.println(" packages:"); + pw.print(size); pw.println(" packages:"); + pw.increaseIndent(); for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) { final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx); - pw.print(prefix2); pw.print(pkgName); pw.print(": "); + pw.print(pkgName); pw.print(": "); final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx); for (int idx = 0; idx < mUserTypes.length; idx++) { if ((userTypesBitSet & (1 << idx)) != 0) { @@ -636,5 +700,40 @@ class UserSystemPackageInstaller { } pw.println(); } + pw.decreaseIndent(); pw.decreaseIndent(); + + pw.increaseIndent(); + dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true); + pw.decreaseIndent(); + } + + void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) { + final int mode = getWhitelistMode(); + final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode)); + if (!show) return; + + final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode); + final int size = warnings.size(); + + if (size == 0) { + if (verbose) { + pw.println("All system packages are accounted for"); + } + return; + } + + if (verbose) { + pw.print(size); pw.println(" warnings for system user:"); + pw.increaseIndent(); + } + for (int i = 0; i < size; i++) { + final Pair<Boolean, String> pair = warnings.get(i); + final String lvl = pair.first ? "WTF" : "WARN"; + final String msg = pair.second; + pw.print(lvl); pw.print(": "); pw.println(msg); + } + if (verbose) { + pw.decreaseIndent(); + } } } diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4624e9ea0209..2f84a99774f4 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -62,6 +62,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER; import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION; import static android.view.WindowManagerGlobal.ADD_OKAY; @@ -1328,6 +1330,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenshotChordVolumeDownKeyConsumed = true; cancelPendingPowerKeyAction(); mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD); mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay()); } } @@ -1411,14 +1414,19 @@ public class PhoneWindowManager implements WindowManagerPolicy { private class ScreenshotRunnable implements Runnable { private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN; + private int mScreenshotSource = SCREENSHOT_KEY_OTHER; public void setScreenshotType(int screenshotType) { mScreenshotType = screenshotType; } + public void setScreenshotSource(int screenshotSource) { + mScreenshotSource = screenshotSource; + } + @Override public void run() { - mDefaultDisplayPolicy.takeScreenshot(mScreenshotType); + mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource); } } @@ -2693,6 +2701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION : TAKE_SCREENSHOT_FULLSCREEN; mScreenshotRunnable.setScreenshotType(type); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); return -1; } @@ -2709,6 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } else if (keyCode == KeyEvent.KEYCODE_SYSRQ) { if (down && repeatCount == 0) { mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN); + mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER); mHandler.post(mScreenshotRunnable); } return -1; diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java index 1ab6adee2320..b69c45070487 100644 --- a/services/core/java/com/android/server/power/AttentionDetector.java +++ b/services/core/java/com/android/server/power/AttentionDetector.java @@ -17,7 +17,6 @@ package com.android.server.power; import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE; -import static android.provider.Settings.Secure.ADAPTIVE_SLEEP; import android.Manifest; import android.app.ActivityManager; @@ -192,9 +191,6 @@ public class AttentionDetector { } if (!isAttentionServiceSupported() || !serviceHasSufficientPermissions()) { - // Turns off adaptive sleep in settings for all users if attention service is not - // available. The setting itself should also be grayed out in this case. - Settings.Secure.putInt(mContentResolver, ADAPTIVE_SLEEP, 0); return nextScreenDimming; } diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 801d75b90a54..f6d46e24246c 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.RollbackInfo; @@ -41,10 +42,13 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FrameworkStatsLog; +import com.android.server.LocalServices; import com.android.server.PackageWatchdog; import com.android.server.PackageWatchdog.FailureReasons; import com.android.server.PackageWatchdog.PackageHealthObserver; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; +import com.android.server.pm.ApexManager; +import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.BufferedReader; import java.io.File; @@ -71,6 +75,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve private final Context mContext; private final Handler mHandler; + private final ApexManager mApexManager; private final File mLastStagedRollbackIdsFile; // Staged rollback ids that have been committed but their session is not yet ready @GuardedBy("mPendingStagedRollbackIds") @@ -85,6 +90,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve dataDir.mkdirs(); mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids"); PackageWatchdog.getInstance(mContext).registerHealthObserver(this); + mApexManager = ApexManager.getInstance(); } @Override @@ -302,6 +308,18 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve * Returns true if the package name is the name of a module. */ private boolean isModule(String packageName) { + // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when + // querying PackageManager. + PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class); + AndroidPackage apkPackage = pmi.getPackage(packageName); + if (apkPackage != null) { + String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage( + apkPackage); + if (apexPackageName != null) { + packageName = apexPackageName; + } + } + PackageManager pm = mContext.getPackageManager(); try { return pm.getModuleInfo(packageName, 0) != null; diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java new file mode 100644 index 000000000000..54ad1d268e56 --- /dev/null +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java @@ -0,0 +1,152 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.tv.tunerresourcemanager; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * A Cas resource object used by the Tuner Resource Manager to record the cas + * information. + * + * @hide + */ +public final class CasResource { + + private final int mSystemId; + + private int mMaxSessionNum; + + private int mAvailableSessionNum; + + /** + * The owner clients' ids when part of the Cas is occupied. + */ + private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>(); + + private CasResource(Builder builder) { + this.mSystemId = builder.mSystemId; + this.mMaxSessionNum = builder.mMaxSessionNum; + this.mAvailableSessionNum = builder.mMaxSessionNum; + } + + public int getSystemId() { + return mSystemId; + } + + public int getMaxSessionNum() { + return mMaxSessionNum; + } + + public int getUsedSessionNum() { + return (mMaxSessionNum - mAvailableSessionNum); + } + + public boolean isFullyUsed() { + return mAvailableSessionNum == 0; + } + + /** + * Update max session number. + * + * @param maxSessionNum the new max session num. + */ + public void updateMaxSessionNum(int maxSessionNum) { + mAvailableSessionNum = Math.max( + 0, mAvailableSessionNum + (maxSessionNum - mMaxSessionNum)); + mMaxSessionNum = maxSessionNum; + } + + /** + * Set an owner for the cas + * + * @param ownerId the client id of the owner. + */ + public void setOwner(int ownerId) { + int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId) == null + ? 1 : (mOwnerClientIdsToSessionNum.get(ownerId) + 1); + mOwnerClientIdsToSessionNum.put(ownerId, sessionNum); + mAvailableSessionNum--; + } + + /** + * Remove an owner of the Cas. + * + * @param ownerId the removing client id of the owner. + */ + public void removeOwner(int ownerId) { + mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId); + mOwnerClientIdsToSessionNum.remove(ownerId); + } + + public Set<Integer> getOwnerClientIds() { + return mOwnerClientIdsToSessionNum.keySet(); + } + + @Override + public String toString() { + return "CasResource[systemId=" + this.mSystemId + + ", isFullyUsed=" + (this.mAvailableSessionNum == 0) + + ", maxSessionNum=" + this.mMaxSessionNum + + ", ownerClients=" + ownersMapToString() + "]"; + } + + /** + * Builder class for {@link CasResource}. + */ + public static class Builder { + + private int mSystemId; + private int mMaxSessionNum; + + Builder(int systemId) { + this.mSystemId = systemId; + } + + /** + * Builder for {@link CasResource}. + * + * @param maxSessionNum the max session num the current Cas has. + */ + public Builder maxSessionNum(int maxSessionNum) { + this.mMaxSessionNum = maxSessionNum; + return this; + } + + /** + * Build a {@link CasResource}. + * + * @return {@link CasResource}. + */ + public CasResource build() { + CasResource cas = new CasResource(this); + return cas; + } + } + + private String ownersMapToString() { + StringBuilder string = new StringBuilder("{"); + for (int clienId : mOwnerClientIdsToSessionNum.keySet()) { + string.append(" clientId=") + .append(clienId) + .append(", owns session num=") + .append(mOwnerClientIdsToSessionNum.get(clienId)) + .append(","); + } + return string.append("}").toString(); + } +} diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java index 4cdc17292ac4..2b0fe8a2602b 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java @@ -27,6 +27,7 @@ import java.util.Set; public final class ClientProfile { public static final int INVALID_GROUP_ID = -1; + public static final int INVALID_RESOURCE_ID = -1; /** * Client id sent to the client when registering with @@ -56,7 +57,6 @@ public final class ClientProfile { * also lose their resources. */ private int mGroupId = INVALID_GROUP_ID; - /** * Optional nice value for TRM to reduce client’s priority. */ @@ -73,6 +73,11 @@ public final class ClientProfile { private Set<Integer> mUsingLnbIds = new HashSet<>(); /** + * List of the Cas system ids that are used by the current client. + */ + private int mUsingCasSystemId = INVALID_RESOURCE_ID; + + /** * Optional arbitrary priority value given by the client. * * <p>This value can override the default priorotiy calculated from @@ -172,11 +177,32 @@ public final class ClientProfile { } /** + * Set when the client starts to use a Cas system. + * + * @param casSystemId cas being used. + */ + public void useCas(int casSystemId) { + mUsingCasSystemId = casSystemId; + } + + public int getInUseCasSystemId() { + return mUsingCasSystemId; + } + + /** + * Called when the client released a Cas System. + */ + public void releaseCas() { + mUsingCasSystemId = INVALID_RESOURCE_ID; + } + + /** * Called to reclaim all the resources being used by the current client. */ public void reclaimAllResources() { mUsingFrontendIds.clear(); mUsingLnbIds.clear(); + mUsingCasSystemId = INVALID_RESOURCE_ID; } @Override diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java index 7231813dd949..2f70840cfc8b 100644 --- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java +++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java @@ -65,6 +65,8 @@ public class TunerResourceManagerService extends SystemService { private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>(); // Map of the current available lnb resources private Map<Integer, LnbResource> mLnbResources = new HashMap<>(); + // Map of the current available Cas resources + private Map<Integer, CasResource> mCasResources = new HashMap<>(); @GuardedBy("mLock") private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>(); @@ -158,10 +160,8 @@ public class TunerResourceManagerService extends SystemService { @Override public void updateCasInfo(int casSystemId, int maxSessionNum) { enforceTrmAccessPermission("updateCasInfo"); - if (DEBUG) { - Slog.d(TAG, - "updateCasInfo(casSystemId=" + casSystemId - + ", maxSessionNum=" + maxSessionNum + ")"); + synchronized (mLock) { + updateCasInfoInternal(casSystemId, maxSessionNum); } } @@ -185,11 +185,11 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("frontendHandle can't be null"); } synchronized (mLock) { - try { - return requestFrontendInternal(request, frontendHandle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request frontend from unregistered client:" + + request.getClientId()); } + return requestFrontendInternal(request, frontendHandle); } } @@ -211,32 +211,45 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("demuxHandle can't be null"); } synchronized (mLock) { + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request demux from unregistered client:" + + request.getClientId()); + } return requestDemuxInternal(request, demuxHandle); } } @Override public boolean requestDescrambler(@NonNull TunerDescramblerRequest request, - @NonNull int[] descrambleHandle) throws RemoteException { + @NonNull int[] descramblerHandle) throws RemoteException { enforceDescramblerAccessPermission("requestDescrambler"); enforceTrmAccessPermission("requestDescrambler"); - if (descrambleHandle == null) { - throw new RemoteException("descrambleHandle can't be null"); + if (descramblerHandle == null) { + throw new RemoteException("descramblerHandle can't be null"); } synchronized (mLock) { - return requestDescramblerInternal(request, descrambleHandle); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request descrambler from unregistered client:" + + request.getClientId()); + } + return requestDescramblerInternal(request, descramblerHandle); } } @Override - public boolean requestCasSession( - @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) { + public boolean requestCasSession(@NonNull CasSessionRequest request, + @NonNull int[] casSessionHandle) throws RemoteException { enforceTrmAccessPermission("requestCasSession"); - if (DEBUG) { - Slog.d(TAG, "requestCasSession(request=" + request + ")"); + if (casSessionHandle == null) { + throw new RemoteException("casSessionHandle can't be null"); + } + synchronized (mLock) { + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request cas from unregistered client:" + + request.getClientId()); + } + return requestCasSessionInternal(request, casSessionHandle); } - - return true; } @Override @@ -248,11 +261,11 @@ public class TunerResourceManagerService extends SystemService { throw new RemoteException("lnbHandle can't be null"); } synchronized (mLock) { - try { - return requestLnbInternal(request, lnbHandle); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); + if (!checkClientExists(request.getClientId())) { + throw new RemoteException("Request lnb from unregistered client:" + + request.getClientId()); } + return requestLnbInternal(request, lnbHandle); } } @@ -264,15 +277,20 @@ public class TunerResourceManagerService extends SystemService { frontendHandle)) { throw new RemoteException("frontendHandle can't be invalid"); } - int frontendId = getResourceIdFromHandle(frontendHandle); - FrontendResource fe = getFrontendResource(frontendId); - if (fe == null) { - throw new RemoteException("Releasing frontend does not exist."); - } - if (fe.getOwnerClientId() != clientId) { - throw new RemoteException("Client is not the current owner of the releasing fe."); - } synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release frontend from unregistered client:" + + clientId); + } + int frontendId = getResourceIdFromHandle(frontendHandle); + FrontendResource fe = getFrontendResource(frontendId); + if (fe == null) { + throw new RemoteException("Releasing frontend does not exist."); + } + if (fe.getOwnerClientId() != clientId) { + throw new RemoteException( + "Client is not the current owner of the releasing fe."); + } releaseFrontendInternal(fe); } } @@ -296,10 +314,26 @@ public class TunerResourceManagerService extends SystemService { } @Override - public void releaseCasSession(int sessionResourceId, int clientId) { + public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException { enforceTrmAccessPermission("releaseCasSession"); - if (DEBUG) { - Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")"); + if (!validateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) { + throw new RemoteException("casSessionHandle can't be invalid"); + } + synchronized (mLock) { + if (!checkClientExists(clientId)) { + throw new RemoteException("Release cas from unregistered client:" + clientId); + } + int casSystemId = getClientProfile(clientId).getInUseCasSystemId(); + CasResource cas = getCasResource(casSystemId); + if (cas == null) { + throw new RemoteException("Releasing cas does not exist."); + } + if (!cas.getOwnerClientIds().contains(clientId)) { + throw new RemoteException( + "Client is not the current owner of the releasing cas."); + } + releaseCasSessionInternal(cas, clientId); } } @@ -310,6 +344,9 @@ public class TunerResourceManagerService extends SystemService { if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) { throw new RemoteException("lnbHandle can't be invalid"); } + if (!checkClientExists(clientId)) { + throw new RemoteException("Release lnb from unregistered client:" + clientId); + } int lnbId = getResourceIdFromHandle(lnbHandle); LnbResource lnb = getLnbResource(lnbId); if (lnb == null) { @@ -465,17 +502,42 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) - throws RemoteException { + protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) { + if (DEBUG) { + Slog.d(TAG, + "updateCasInfo(casSystemId=" + casSystemId + + ", maxSessionNum=" + maxSessionNum + ")"); + } + // If maxSessionNum is 0, removing the Cas Resource. + if (maxSessionNum == 0) { + removeCasResource(casSystemId); + return; + } + // If the Cas exists, updates the Cas Resource accordingly. + CasResource cas = getCasResource(casSystemId); + if (cas != null) { + if (cas.getUsedSessionNum() > maxSessionNum) { + // Sort and release the short number of Cas resources. + int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum; + releaseLowerPriorityClientCasResources(releasingCasResourceNum); + } + cas.updateMaxSessionNum(maxSessionNum); + return; + } + // Add the new Cas Resource. + cas = new CasResource.Builder(casSystemId) + .maxSessionNum(maxSessionNum) + .build(); + addCasResource(cas); + } + + @VisibleForTesting + protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) { if (DEBUG) { Slog.d(TAG, "requestFrontend(request=" + request + ")"); } frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - if (!checkClientExists(request.getClientId())) { - Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId()); - return false; - } ClientProfile requestClient = getClientProfile(request.getClientId()); int grantingFrontendId = -1; int inUseLowestPriorityFrId = -1; @@ -496,7 +558,7 @@ public class TunerResourceManagerService extends SystemService { } else if (grantingFrontendId < 0) { // Record the frontend id with the lowest client priority among all the // in use frontends when no available frontend has been found. - int priority = getOwnerClientPriority(fr); + int priority = getOwnerClientPriority(fr.getOwnerClientId()); if (currentLowestPriority > priority) { inUseLowestPriorityFrId = fr.getId(); currentLowestPriority = priority; @@ -530,17 +592,12 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) - throws RemoteException { + protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) { if (DEBUG) { Slog.d(TAG, "requestLnb(request=" + request + ")"); } lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; - if (!checkClientExists(request.getClientId())) { - Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId()); - return false; - } ClientProfile requestClient = getClientProfile(request.getClientId()); int grantingLnbId = -1; int inUseLowestPriorityLnbId = -1; @@ -554,7 +611,7 @@ public class TunerResourceManagerService extends SystemService { } else { // Record the lnb id with the lowest client priority among all the // in use lnb when no available lnb has been found. - int priority = getOwnerClientPriority(lnb); + int priority = getOwnerClientPriority(lnb.getOwnerClientId()); if (currentLowestPriority > priority) { inUseLowestPriorityLnbId = lnb.getId(); currentLowestPriority = priority; @@ -588,7 +645,55 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - void releaseFrontendInternal(FrontendResource fe) { + protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) { + if (DEBUG) { + Slog.d(TAG, "requestCasSession(request=" + request + ")"); + } + CasResource cas = getCasResource(request.getCasSystemId()); + // Unregistered Cas System is treated as having unlimited sessions. + if (cas == null) { + cas = new CasResource.Builder(request.getCasSystemId()) + .maxSessionNum(Integer.MAX_VALUE) + .build(); + addCasResource(cas); + } + casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE; + ClientProfile requestClient = getClientProfile(request.getClientId()); + int lowestPriorityOwnerId = -1; + // Priority max value is 1000 + int currentLowestPriority = MAX_CLIENT_PRIORITY + 1; + if (!cas.isFullyUsed()) { + casSessionHandle[0] = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); + updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId()); + return true; + } + for (int ownerId : cas.getOwnerClientIds()) { + // Record the client id with lowest priority that is using the current Cas system. + int priority = getOwnerClientPriority(ownerId); + if (currentLowestPriority > priority) { + lowestPriorityOwnerId = ownerId; + currentLowestPriority = priority; + } + } + + // When all the Cas sessions are occupied, reclaim the lowest priority client if the + // request client has higher priority. + if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) { + if (!reclaimResource(lowestPriorityOwnerId, + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) { + return false; + } + casSessionHandle[0] = generateResourceHandle( + TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId()); + updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId()); + return true; + } + return false; + } + + @VisibleForTesting + protected void releaseFrontendInternal(FrontendResource fe) { if (DEBUG) { Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")"); } @@ -596,7 +701,7 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - void releaseLnbInternal(LnbResource lnb) { + protected void releaseLnbInternal(LnbResource lnb) { if (DEBUG) { Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")"); } @@ -604,7 +709,15 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { + protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) { + if (DEBUG) { + Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")"); + } + updateCasClientMappingOnRelease(cas, ownerClientId); + } + + @VisibleForTesting + protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) { if (DEBUG) { Slog.d(TAG, "requestDemux(request=" + request + ")"); } @@ -614,7 +727,8 @@ public class TunerResourceManagerService extends SystemService { } @VisibleForTesting - boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) { + protected boolean requestDescramblerInternal( + TunerDescramblerRequest request, int[] descramblerHandle) { if (DEBUG) { Slog.d(TAG, "requestDescrambler(request=" + request + ")"); } @@ -742,14 +856,28 @@ public class TunerResourceManagerService extends SystemService { ownerProfile.releaseLnb(releasingLnb.getId()); } + private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) { + CasResource grantingCas = getCasResource(grantingId); + ClientProfile ownerProfile = getClientProfile(ownerClientId); + grantingCas.setOwner(ownerClientId); + ownerProfile.useCas(grantingId); + } + + private void updateCasClientMappingOnRelease( + @NonNull CasResource releasingCas, int ownerClientId) { + ClientProfile ownerProfile = getClientProfile(ownerClientId); + releasingCas.removeOwner(ownerClientId); + ownerProfile.releaseCas(); + } + /** * Get the owner client's priority from the resource id. * - * @param resource a in use tuner resource. + * @param clientId the owner client id. * @return the priority of the owner client of the resource. */ - private int getOwnerClientPriority(TunerResourceBasic resource) { - return getClientProfile(resource.getOwnerClientId()).getPriority(); + private int getOwnerClientPriority(int clientId) { + return getClientProfile(clientId).getPriority(); } @VisibleForTesting @@ -783,6 +911,9 @@ public class TunerResourceManagerService extends SystemService { private void removeFrontendResource(int removingId) { FrontendResource fe = getFrontendResource(removingId); + if (fe == null) { + return; + } if (fe.isInUse()) { releaseFrontendInternal(fe); } @@ -811,6 +942,9 @@ public class TunerResourceManagerService extends SystemService { private void removeLnbResource(int removingId) { LnbResource lnb = getLnbResource(removingId); + if (lnb == null) { + return; + } if (lnb.isInUse()) { releaseLnbInternal(lnb); } @@ -819,6 +953,39 @@ public class TunerResourceManagerService extends SystemService { @VisibleForTesting @Nullable + protected CasResource getCasResource(int systemId) { + return mCasResources.get(systemId); + } + + @VisibleForTesting + protected Map<Integer, CasResource> getCasResources() { + return mCasResources; + } + + private void addCasResource(CasResource newCas) { + // Update resource list and available id list + mCasResources.put(newCas.getSystemId(), newCas); + } + + private void removeCasResource(int removingId) { + CasResource cas = getCasResource(removingId); + if (cas == null) { + return; + } + for (int ownerId : cas.getOwnerClientIds()) { + getClientProfile(ownerId).releaseCas(); + } + mCasResources.remove(removingId); + } + + private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) { + // TODO: Sort with a treemap + + // select the first num client to release + } + + @VisibleForTesting + @Nullable protected ClientProfile getClientProfile(int clientId) { return mClientProfiles.get(clientId); } @@ -830,12 +997,7 @@ public class TunerResourceManagerService extends SystemService { } private void removeClientProfile(int clientId) { - for (int id : getClientProfile(clientId).getInUseFrontendIds()) { - getFrontendResource(id).removeOwner(); - for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) { - getFrontendResource(groupMemberId).removeOwner(); - } - } + reclaimingResourcesFromClient(getClientProfile(clientId)); mClientProfiles.remove(clientId); mListeners.remove(clientId); } @@ -847,6 +1009,9 @@ public class TunerResourceManagerService extends SystemService { for (Integer lnbId : profile.getInUseLnbIds()) { getLnbResource(lnbId).removeOwner(); } + if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) { + getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId()); + } profile.reclaimAllResources(); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 736bc9f8bc22..93a757449564 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5400,14 +5400,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH && config.navigation == Configuration.NAVIGATION_NONAV); - int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK; - final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR - && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER) - && modeType != Configuration.UI_MODE_TYPE_TELEVISION - && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET); final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(), HIDE_ERROR_DIALOGS, 0) != 0; - mShowDialogs = inputMethodExists && uiModeSupportsDialogs && !hideDialogsSet; + mShowDialogs = inputMethodExists + && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext) + && !hideDialogsSet; } private void updateFontScaleIfNeeded(@UserIdInt int userId) { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index e9d3d56ee283..8aace212d094 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -261,6 +261,8 @@ public class DisplayPolicy { @Px private int mRightGestureInset; + private boolean mNavButtonForcedVisible; + StatusBarManagerInternal getStatusBarManagerInternal() { synchronized (mServiceAcquireLock) { if (mStatusBarManagerInternal == null) { @@ -1046,12 +1048,14 @@ public class DisplayPolicy { // calculate inset. if (navigationBarPosition(displayFrames.mDisplayWidth, displayFrames.mDisplayHeight, - displayFrames.mRotation) == NAV_BAR_BOTTOM) { + displayFrames.mRotation) == NAV_BAR_BOTTOM + && !mNavButtonForcedVisible) { + sTmpRect.set(displayFrames.mUnrestricted); sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe); inOutFrame.top = sTmpRect.bottom - getNavigationBarHeight(displayFrames.mRotation, - mDisplayContent.getConfiguration().uiMode); + mDisplayContent.getConfiguration().uiMode); } }, @@ -2810,6 +2814,8 @@ public class DisplayPolicy { mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode); mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res); mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res); + mNavButtonForcedVisible = + mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible(); mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough); mNavigationBarAlwaysShowOnSideGesture = res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture); @@ -3783,13 +3789,14 @@ public class DisplayPolicy { * @param screenshotType The type of screenshot, for example either * {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or * {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION} + * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource) */ - public void takeScreenshot(int screenshotType) { + public void takeScreenshot(int screenshotType, int source) { if (mScreenshotHelper != null) { mScreenshotHelper.takeScreenshot(screenshotType, mStatusBar != null && mStatusBar.isVisibleLw(), mNavigationBar != null && mNavigationBar.isVisibleLw(), - mHandler, null /* completionConsumer */); + source, mHandler, null /* completionConsumer */); } } diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java index 9d44cad70281..86e081854597 100644 --- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java @@ -212,7 +212,7 @@ class ScreenRotationAnimation { final Surface surface = mService.mSurfaceFactory.get(); surface.copyFrom(mScreenshotLayer); SurfaceControl.ScreenshotGraphicBuffer gb = - mService.mDisplayManagerInternal.screenshot(displayId); + mService.mDisplayManagerInternal.systemScreenshot(displayId); if (gb != null) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ScreenRotationAnimation#getMedianBorderLuma"); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index df5cfee6c01c..66e1b1758d85 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4435,19 +4435,20 @@ class Task extends WindowContainer<WindowContainer> { // Let the old organizer know it has lost control. sendTaskVanished(); mTaskOrganizer = organizer; - sendTaskAppeared(); - onTaskOrganizerChanged(); - return true; - } - void taskOrganizerUnregistered() { - mTaskOrganizer = null; - mTaskAppearedSent = false; - mLastTaskOrganizerWindowingMode = -1; - onTaskOrganizerChanged(); - if (mCreatedByOrganizer) { - removeImmediately(); + if (mTaskOrganizer != null) { + sendTaskAppeared(); + } else { + // No longer managed by any organizer. + mTaskAppearedSent = false; + mLastTaskOrganizerWindowingMode = -1; + setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */); + if (mCreatedByOrganizer) { + removeImmediately(); + } } + + return true; } /** @@ -4484,14 +4485,6 @@ class Task extends WindowContainer<WindowContainer> { return result; } - private void onTaskOrganizerChanged() { - if (mTaskOrganizer == null) { - // If this task is no longer controlled by a task organizer, then reset the force hidden - // state - setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */); - } - } - @Override void setSurfaceControl(SurfaceControl sc) { super.setSurfaceControl(sc); diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index adc50bf70446..306c100e651c 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -218,18 +218,24 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } void dispose() { - releaseTasks(); + // Move organizer from managing specific windowing modes for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) { mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder()); } - } - private void releaseTasks() { - for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) { - final Task t = mOrganizedTasks.get(i); - removeTask(t); - t.taskOrganizerUnregistered(); + // Update tasks currently managed by this organizer to the next one available if + // possible. + while (!mOrganizedTasks.isEmpty()) { + final Task t = mOrganizedTasks.get(0); + t.updateTaskOrganizerState(true /* forceUpdate */); + if (mOrganizedTasks.contains(t)) { + removeTask(t); + } } + + // Remove organizer state after removing tasks so we get a chance to send + // onTaskVanished. + mTaskOrganizerStates.remove(asBinder()); } void unlinkDeath() { @@ -313,16 +319,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { new TaskOrganizerState(organizer, uid)); } - if (orgs.size() == 1) { - // Only in the case where this is the root task organizer for the given - // windowing mode, we add report all existing tasks in that mode to the new - // task organizer. - mService.mRootWindowContainer.forAllTasks((task) -> { - if (task.getWindowingMode() == windowingMode) { - task.updateTaskOrganizerState(true /* forceUpdate */); - } - }); - } + mService.mRootWindowContainer.forAllTasks((task) -> { + if (task.getWindowingMode() == windowingMode) { + task.updateTaskOrganizerState(true /* forceUpdate */); + } + }); } } finally { Binder.restoreCallingIdentity(origId); @@ -335,7 +336,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - final TaskOrganizerState state = mTaskOrganizerStates.remove(organizer.asBinder()); + final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); if (state == null) { return; } @@ -367,7 +368,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { void onTaskVanished(ITaskOrganizer organizer, Task task) { final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder()); - state.removeTask(task); + if (state != null) { + state.removeTask(task); + } } @Override diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0857204e4ffe..c6cabf8bac35 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2446,17 +2446,15 @@ public class WindowManagerService extends IWindowManager.Stub if (controls != null) { final int length = Math.min(controls.length, outControls.length); for (int i = 0; i < length; i++) { - final InsetsSourceControl control = controls[i]; - - // Check if we are sending invalid leashes. - final SurfaceControl leash = control != null ? control.getLeash() : null; - if (leash != null && !leash.isValid()) { - Slog.wtf(TAG, leash + " is not valid before sending to " + win, - leash.getReleaseStack()); - } - - outControls[i] = win.isClientLocal() && control != null - ? new InsetsSourceControl(control) : control; + // We will leave the critical section before returning the leash to the client, + // so we need to copy the leash to prevent others release the one that we are + // about to return. + // TODO: We will have an extra copy if the client is not local. + // For now, we rely on GC to release it. + // Maybe we can modify InsetsSourceControl.writeToParcel so it can release + // the extra leash as soon as possible. + outControls[i] = controls[i] != null + ? new InsetsSourceControl(controls[i]) : null; } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2c0d4c0c9208..7b845557cf73 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -161,8 +161,11 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; +import android.content.IntentSender; import android.content.PermissionChecker; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -171,6 +174,7 @@ import android.content.pm.CrossProfileAppsInternal; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; @@ -2703,8 +2707,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final ComponentName doAdminReceiver = doAdmin.info.getComponent(); clearDeviceOwnerLocked(doAdmin, doUserId); Slog.i(LOG_TAG, "Removing admin artifacts..."); - // TODO(b/149075700): Clean up application restrictions in UserManager. removeAdminArtifacts(doAdminReceiver, doUserId); + Slog.i(LOG_TAG, "Uninstalling the DO..."); + uninstallOrDisablePackage(doAdminComponent.getPackageName(), doUserId); Slog.i(LOG_TAG, "Migration complete."); // Note: KeyChain keys are not removed and will remain accessible for the apps that have @@ -2716,6 +2721,47 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .write(); } + private void uninstallOrDisablePackage(String packageName, int userHandle) { + final ApplicationInfo appInfo; + try { + appInfo = mIPackageManager.getApplicationInfo( + packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle); + } catch (RemoteException e) { + // Shouldn't happen. + return; + } + if (appInfo == null) { + Slog.wtf(LOG_TAG, "Failed to get package info for " + packageName); + return; + } + if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + Slog.i(LOG_TAG, String.format( + "Package %s is pre-installed, marking disabled until used", packageName)); + mContext.getPackageManager().setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */); + return; + } + + final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() { + @Override + public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken, + IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { + final int status = intent.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + if (status == PackageInstaller.STATUS_SUCCESS) { + Slog.i(LOG_TAG, String.format( + "Package %s uninstalled for user %d", packageName, userHandle)); + } else { + Slog.e(LOG_TAG, String.format( + "Failed to uninstall %s; status: %d", packageName, status)); + } + } + }; + + final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller(); + pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender)); + } + private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) { // The following policies can be already controlled via parent instance, skip if so. if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) { @@ -8766,6 +8812,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { saveSettingsLocked(UserHandle.USER_SYSTEM); clearUserPoliciesLocked(userId); clearOverrideApnUnchecked(); + clearApplicationRestrictions(userId); + mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId); mOwners.clearDeviceOwner(); mOwners.writeDeviceOwner(); @@ -8779,6 +8827,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { toggleBackupServiceActive(UserHandle.USER_SYSTEM, true); } + private void clearApplicationRestrictions(int userId) { + // Changing app restrictions involves disk IO, offload it to the background thread. + mBackgroundHandler.post(() -> { + final List<PackageInfo> installedPackageInfos = mInjector.getPackageManager(userId) + .getInstalledPackages(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE); + final UserHandle userHandle = UserHandle.of(userId); + for (final PackageInfo packageInfo : installedPackageInfos) { + mInjector.getUserManager().setApplicationRestrictions( + packageInfo.packageName, null /* restrictions */, userHandle); + } + }); + } + @Override public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) { if (!mHasFeature) { @@ -8898,6 +8959,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { policyData.mOwnerInstalledCaCerts.clear(); saveSettingsLocked(userId); clearUserPoliciesLocked(userId); + clearApplicationRestrictions(userId); mOwners.removeProfileOwner(userId); mOwners.writeProfileOwner(userId); deleteTransferOwnershipBundleLocked(userId); diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java index c87ece29800c..763e19bd14ab 100644 --- a/services/people/java/com/android/server/people/data/DataManager.java +++ b/services/people/java/com/android/server/people/data/DataManager.java @@ -67,6 +67,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.telephony.SmsApplication; import com.android.server.LocalServices; import com.android.server.notification.NotificationManagerInternal; +import com.android.server.notification.ShortcutHelper; import java.util.ArrayList; import java.util.Collections; @@ -497,10 +498,6 @@ public class DataManager { EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); } - private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) { - return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0; - } - @VisibleForTesting @WorkerThread void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) { @@ -712,7 +709,8 @@ public class DataManager { @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) { mInjector.getBackgroundExecutor().execute(() -> { for (ShortcutInfo shortcut : shortcuts) { - if (isPersonShortcut(shortcut)) { + if (ShortcutHelper.isConversationShortcut( + shortcut, mShortcutServiceInternal, user.getIdentifier())) { addOrUpdateConversationInfo(shortcut); } } diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java index 335217719cc9..064e3486823a 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java @@ -16,6 +16,8 @@ package com.android.server.accessibility; +import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS; + import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; @@ -308,7 +310,7 @@ public class SystemActionPerformerTest { AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT); verify(mMockScreenshotHelper).takeScreenshot( eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(), - anyBoolean(), any(Handler.class), any()); + anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any()); } // PendingIntent is a final class and cannot be mocked. So we are using this diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 74e7f8c44d1a..a0b9d9d2a875 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -62,6 +62,10 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { mContext = getContext(); + // Make createContextAsUser to work. + mContext.packageName = "com.android.frameworks.servicestests"; + getServices().addPackageContext(UserHandle.of(0), mContext); + when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN))) .thenReturn(true); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 09d1d3a270ba..57039e53429e 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -196,6 +196,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { anyInt(), any(UserHandle.class)); + // Make createContextAsUser to work. + mContext.packageName = "com.android.frameworks.servicestests"; + getServices().addPackageContext(UserHandle.of(0), mContext); + getServices().addPackageContext(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE), mContext); + // By default, pretend all users are running and unlocked. when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 8625a1ed9fda..20716ab501df 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -460,6 +460,15 @@ public class DpmMockContext extends MockContext { } @Override + public Context createContextAsUser(UserHandle user, int flags) { + try { + return mMockSystemServices.createPackageContextAsUser(packageName, flags, user); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException(e); + } + } + + @Override public ContentResolver getContentResolver() { return mMockSystemServices.contentResolver; } diff --git a/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java b/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java new file mode 100644 index 000000000000..d438a0eb9411 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.emergency; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.UserHandle; +import android.provider.Settings; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; +import android.telephony.TelephonyManager; +import android.test.mock.MockContentResolver; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.SystemService; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +/** + * Unit test for EmergencyAffordanceService (EAS for short) which determines when + * should we enable Emergency Affordance feature (EA for short). + * + * Please refer to https://source.android.com/devices/tech/connect/emergency-affordance + * to see the details of the feature. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class EmergencyAffordanceServiceTest { + + // Default country ISO that should enable EA. Value comes from resource + // com.android.internal.R.array.config_emergency_iso_country_codes + private static final String EMERGENCY_ISO_CODE = "in"; + // Randomly picked country ISO that should not enable EA. + private static final String NON_EMERGENCY_ISO_CODE = "us"; + + // Valid values for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED + private static final int OFF = 0; // which means feature disabled + private static final int ON = 1; // which means feature enabled + + private static final int ACTIVE_MODEM_COUNT = 2; + + @Mock private Resources mResources; + @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonyManager mTelephonyManager; + + private TestContext mServiceContext; + private MockContentResolver mContentResolver; + private OnSubscriptionsChangedListener mSubscriptionChangedListener; + private EmergencyAffordanceService mService; + + // Testable Context that mocks resources, content resolver and system services + private class TestContext extends BroadcastInterceptingContext { + TestContext(Context base) { + super(base); + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public Object getSystemService(String name) { + switch (name) { + case Context.TELEPHONY_SUBSCRIPTION_SERVICE: + return mSubscriptionManager; + case Context.TELEPHONY_SERVICE: + return mTelephonyManager; + default: + return super.getSystemService(name); + } + } + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + doReturn(new String[] { EMERGENCY_ISO_CODE }).when(mResources) + .getStringArray(com.android.internal.R.array.config_emergency_iso_country_codes); + + final Context context = InstrumentationRegistry.getContext(); + mServiceContext = new TestContext(context); + mContentResolver = new MockContentResolver(mServiceContext); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + + // Initialize feature off, to have constant starting + Settings.Global.putInt(mContentResolver, Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, 0); + mService = new EmergencyAffordanceService(mServiceContext); + } + + /** + * Verify if the device is not voice capable, the feature should be disabled. + */ + @Test + public void testSettings_shouldBeOff_whenVoiceCapableIsFalse() throws Exception { + // Given: the device is not voice capable + // When: setup device and boot service + setUpDevice(false /* withVoiceCapable */, true /* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // Then: EA setting will should be 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + + /** + * Verify the voice capable device is booted up without EA-enabled cell network, with + * no EA-enabled SIM installed, feature should be disabled. + */ + @Test + public void testSettings_shouldBeOff_whenWithoutEAEanbledNetworkNorSim() throws Exception { + // Given: the device is voice capble, no EA-enable SIM, no EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // Then: EA setting will should be 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + + /** + * Verify the voice capable device is booted up with EA-enabled SIM installed, the + * feature should be enabled. + */ + @Test + public void testSettings_shouldBeOn_whenBootUpWithEAEanbledSim() throws Exception { + // Given: the device is voice capble, with EA-enable SIM, no EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // Then: EA setting will immediately update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify the voice capable device is booted up with EA-enabled Cell network, the + * feature should be enabled. + */ + @Test + public void testSettings_shouldBeOn_whenBootUpWithEAEanbledCell() throws Exception { + // Given: the device is voice capble, with EA-enable SIM, with EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // Then: EA setting will immediately update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify when device boot up with no EA-enabled SIM, but later install one, + * feature should be enabled. + */ + @Test + public void testSettings_shouldBeOn_whenSubscriptionInfoChangedWithEmergencyIso() + throws Exception { + // Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // When: Insert EA-enabled SIM and get notified + setUpSim(true /* withEmergencyIsoInSim */); + mSubscriptionChangedListener.onSubscriptionsChanged(); + + // Then: EA Setting will update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify when feature was on, device re-insert with no EA-enabled SIMs, + * feature should be disabled. + */ + @Test + public void testSettings_shouldBeOff_whenSubscriptionInfoChangedWithoutEmergencyIso() + throws Exception { + // Given: the device is voice capable, no EA-enabled Cell, with EA-enabled SIM + setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // When: All SIMs are replaced with EA-disabled ones. + setUpSim(false /* withEmergencyIsoInSim */); + mSubscriptionChangedListener.onSubscriptionsChanged(); + + // Then: EA Setting will update to 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + + /** + * Verify when device boot up with no EA-enabled Cell, but later move into one, + * feature should be enabled. + */ + @Test + public void testSettings_shouldBeOn_whenCountryIsoChangedWithEmergencyIso() + throws Exception { + // Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // When: device locale change to EA-enabled Cell and get notified + resetCell(true /* withEmergencyIsoInSim */); + sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO); + + // Then: EA Setting will update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify when device boot up with EA-enabled Cell, but later move out of it, + * feature should be enabled. + */ + @Test + public void testSettings_shouldBeOff_whenCountryIsoChangedWithoutEmergencyIso() + throws Exception { + // Given: the device is voice capable, boot up with no EA-enabled SIM, with EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // When: device locale change to no EA-enabled Cell and get notified + resetCell(false /* withEmergencyIsoInSim */); + sendBroadcastNetworkCountryChanged(NON_EMERGENCY_COUNTRY_ISO); + + // Then: EA Setting will update to 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + /** + * Verify if device is not in EA-enabled Mobile Network without EA-enable SIM(s) installed, + * when receive SubscriptionInfo change, the feature should not be enabled. + */ + @Test + public void testSettings_shouldBeOff_whenNoEmergencyIsoInCellNorSim() throws Exception { + // Given: the device is voice capable, no EA-enabled Cell, no EA-enabled SIM + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // When: Subscription changed event received + mSubscriptionChangedListener.onSubscriptionsChanged(); + + // Then: EA Settings should be 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + + /** + * Verify while feature was on, when device receive empty country iso change, while APM is + * enabled, feature status should keep on. + */ + @Test + public void testSettings_shouldOn_whenReceiveEmptyISOWithAPMEnabled() throws Exception { + // Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // Then: EA Settings will update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + + // When: Airplane mode is enabled, and we receive EMPTY ISO in locale change + setAirplaneMode(true); + sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO); + + // Then: EA Settings will keep to 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify while feature was on, when device receive empty country iso change, while APM is + * disabled, feature should be disabled. + */ + @Test + public void testSettings_shouldOff_whenReceiveEmptyISOWithAPMDisabled() throws Exception { + // Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // Then: EA Settings will update to 1 + verifyEmergencyAffordanceNeededSettings(ON); + + // When: Airplane mode is disabled, and we receive valid empty ISO in locale change + setUpCell(false /* withEmergencyIsoInCell */); + setAirplaneMode(false); + sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO); + + // Then: EA Settings will keep to 0 + verifyEmergencyAffordanceNeededSettings(OFF); + } + + /** + * Verify when airplane mode is turn on and off in cell network with EA-enabled ISO, + * feature should keep enabled. + */ + @Test + public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInCell() + throws Exception { + // Given: the device is voice capable, no EA-enabled SIM, with EA-enabled Cell + setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */, + true /* withEmergencyIsoInCell */); + + // When: Device receive locale change with EA-enabled iso + sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO); + + // When: Airplane mode is disabled + setAirplaneMode(false); + + // Then: EA Settings will keep with 1 + verifyEmergencyAffordanceNeededSettings(ON); + + // When: Airplane mode is enabled + setAirplaneMode(true); + + // Then: EA Settings is still 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + /** + * Verify when airplane mode is turn on and off with EA-enabled ISO in SIM, + * feature should keep enabled. + */ + @Test + public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInSim() + throws Exception { + // Given: the device is voice capable, no EA-enabled Cell Network, with EA-enabled SIM + setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */, + false /* withEmergencyIsoInCell */); + + // When: Airplane mode is disabled + setAirplaneMode(false); + + // Then: EA Settings will keep with 1 + verifyEmergencyAffordanceNeededSettings(ON); + + // When: Airplane mode is enabled + setAirplaneMode(true); + + // Then: EA Settings is still 1 + verifyEmergencyAffordanceNeededSettings(ON); + } + + // EAS reads voice capable during boot up and cache it. To test non voice capable device, + // we can not put this in setUp + private void setUpDevice(boolean withVoiceCapable, boolean withEmergencyIsoInSim, + boolean withEmergencyIsoInCell) throws Exception { + setUpVoiceCapable(withVoiceCapable); + + setUpSim(withEmergencyIsoInSim); + + setUpCell(withEmergencyIsoInCell); + + // bypass onStart which is used to publish binder service and need sepolicy policy update + // mService.onStart(); + + mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START); + + if (!withVoiceCapable) { + return; + } + + captureSubscriptionChangeListener(); + } + + private void setUpVoiceCapable(boolean voiceCapable) { + doReturn(voiceCapable).when(mTelephonyManager).isVoiceCapable(); + } + + private static final Supplier<String> EMPTY_COUNTRY_ISO = () -> ""; + private static final Supplier<String> EMERGENCY_COUNTRY_ISO = () -> EMERGENCY_ISO_CODE; + private static final Supplier<String> NON_EMERGENCY_COUNTRY_ISO = () -> NON_EMERGENCY_ISO_CODE; + private void sendBroadcastNetworkCountryChanged(Supplier<String> countryIso) { + Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED); + intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso.get()); + SubscriptionManager.putPhoneIdAndSubIdExtra(intent, 0); + mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + private void setUpSim(boolean withEmergencyIsoInSim) { + List<SubscriptionInfo> subInfos = getSubscriptionInfoList(withEmergencyIsoInSim); + doReturn(subInfos).when(mSubscriptionManager).getActiveSubscriptionInfoList(); + } + + private void setUpCell(boolean withEmergencyIsoInCell) { + doReturn(ACTIVE_MODEM_COUNT).when(mTelephonyManager).getActiveModemCount(); + doReturn(NON_EMERGENCY_ISO_CODE).when(mTelephonyManager).getNetworkCountryIso(0); + doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE) + .when(mTelephonyManager).getNetworkCountryIso(1); + } + + private void resetCell(boolean withEmergencyIsoInCell) { + doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE) + .when(mTelephonyManager).getNetworkCountryIso(1); + } + + private void captureSubscriptionChangeListener() { + final ArgumentCaptor<OnSubscriptionsChangedListener> subChangedListenerCaptor = + ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class); + verify(mSubscriptionManager).addOnSubscriptionsChangedListener( + subChangedListenerCaptor.capture()); + mSubscriptionChangedListener = subChangedListenerCaptor.getValue(); + } + + private void setAirplaneMode(boolean enabled) { + // Change the system settings + Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, + enabled ? 1 : 0); + + // Post the intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", enabled); + mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + + private List<SubscriptionInfo> getSubscriptionInfoList(boolean withEmergencyIso) { + List<SubscriptionInfo> subInfos = new ArrayList<>(2); + + // Test with Multiple SIMs. SIM1 is a non-EA SIM + // Only country iso is valuable, all other info are filled with dummy values + SubscriptionInfo subInfo = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "T-mobile", + "T-mobile", 0, 255, "12345", 0, null, + "310", "226", NON_EMERGENCY_ISO_CODE, false, null, null); + subInfos.add(subInfo); + + // SIM2 can configured to be non-EA or EA SIM according parameter withEmergencyIso + SubscriptionInfo subInfo2 = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "Airtel", + "Aritel", 0, 255, "12345", 0, null, "310", "226", + withEmergencyIso ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE, + false, null, null); + subInfos.add(subInfo2); + + return subInfos; + } + + // EAS has handler thread to perform heavy work, while FakeSettingProvider does not support + // ContentObserver. To make sure consistent result, we use a simple sleep & retry to wait for + // real work finished before verify result. + private static final int TIME_DELAY_BEFORE_VERIFY_IN_MS = 50; + private static final int RETRIES_BEFORE_VERIFY = 20; + private void verifyEmergencyAffordanceNeededSettings(int expected) throws Exception { + try { + int ct = 0; + int actual = -1; + while (ct++ < RETRIES_BEFORE_VERIFY + && (actual = Settings.Global.getInt(mContentResolver, + Settings.Global.EMERGENCY_AFFORDANCE_NEEDED)) != expected) { + Thread.sleep(TIME_DELAY_BEFORE_VERIFY_IN_MS); + } + assertEquals(expected, actual); + } catch (Settings.SettingNotFoundException e) { + fail("SettingNotFoundException thrown for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED"); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 728e1492c0d5..e16f3145de0b 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -215,6 +215,8 @@ public final class DataManagerTest { mDataManager = new DataManager(mContext, mInjector); mDataManager.initialize(); + when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), + anyString(), anyInt(), any())).thenReturn(true); verify(mShortcutServiceInternal).addShortcutChangeCallback( mShortcutChangeCallbackCaptor.capture()); mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue(); diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java index c4289efe1839..e7e8aca86364 100644 --- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java +++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java @@ -158,17 +158,6 @@ public class AttentionDetectorTest extends AndroidTestCase { } @Test - public void testOnUserActivity_disablesSettingIfNotSufficientPermissions() { - when(mPackageManager.checkPermission(any(), any())).thenReturn( - PackageManager.PERMISSION_DENIED); - - registerAttention(); - boolean enabled = Settings.Secure.getIntForUser(getContext().getContentResolver(), - Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT) == 1; - assertFalse(enabled); - } - - @Test public void testOnUserActivity_doesntCrashIfNoAttentionService() { mAttentionManagerInternal = null; registerAttention(); diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java index 965304f3c433..21af3563b869 100644 --- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java @@ -26,6 +26,7 @@ import android.media.tv.ITvInputManager; import android.media.tv.TvInputManager; import android.media.tv.TvInputService; import android.media.tv.tuner.frontend.FrontendSettings; +import android.media.tv.tunerresourcemanager.CasSessionRequest; import android.media.tv.tunerresourcemanager.IResourcesReclaimListener; import android.media.tv.tunerresourcemanager.ResourceClientProfile; import android.media.tv.tunerresourcemanager.TunerDemuxRequest; @@ -34,7 +35,6 @@ import android.media.tv.tunerresourcemanager.TunerFrontendInfo; import android.media.tv.tunerresourcemanager.TunerFrontendRequest; import android.media.tv.tunerresourcemanager.TunerLnbRequest; import android.media.tv.tunerresourcemanager.TunerResourceManager; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -236,12 +236,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -264,12 +260,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE); } @@ -296,12 +288,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(0); } @@ -334,23 +322,15 @@ public class TunerResourceManagerServiceTest { int[] frontendHandle = new int[1]; TunerFrontendRequest request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId()) @@ -394,32 +374,20 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isRelaimed()).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); + assertThat(listener.isRelaimed()).isFalse(); request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isFalse(); - assertThat(listener.isRelaimed()).isFalse(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isFalse(); + assertThat(listener.isRelaimed()).isFalse(); } @Test @@ -456,12 +424,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) @@ -470,12 +434,8 @@ public class TunerResourceManagerServiceTest { request = new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS); - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[1].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) @@ -511,12 +471,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]); assertThat(frontendId).isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService @@ -534,6 +490,99 @@ public class TunerResourceManagerServiceTest { } @Test + public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[2]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + profiles[1] = new ResourceClientProfile("1" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientPriorities = {100, 500}; + int[] clientId0 = new int[1]; + int[] clientId1 = new int[1]; + TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[0], listener, clientId0); + assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfile(clientId0[0]) + .setPriority(clientPriorities[0]); + mTunerResourceManagerService.registerClientProfileInternal( + profiles[1], new TestResourcesReclaimListener(), clientId1); + assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + mTunerResourceManagerService.getClientProfile(clientId1[0]) + .setPriority(clientPriorities[1]); + + // Init cas resources. + mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); + + CasSessionRequest request = new CasSessionRequest(clientId0[0], 1 /*casSystemId*/); + int[] casSessionHandle = new int[1]; + // Request for 2 cas sessions. + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue(); + + request = new CasSessionRequest(clientId1[0], 1); + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) + .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + assertThat(listener.isRelaimed()).isTrue(); + } + + @Test + public void releaseCasTest() { + // Register clients + ResourceClientProfile[] profiles = new ResourceClientProfile[1]; + profiles[0] = new ResourceClientProfile("0" /*sessionId*/, + TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK); + int[] clientId = new int[1]; + TestResourcesReclaimListener listener = new TestResourcesReclaimListener(); + mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId); + assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID); + + // Init cas resources. + mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/); + + CasSessionRequest request = new CasSessionRequest(clientId[0], 1 /*casSystemId*/); + int[] casSessionHandle = new int[1]; + // Request for 1 cas sessions. + assertThat(mTunerResourceManagerService + .requestCasSessionInternal(request, casSessionHandle)).isTrue(); + assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0])) + .isEqualTo(1); + assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) + .getInUseCasSystemId()).isEqualTo(1); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0]))); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + + // Release cas + mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService + .getCasResource(1), clientId[0]); + assertThat(mTunerResourceManagerService.getClientProfile(clientId[0]) + .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID); + assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse(); + assertThat(mTunerResourceManagerService.getCasResource(1) + .getOwnerClientIds()).isEmpty(); + } + + @Test public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() { // Register clients ResourceClientProfile[] profiles = new ResourceClientProfile[2]; @@ -562,24 +611,16 @@ public class TunerResourceManagerServiceTest { TunerLnbRequest request = new TunerLnbRequest(clientId0[0]); int[] lnbHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0])) .isEqualTo(lnbIds[0]); assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0]) .getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0]))); request = new TunerLnbRequest(clientId1[0]); - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0])) .isEqualTo(lnbIds[0]); assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0]) @@ -608,12 +649,8 @@ public class TunerResourceManagerServiceTest { TunerLnbRequest request = new TunerLnbRequest(clientId[0]); int[] lnbHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestLnbInternal(request, lnbHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestLnbInternal(request, lnbHandle)).isTrue(); int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]); assertThat(lnbId).isEqualTo(lnbIds[0]); @@ -647,12 +684,8 @@ public class TunerResourceManagerServiceTest { TunerFrontendRequest request = new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT); int[] frontendHandle = new int[1]; - try { - assertThat(mTunerResourceManagerService - .requestFrontendInternal(request, frontendHandle)).isTrue(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + assertThat(mTunerResourceManagerService + .requestFrontendInternal(request, frontendHandle)).isTrue(); assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0])) .isEqualTo(infos[0].getId()); assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId()) diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ecdd9e548e6a..7a5e2266e62f 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6074,6 +6074,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(PKG); + when(info.getId()).thenReturn("someshortcutId"); + when(info.getUserId()).thenReturn(USER_SYSTEM); when(info.isLongLived()).thenReturn(true); when(info.isEnabled()).thenReturn(true); shortcutInfos.add(info); @@ -6137,6 +6140,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Pretend the shortcut exists List<ShortcutInfo> shortcutInfos = new ArrayList<>(); ShortcutInfo info = mock(ShortcutInfo.class); + when(info.getPackage()).thenReturn(PKG); + when(info.getId()).thenReturn("someshortcutId"); + when(info.getUserId()).thenReturn(USER_SYSTEM); when(info.isLongLived()).thenReturn(true); when(info.isEnabled()).thenReturn(true); shortcutInfos.add(info); @@ -6483,6 +6489,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG_P); + when(si.getId()).thenReturn("convo"); + when(si.getUserId()).thenReturn(USER_SYSTEM); when(si.getShortLabel()).thenReturn("Hello"); when(si.isLongLived()).thenReturn(true); when(si.isEnabled()).thenReturn(true); @@ -6514,6 +6523,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos); ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG_P); + when(si.getId()).thenReturn("convo"); + when(si.getUserId()).thenReturn(USER_SYSTEM); when(si.getShortLabel()).thenReturn("Hello"); when(si.isLongLived()).thenReturn(false); when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si)); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java index f7304bd0075b..3095c87ba2f6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java @@ -170,6 +170,9 @@ public class ShortcutHelperTest extends UiServiceTestCase { @Test public void testGetValidShortcutInfo_notLongLived() { ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG); + when(si.getId()).thenReturn(SHORTCUT_ID); + when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(si.isLongLived()).thenReturn(false); when(si.isEnabled()).thenReturn(true); ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); @@ -184,6 +187,9 @@ public class ShortcutHelperTest extends UiServiceTestCase { @Test public void testGetValidShortcutInfo_notSharingShortcut() { ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG); + when(si.getId()).thenReturn(SHORTCUT_ID); + when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(si.isLongLived()).thenReturn(true); when(si.isEnabled()).thenReturn(true); ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); @@ -198,6 +204,9 @@ public class ShortcutHelperTest extends UiServiceTestCase { @Test public void testGetValidShortcutInfo_notEnabled() { ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG); + when(si.getId()).thenReturn(SHORTCUT_ID); + when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(si.isLongLived()).thenReturn(true); when(si.isEnabled()).thenReturn(false); ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); @@ -212,6 +221,9 @@ public class ShortcutHelperTest extends UiServiceTestCase { @Test public void testGetValidShortcutInfo_isValid() { ShortcutInfo si = mock(ShortcutInfo.class); + when(si.getPackage()).thenReturn(PKG); + when(si.getId()).thenReturn(SHORTCUT_ID); + when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM); when(si.isLongLived()).thenReturn(true); when(si.isEnabled()).thenReturn(true); ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index f65328dcbd42..7d2e88014f45 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -81,7 +81,7 @@ import java.util.List; * Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}. * * Build/Install/Run: - * atest WmTests:TaskOrganizerTests + * atest WmTests:WindowOrganizerTests */ @SmallTest @Presubmit @@ -264,15 +264,22 @@ public class WindowOrganizerTests extends WindowTestsBase { // newly entering the windowing mode. final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW); stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer2).onTaskAppeared(any()); + // One each for task and task2 + verify(organizer2, times(2)).onTaskAppeared(any()); + verify(organizer2, times(0)).onTaskVanished(any()); + // One for task + verify(organizer).onTaskVanished(any()); assertTrue(stack2.isOrganized()); // Now we unregister the second one, the first one should automatically be reregistered // so we verify that it's now seeing changes. mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2); + verify(organizer, times(3)).onTaskAppeared(any()); + verify(organizer2, times(2)).onTaskVanished(any()); stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - verify(organizer, times(2)).onTaskAppeared(any()); + verify(organizer, times(4)).onTaskAppeared(any()); + verify(organizer2, times(2)).onTaskVanished(any()); assertTrue(stack3.isOrganized()); } @@ -902,12 +909,13 @@ public class WindowOrganizerTests extends WindowTestsBase { task.setHasBeenVisible(true); verify(organizer, times(1)).onTaskAppeared(any()); - task.taskOrganizerUnregistered(); + task.setTaskOrganizer(null); + verify(organizer, times(1)).onTaskVanished(any()); task.setTaskOrganizer(organizer); verify(organizer, times(2)).onTaskAppeared(any()); task.removeImmediately(); - verify(organizer).onTaskVanished(any()); + verify(organizer, times(2)).onTaskVanished(any()); } @Test diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index 9dfa3ac7a5d8..fa9909547fc4 100755 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -734,6 +734,31 @@ public abstract class Connection extends Conferenceable { "android.telecom.extra.ORIGINAL_CONNECTION_ID"; /** + * Extra key set on a {@link Connection} when it was created via a remote connection service. + * For example, if a connection manager requests a remote connection service to create a call + * using one of the remote connection service's phone account handle, this extra will be set so + * that Telecom knows that the wrapped remote connection originated in a remote connection + * service. We stash this in the extras since connection managers will typically copy the + * extras from a {@link RemoteConnection} to a {@link Connection} (there is ultimately not + * other way to relate a {@link RemoteConnection} to a {@link Connection}. + * @hide + */ + public static final String EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE = + "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE"; + + /** + * Extra key set from a {@link ConnectionService} when using the remote connection APIs + * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle, + * ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving + * {@link ConnectionService} with a means to know the package name of the requesting + * {@link ConnectionService} so that {@link #EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE} can be set for + * better visibility in Telecom of where a connection ultimately originated. + * @hide + */ + public static final String EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME = + "android.telecom.extra.REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME"; + + /** * Boolean connection extra key set on the extras passed to * {@link Connection#sendConnectionEvent} which indicates that audio is present * on the RTT call when the extra value is true. diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 1b60e4820ad0..a716b37f7efd 100755 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -1859,9 +1859,25 @@ public abstract class ConnectionService extends Service { new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"), request.getAccountHandle()); } - if (conference.getExtras() != null) { - conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + + Bundle extras = request.getExtras(); + Bundle newExtras = new Bundle(); + newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + if (extras != null) { + // If the request originated from a remote connection service, we will add some + // tracking information that Telecom can use to keep informed of which package + // made the remote request, and which remote connection service was used. + if (extras.containsKey(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) { + newExtras.putString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + extras.getString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + request.getAccountHandle()); + } } + conference.putExtras(newExtras); + mConferenceById.put(callId, conference); mIdByConference.put(conference, callId); conference.addListener(mConferenceListener); @@ -1936,6 +1952,30 @@ public abstract class ConnectionService extends Service { Log.i(this, "createConnection, implementation returned null connection."); connection = Connection.createFailedConnection( new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION")); + } else { + try { + Bundle extras = request.getExtras(); + if (extras != null) { + // If the request originated from a remote connection service, we will add some + // tracking information that Telecom can use to keep informed of which package + // made the remote request, and which remote connection service was used. + if (extras.containsKey( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) { + Bundle newExtras = new Bundle(); + newExtras.putString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + extras.getString( + Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME + )); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + request.getAccountHandle()); + connection.putExtras(newExtras); + } + } + } catch (UnsupportedOperationException ose) { + // Do nothing; if the ConnectionService reported a failure it will be an instance + // of an immutable Connection which we cannot edit, so we're out of luck. + } } boolean isSelfManaged = diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java index d82e93fac76d..8d3f4e1df8bc 100644 --- a/telecomm/java/android/telecom/Logging/Session.java +++ b/telecomm/java/android/telecom/Logging/Session.java @@ -427,7 +427,7 @@ public class Session { StringBuilder methodName = new StringBuilder(); methodName.append(getFullMethodPath(false /*truncatePath*/)); if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) { - methodName.append("(InCall package: "); + methodName.append("("); methodName.append(mOwnerInfo); methodName.append(")"); } diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java index cad5b707a146..a0833011715d 100644 --- a/telecomm/java/android/telecom/RemoteConnectionService.java +++ b/telecomm/java/android/telecom/RemoteConnectionService.java @@ -258,6 +258,9 @@ final class RemoteConnectionService { // See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information. Bundle newExtras = new Bundle(); newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId); + // Track the fact this request was relayed through the remote connection service. + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + parcel.getPhoneAccount()); conference.putExtras(newExtras); conference.registerCallback(new RemoteConference.Callback() { @@ -383,6 +386,11 @@ final class RemoteConnectionService { RemoteConnection remoteConnection = new RemoteConnection(callId, mOutgoingConnectionServiceRpc, connection, callingPackage, callingTargetSdkVersion); + // Track that it is via a remote connection. + Bundle newExtras = new Bundle(); + newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE, + connection.getPhoneAccount()); + remoteConnection.putExtras(newExtras); mConnectionById.put(callId, remoteConnection); remoteConnection.registerCallback(new RemoteConnection.Callback() { @Override @@ -535,10 +543,20 @@ final class RemoteConnectionService { ConnectionRequest request, boolean isIncoming) { final String id = UUID.randomUUID().toString(); + Bundle extras = new Bundle(); + if (request.getExtras() != null) { + extras.putAll(request.getExtras()); + } + // We will set the package name for the originator of the remote request; this lets the + // receiving ConnectionService know that the request originated from a remote connection + // service so that it can provide tracking information for Telecom. + extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME, + mOurConnectionServiceImpl.getApplicationContext().getOpPackageName()); + final ConnectionRequest newRequest = new ConnectionRequest.Builder() .setAccountHandle(request.getAccountHandle()) .setAddress(request.getAddress()) - .setExtras(request.getExtras()) + .setExtras(extras) .setVideoState(request.getVideoState()) .setRttPipeFromInCall(request.getRttPipeFromInCall()) .setRttPipeToInCall(request.getRttPipeToInCall()) diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java index 151fb59b7550..ede67dd9fd61 100644 --- a/telephony/java/android/telephony/ims/ImsRcsManager.java +++ b/telephony/java/android/telephony/ims/ImsRcsManager.java @@ -49,7 +49,7 @@ import java.util.function.Consumer; * * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager. */ -public class ImsRcsManager implements RegistrationManager { +public class ImsRcsManager { private static final String TAG = "ImsRcsManager"; /** @@ -173,11 +173,11 @@ public class ImsRcsManager implements RegistrationManager { /** * @hide */ - @Override + // @Override add back to RegistrationManager interface once public. @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerImsRegistrationCallback( @NonNull @CallbackExecutor Executor executor, - @NonNull RegistrationCallback c) + @NonNull RegistrationManager.RegistrationCallback c) throws ImsException { if (c == null) { throw new IllegalArgumentException("Must include a non-null RegistrationCallback."); @@ -204,7 +204,7 @@ public class ImsRcsManager implements RegistrationManager { /** * @hide */ - @Override + // @Override add back to RegistrationManager interface once public. @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterImsRegistrationCallback( @NonNull RegistrationManager.RegistrationCallback c) { @@ -228,10 +228,10 @@ public class ImsRcsManager implements RegistrationManager { /** * @hide */ - @Override + // @Override add back to RegistrationManager interface once public. @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull @CallbackExecutor Executor executor, - @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) { + @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback) { if (stateCallback == null) { throw new IllegalArgumentException("Must include a non-null stateCallback."); } @@ -260,7 +260,6 @@ public class ImsRcsManager implements RegistrationManager { /** * @hide */ - @Override @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor, @NonNull @AccessNetworkConstants.TransportType @@ -347,8 +346,7 @@ public class ImsRcsManager implements RegistrationManager { * inactive subscription, it will result in a no-op. * @param c The RCS {@link AvailabilityCallback} to be removed. * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback) - * @throws ImsException if the IMS service is not available when calling this method - * {@link ImsRcsController#unregisterRcsAvailabilityCallback()}. + * @throws ImsException if the IMS service is not available when calling this method. * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ @@ -390,8 +388,7 @@ public class ImsRcsManager implements RegistrationManager { * rather the subscription is capable of this service over IMS. * @see #isAvailable(int) * @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL - * @throws ImsException if the IMS service is not available when calling this method - * {@link ImsRcsController#isCapable(int, int)}. + * @throws ImsException if the IMS service is not available when calling this method. * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ @@ -424,9 +421,8 @@ public class ImsRcsManager implements RegistrationManager { * @return true if the RCS capability is currently available for the associated subscription, * false otherwise. If the capability is available, IMS is registered and the service is * currently available over IMS. - * @see #isCapable(int) - * @throws ImsException if the IMS service is not available when calling this method - * {@link ImsRcsController#isAvailable(int, int)}. + * @see #isCapable(int, int) + * @throws ImsException if the IMS service is not available when calling this method. * See {@link ImsException#getCode()} for more information on the error codes. * @hide */ diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 6c8dc00cb579..7d20d0d09dc2 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -758,6 +758,13 @@ public class WifiManager { @SystemApi public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; + /** + * Client disconnected for unspecified reason. This could for example be because the AP is being + * shut down. + * @hide + */ + public static final int SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED = 2; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"IFACE_IP_MODE_"}, value = { |