Merge "Packages on IncrementalFS can't have oat dir." into rvc-dev
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index 80308d2..0d3f420 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -182,12 +182,6 @@
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 536b71a..4eeae57 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.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 @@
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 @@
* @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 @@
* @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 @@
* @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 @@
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 80e2d00..3f8bee9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -48759,12 +48759,8 @@
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 755380e..ca291f3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -126,6 +126,7 @@
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 @@
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 acd9ec3..f30ed17 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -104,7 +104,7 @@
"src/subscriber/IncidentdReporter.cpp",
"src/subscriber/SubscriberReporter.cpp",
"src/uid_data.proto",
- "src/utils/NamedLatch.cpp",
+ "src/utils/MultiConditionTrigger.cpp",
],
local_include_dirs: [
@@ -295,7 +295,12 @@
//TODO(b/153588990): Remove when the build system properly separates
//32bit and 64bit architectures.
- compile_multilib: "prefer32",
+ compile_multilib: "both",
+ multilib: {
+ lib64: {
+ suffix: "64",
+ }
+ },
cflags: [
"-Wall",
@@ -366,7 +371,7 @@
"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 e251399..ba4cf11 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -382,10 +382,6 @@
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 @@
// 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 @@
// 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 d914ab2..cc48d50 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -389,15 +389,24 @@
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 @@
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 @@
}
- 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 @@
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::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 97512ed..ffd83ba 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -120,6 +120,11 @@
/* 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 ae7a8d0..bd9f7a5 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -118,7 +118,8 @@
}
})),
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 @@
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 @@
packageNames,
installers);
- mBootCompleteLatch.countDown(kUidMapReceivedTag);
+ mBootCompleteTrigger.markComplete(kUidMapReceivedTag);
VLOG("StatsService::informAllUidData UidData proto parsed successfully.");
return Status::ok();
}
@@ -1066,7 +1061,7 @@
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::bootCompleted was called");
- mBootCompleteLatch.countDown(kBootCompleteTag);
+ mBootCompleteTrigger.markComplete(kBootCompleteTag);
return Status::ok();
}
@@ -1222,20 +1217,11 @@
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 79324d8..b49fa1d 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 @@
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 @@
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 @@
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 933f48d..3618bb0 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -67,8 +67,14 @@
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/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index ebe9610..cfd5d14 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -252,9 +252,13 @@
mPullUidProviders[configKey] = provider;
}
-void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey) {
+void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
+ wp<PullUidProvider> provider) {
std::lock_guard<std::mutex> _l(mLock);
- mPullUidProviders.erase(configKey);
+ const auto& it = mPullUidProviders.find(configKey);
+ if (it != mPullUidProviders.end() && it->second == provider) {
+ mPullUidProviders.erase(it);
+ }
}
void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index ab0ccee..5e18aaa 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -78,11 +78,12 @@
wp<PullDataReceiver> receiver);
// Registers a pull uid provider for the config key. When pulling atoms, it will be used to
- // determine which atoms to pull from.
+ // determine which uids to pull from.
virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider);
// Unregister a pull uid provider.
- virtual void UnregisterPullUidProvider(const ConfigKey& configKey);
+ virtual void UnregisterPullUidProvider(const ConfigKey& configKey,
+ wp<PullUidProvider> provider);
// Verify if we know how to pull for this matcher
bool PullerForMatcherExists(int tagId) const;
@@ -180,6 +181,8 @@
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
+
+ FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
};
} // namespace statsd
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index db637b1..46f5dbd 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -54,6 +54,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
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 @@
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 ff31e9e..21e524a 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -461,6 +461,16 @@
*/
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 @@
long emptyData = 0;
long registeredCount = 0;
long unregisteredCount = 0;
+ int32_t atomErrorCount = 0;
} PulledAtomStats;
typedef struct {
@@ -546,6 +557,12 @@
// 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 @@
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 @@
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 61cd017..eb830e1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -114,14 +114,6 @@
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 @@
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 @@
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 @@
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 41fdcc2..dedcfaf 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -70,7 +70,7 @@
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const InstallTrainInfo& installTrainInfo);
- ~LogEvent();
+ ~LogEvent() {}
/**
* Get the timestamp associated with this event.
@@ -184,6 +184,12 @@
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 @@
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 @@
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 @@
// 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 @@
// 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/main.cpp b/cmds/statsd/src/main.cpp
index e394533..cd9c4e5 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -37,6 +37,7 @@
using std::make_shared;
shared_ptr<StatsService> gStatsService = nullptr;
+sp<StatsSocketListener> gSocketListener = nullptr;
void signalHandler(int sig) {
if (sig == SIGPIPE) {
@@ -47,6 +48,7 @@
return;
}
+ if (gSocketListener != nullptr) gSocketListener->stopListener();
if (gStatsService != nullptr) gStatsService->Terminate();
ALOGW("statsd terminated on receiving signal %d.", sig);
exit(1);
@@ -92,11 +94,11 @@
gStatsService->Startup();
- sp<StatsSocketListener> socketListener = new StatsSocketListener(eventQueue);
+ gSocketListener = new StatsSocketListener(eventQueue);
ALOGI("Statsd starts to listen to socket.");
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
- if (socketListener->startListener(600)) {
+ if (gSocketListener->startListener(600)) {
exit(1);
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index a4711e8..f9a8842 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -109,10 +109,11 @@
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 cc48f99..6f84076 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -154,12 +154,14 @@
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 aa0cae2..2eb584b 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -73,18 +73,23 @@
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 @@
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 6aba13ca..91c98ea2 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -141,30 +141,25 @@
}
/**
- * 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 @@
// 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 d832ed8..e03de0b 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -189,7 +189,7 @@
StateManager::getInstance().unregisterListener(atomId, it);
}
}
- mPullerManager->UnregisterPullUidProvider(mConfigKey);
+ mPullerManager->UnregisterPullUidProvider(mConfigKey, this);
VLOG("~MetricsManager()");
}
@@ -231,8 +231,8 @@
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::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 @@
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 1fd6572..ef03d20 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -70,6 +70,8 @@
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 e9273dc..c8dc8cc 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -69,8 +69,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 b7f314a..b63713b 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -51,7 +51,7 @@
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 f4247ec..868247b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -425,6 +425,7 @@
message AtomStats {
optional int32 tag = 1;
optional int32 count = 2;
+ optional int32 error_count = 3;
}
repeated AtomStats atom_stats = 7;
@@ -460,6 +461,7 @@
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 5635313..f9fddc8 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_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 @@
(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/MultiConditionTrigger.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
new file mode 100644
index 0000000..43a6933
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+#define DEBUG false // STOPSHIP if true
+
+#include "MultiConditionTrigger.h"
+
+#include <thread>
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+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 MultiConditionTrigger::markComplete(const string& conditionName) {
+ bool doTrigger = false;
+ {
+ lock_guard<mutex> lg(mMutex);
+ if (mCompleted) {
+ return;
+ }
+ mRemainingConditionNames.erase(conditionName);
+ mCompleted = mRemainingConditionNames.empty();
+ doTrigger = mCompleted;
+ }
+ if (doTrigger) {
+ std::thread executorThread([this] { mTrigger(); });
+ executorThread.detach();
+ }
+}
+} // 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 0000000..51f6029
--- /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.cpp b/cmds/statsd/src/utils/NamedLatch.cpp
deleted file mode 100644
index 6e77977..0000000
--- a/cmds/statsd/src/utils/NamedLatch.cpp
+++ /dev/null
@@ -1,48 +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.
- */
-#define DEBUG false // STOPSHIP if true
-
-#include "NamedLatch.h"
-
-using namespace std;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-NamedLatch::NamedLatch(const set<string>& eventNames) : mRemainingEventNames(eventNames) {
-}
-
-void NamedLatch::countDown(const string& eventName) {
- bool notify = false;
- {
- lock_guard<mutex> lg(mMutex);
- mRemainingEventNames.erase(eventName);
- notify = mRemainingEventNames.empty();
- }
- if (notify) {
- mConditionVariable.notify_all();
- }
-}
-
-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/NamedLatch.h b/cmds/statsd/src/utils/NamedLatch.h
deleted file mode 100644
index 70238370..0000000
--- 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 bb4578d..e52e2d0 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -352,7 +352,7 @@
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/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 1075fe4..a44541d 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -528,7 +528,7 @@
}));
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1);
- EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey)).Times(1);
+ EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1);
sp<AlarmMonitor> anomalyAlarmMonitor;
sp<AlarmMonitor> periodicAlarmMonitor;
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index d29394b..b809286 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -301,6 +301,29 @@
EXPECT_TRUE(noData);
}
+TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) {
+ // Setup simple config key corresponding to empty config.
+ sp<UidMap> m = new UidMap();
+ sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+ sp<AlarmMonitor> anomalyAlarmMonitor;
+ sp<AlarmMonitor> subscriberAlarmMonitor;
+ StatsLogProcessor p(
+ m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+ [](const ConfigKey& key) { return true; },
+ [](const int&, const vector<int64_t>&) { return true; });
+ ConfigKey key(3, 4);
+ StatsdConfig config = MakeConfig(false);
+ p.OnConfigUpdated(0, key, config);
+ EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+ config.add_default_pull_packages("AID_STATSD");
+ p.OnConfigUpdated(5, key, config);
+ EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+ p.OnConfigRemoved(key);
+ EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+}
+
TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) {
int uid = 1111;
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index b173ee0..9117623 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -89,6 +89,7 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 129fafa..cdde603 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -486,6 +486,41 @@
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 65f8de6..8131725 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -38,9 +38,9 @@
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 @@
} // 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 30f8159..8ef2519 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -41,10 +41,10 @@
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 @@
} // 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 @@
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 @@
*/
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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 42d0d5d..9d2ec88 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -42,6 +42,8 @@
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 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 @@
}
} // 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 @@
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 @@
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 @@
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 @@
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 @@
.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 @@
.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 @@
.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 3b4d646..f493cc4 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -41,6 +41,8 @@
namespace os {
namespace statsd {
+namespace {
+
const ConfigKey kConfigKey(0, 12345);
const int tagId = 1;
const int64_t metricId = 123;
@@ -58,10 +60,18 @@
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 @@
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 @@
}
};
+// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 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 @@
// 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// No buckets, we had a failure.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
}
/*
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 69f7e3f..be410b1 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -44,7 +44,8 @@
vector<std::shared_ptr<LogEvent>>* data, bool useUids));
MOCK_METHOD2(RegisterPullUidProvider,
void(const ConfigKey& configKey, wp<PullUidProvider> provider));
- MOCK_METHOD1(UnregisterPullUidProvider, void(const ConfigKey& configKey));
+ MOCK_METHOD2(UnregisterPullUidProvider,
+ void(const ConfigKey& configKey, wp<PullUidProvider> provider));
};
class MockUidMap : public UidMap {
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 37b9889..4d68ea2 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -42,6 +42,8 @@
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 0000000..db402a0
--- /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 de48a13..0000000
--- 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/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 67334f5..18e5c3d 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -494,6 +494,13 @@
*/
public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
+ /**
+ * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+ * play/stop media
+ * @hide
+ */
+ public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index 9a3dad2..623734e 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -30,7 +30,6 @@
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 @@
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 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 1b941de..1cc63da 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -23,8 +23,10 @@
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 @@
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/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 21b56d3..696ee9f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -375,7 +375,6 @@
String mInstrumentedLibDir = null;
boolean mSystemThread = false;
boolean mSomeActivitiesChanged = false;
- boolean mUpdatingSystemConfig = false;
/* package */ boolean mHiddenApiWarningShown = false;
// These can be accessed by multiple threads; mResourcesManager is the lock.
@@ -587,8 +586,11 @@
throw new IllegalStateException(
"Received config update for non-existing activity");
}
+ // Given alwaysReportChange=false because the configuration is from view root, the
+ // activity may not be able to handle the changes. In that case the activity will be
+ // relaunched immediately, then Activity#onConfigurationChanged shouldn't be called.
activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
- newDisplayId);
+ newDisplayId, false /* alwaysReportChange */);
};
}
@@ -2029,12 +2031,7 @@
break;
}
case APPLICATION_INFO_CHANGED:
- mUpdatingSystemConfig = true;
- try {
- handleApplicationInfoChanged((ApplicationInfo) msg.obj);
- } finally {
- mUpdatingSystemConfig = false;
- }
+ handleApplicationInfoChanged((ApplicationInfo) msg.obj);
break;
case RUN_ISOLATED_ENTRY_POINT:
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
@@ -4467,7 +4464,8 @@
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig);
+ performConfigurationChangedForActivity(r, r.newConfig,
+ false /* alwaysReportChange */);
if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
+ r.activity.mCurrentConfig);
@@ -4787,7 +4785,8 @@
}
}
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig);
+ performConfigurationChangedForActivity(r, r.newConfig,
+ false /* alwaysReportChange */);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+ r.activityInfo.name + " with new config "
+ r.activity.mCurrentConfig);
@@ -5447,11 +5446,12 @@
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
+ * @param alwaysReportChange If the configuration is changed, always report to activity.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig) {
- performConfigurationChangedForActivity(r, newBaseConfig,
- r.activity.getDisplayId(), false /* movedToDifferentDisplay */);
+ Configuration newBaseConfig, boolean alwaysReportChange) {
+ performConfigurationChangedForActivity(r, newBaseConfig, r.activity.getDisplayId(),
+ false /* movedToDifferentDisplay */, alwaysReportChange);
}
/**
@@ -5464,16 +5464,19 @@
* {@link ActivityClientRecord#overrideConfig}.
* @param displayId The id of the display where the Activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+ * @param alwaysReportChange If the configuration is changed, always report to activity.
* @return {@link Configuration} instance sent to client, null if not sent.
*/
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
+ Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay,
+ boolean alwaysReportChange) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
- r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
+ r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay,
+ alwaysReportChange);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}
@@ -5529,11 +5532,12 @@
* ActivityManager.
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+ * @param alwaysReportChange If the configuration is changed, always report to activity.
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId,
- boolean movedToDifferentDisplay) {
+ boolean movedToDifferentDisplay, boolean alwaysReportChange) {
if (activity == null) {
throw new IllegalArgumentException("No activity provided.");
}
@@ -5556,7 +5560,7 @@
// Always send the task-level config changes. For system-level configuration, if
// this activity doesn't handle any of the config changes, then don't bother
// calling onConfigurationChanged as we're going to destroy it.
- if (!mUpdatingSystemConfig
+ if (alwaysReportChange
|| (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
|| !REPORT_TO_ACTIVITY) {
shouldChangeConfig = true;
@@ -5638,12 +5642,7 @@
public void handleConfigurationChanged(Configuration config) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
mCurDefaultDisplayDpi = config.densityDpi;
- mUpdatingSystemConfig = true;
- try {
- handleConfigurationChanged(config, null /* compat */);
- } finally {
- mUpdatingSystemConfig = false;
- }
+ handleConfigurationChanged(config, null /* compat */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -5725,7 +5724,7 @@
// config and avoid onConfigurationChanged if it hasn't changed.
Activity a = (Activity) cb;
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
- config);
+ config, false /* alwaysReportChange */);
} else if (!equivalent) {
performConfigurationChanged(cb, config);
} else {
@@ -5832,16 +5831,28 @@
}
}
+ @Override
+ public void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId) {
+ handleActivityConfigurationChanged(activityToken, overrideConfig, displayId,
+ // This is the only place that uses alwaysReportChange=true. The entry point should
+ // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
+ // has confirmed the activity should handle the configuration instead of relaunch.
+ // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
+ // something wrong from server side.
+ true /* alwaysReportChange */);
+ }
+
/**
* Handle new activity configuration and/or move to a different display.
* @param activityToken Target activity token.
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
+ * @param alwaysReportChange If the configuration is changed, always report to activity.
*/
- @Override
- public void handleActivityConfigurationChanged(IBinder activityToken,
- Configuration overrideConfig, int displayId) {
+ void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig,
+ int displayId, boolean alwaysReportChange) {
ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
@@ -5880,14 +5891,15 @@
+ ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
- mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
+ mCompatConfiguration, displayId, true /* movedToDifferentDisplay */,
+ alwaysReportChange);
if (viewRoot != null) {
viewRoot.onMovedToDisplay(displayId, reportedConfig);
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name + ", config=" + overrideConfig);
- performConfigurationChangedForActivity(r, mCompatConfiguration);
+ performConfigurationChangedForActivity(r, mCompatConfiguration, alwaysReportChange);
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8edf03d..af02783 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2301,11 +2301,11 @@
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 @@
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 @@
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 @@
if (mChannelId != null) {
parcel.writeInt(1);
- parcel.writeString(mChannelId);
+ parcel.writeString8(mChannelId);
} else {
parcel.writeInt(0);
}
@@ -2811,7 +2811,7 @@
if (mShortcutId != null) {
parcel.writeInt(1);
- parcel.writeString(mShortcutId);
+ parcel.writeString8(mShortcutId);
} else {
parcel.writeInt(0);
}
@@ -8873,7 +8873,7 @@
}
mDesiredHeightResId = in.readInt();
if (in.readInt() != 0) {
- mShortcutId = in.readString();
+ mShortcutId = in.readString8();
}
}
@@ -9029,7 +9029,7 @@
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 2feb927..9f8d3c4 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -102,7 +102,7 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
* @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 @@
* @see Notification#getBubbleMetadata()
*/
public boolean canBubble() {
+ return mAllowBubbles == ALLOW_BUBBLE_ON;
+ }
+
+ /**
+ * @hide
+ */
+ public int getAllowBubbles() {
return mAllowBubbles;
}
@@ -872,7 +891,7 @@
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 @@
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 @@
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 0173731..c7a2a1e 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -20,6 +20,7 @@
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 @@
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 9c806fa..1923bf3 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1206,7 +1206,7 @@
}
} else {
dest.writeInt(PARCEL_TYPE_STRING);
- dest.writeString(text);
+ dest.writeString8(text);
}
}
@@ -1215,7 +1215,7 @@
*/
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 494d2ae..1fb426e 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -91,8 +91,8 @@
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 @@
} 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 @@
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 @@
}
if (mSelection != null) {
dest.writeInt(1);
- dest.writeString(mSelection);
+ dest.writeString8(mSelection);
} else {
dest.writeInt(0);
}
@@ -591,7 +591,7 @@
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 @@
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 b1d6c83..def150a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -888,8 +888,8 @@
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 void writeToParcel(Parcel dest, int flags) {
- dest.writeString(packageName);
- dest.writeString(resourceName);
+ dest.writeString8(packageName);
+ dest.writeString8(resourceName);
}
@Override
@@ -10807,12 +10807,12 @@
}
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 @@
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 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 @@
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 f25ce76..b1f8869 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1206,17 +1206,17 @@
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 @@
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 @@
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 @@
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 @@
gravity = source.readInt();
minWidth = source.readInt();
minHeight = source.readInt();
- windowLayoutAffinity = source.readString();
+ windowLayoutAffinity = source.readString8();
}
/**
@@ -1476,7 +1476,7 @@
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 b6706011..b37521b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1704,10 +1704,10 @@
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 @@
} 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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 362098c..628bcd7 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -197,8 +197,8 @@
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 @@
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 9f3ab77..89269e0 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -108,7 +108,7 @@
@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 @@
};
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/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 574a1ee..745a6c1 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -157,21 +157,21 @@
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 @@
@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 85c698f..bb56ef7 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -441,14 +441,14 @@
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 @@
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 @@
@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 @@
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 7fd5531..d41ace5 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -422,8 +422,8 @@
}
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 @@
}
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/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1f2ff28..8a57f82 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1695,7 +1695,7 @@
}
// Check to see if overlay should be excluded based on system property condition
- if (!checkRequiredSystemProperty(requiredSystemPropertyName,
+ if (!checkRequiredSystemProperties(requiredSystemPropertyName,
requiredSystemPropertyValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ codePath + ": overlay ignored due to required system property: "
@@ -1997,7 +1997,7 @@
}
// check to see if overlay should be excluded based on system property condition
- if (!checkRequiredSystemProperty(propName, propValue)) {
+ if (!checkRequiredSystemProperties(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
+ pkg.baseCodePath+ ": overlay ignored due to required system property: "
+ propName + " with value: " + propValue);
@@ -2427,24 +2427,42 @@
/**
* Returns {@code true} if both the property name and value are empty or if the given system
- * property is set to the specified value. In all other cases, returns {@code false}
+ * property is set to the specified value. Properties can be one or more, and if properties are
+ * more than one, they must be separated by comma, and count of names and values must be equal,
+ * and also every given system property must be set to the corresponding value.
+ * In all other cases, returns {@code false}
*/
- public static boolean checkRequiredSystemProperty(String propName, String propValue) {
- if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
- if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
+ public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames,
+ @Nullable String rawPropValues) {
+ if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) {
+ if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) {
// malformed condition - incomplete
- Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
- + "=" + propValue + "' - require both requiredSystemPropertyName"
- + " AND requiredSystemPropertyValue to be specified.");
+ Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue to be specified.");
return false;
}
// no valid condition set - so no exclusion criteria, overlay will be included.
return true;
}
- // check property value - make sure it is both set and equal to expected value
- final String currValue = SystemProperties.get(propName);
- return (currValue != null && currValue.equals(propValue));
+ final String[] propNames = rawPropNames.split(",");
+ final String[] propValues = rawPropValues.split(",");
+
+ if (propNames.length != propValues.length) {
+ Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue lists to have the same size.");
+ return false;
+ }
+ for (int i = 0; i < propNames.length; i++) {
+ // Check property value: make sure it is both set and equal to expected value
+ final String currValue = SystemProperties.get(propNames[i]);
+ if (!TextUtils.equals(currValue, propValues[i])) {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java
index 44def33..8212224 100644
--- a/core/java/android/content/pm/PackageParserCacheHelper.java
+++ b/core/java/android/content/pm/PackageParserCacheHelper.java
@@ -78,10 +78,19 @@
/**
* 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 @@
* 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 @@
}
}
+ @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 07d42dc..3984ade 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -145,9 +145,9 @@
@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 @@
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 5f90b6c..d3f9e24 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -244,7 +244,7 @@
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 @@
private ServiceInfo(Parcel source) {
super(source);
- permission = source.readString();
+ permission = source.readString8();
flags = source.readInt();
mForegroundServiceType = source.readInt();
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 27399e4..2f416a2 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -413,7 +413,7 @@
}
// Check to see if overlay should be excluded based on system property condition
- if (!PackageParser.checkRequiredSystemProperty(requiredSystemPropertyName,
+ if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName,
requiredSystemPropertyValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ codePath + ": overlay ignored due to required system property: "
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 29ece49..88f4c31 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -84,7 +84,6 @@
import android.os.Bundle;
import android.os.FileUtils;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.os.Trace;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
@@ -2348,7 +2347,7 @@
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName);
String propValue = sa.getString(
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
- if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
+ if (!PackageParser.checkRequiredSystemProperties(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + target + " and "
+ pkg.getBaseCodePath()
+ ": overlay ignored due to required system property: "
@@ -2522,24 +2521,6 @@
}
}
- private static boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
- if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
- if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
- // malformed condition - incomplete
- Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
- + "=" + propValue + "' - require both requiredSystemPropertyName"
- + " AND requiredSystemPropertyValue to be specified.");
- return false;
- }
- // no valid condition set - so no exclusion criteria, overlay will be included.
- return true;
- }
-
- // check property value - make sure it is both set and equal to expected value
- final String currValue = SystemProperties.get(propName);
- return (currValue != null && currValue.equals(propValue));
- }
-
/**
* This is a pre-density application which will get scaled - instead of being pixel perfect.
* This type of application is not resizable.
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index f7c96a3..2f67f6d 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -228,19 +228,26 @@
} catch (SQLiteCantOpenDatabaseException e) {
String message = String.format("Cannot open database '%s'", file);
- final Path path = FileSystems.getDefault().getPath(file);
- final Path dir = path.getParent();
+ try {
+ // Try to diagnose for common reasons. If something fails in here, that's fine;
+ // just swallow the exception.
- 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";
+ 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 462110f..ad9bf07 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -65,21 +65,23 @@
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 525bbfd..1cb4fe8 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -500,7 +500,7 @@
}
static Uri readFrom(Parcel parcel) {
- return new StringUri(parcel.readString());
+ return new StringUri(parcel.readString8());
}
public int describeContents() {
@@ -509,7 +509,7 @@
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 @@
static Uri readFrom(Parcel parcel) {
return new OpaqueUri(
- parcel.readString(),
+ parcel.readString8(),
Part.readFrom(parcel),
Part.readFrom(parcel)
);
@@ -887,7 +887,7 @@
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 @@
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 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 @@
+ mCanonicalRepresentation + ")");
}
parcel.writeInt(mCanonicalRepresentation);
- parcel.writeString(canonicalValue);
+ parcel.writeString8(canonicalValue);
}
}
@@ -2060,7 +2060,7 @@
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 @@
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 f0b7b5f..93f6607 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -307,7 +307,9 @@
@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 @@
@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 @@
* 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 @@
* 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 @@
* 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 @@
* @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 @@
*/
@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 @@
*
* @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 aee32ed..a1a11ed 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2447,7 +2447,12 @@
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 df4ead1..28639b3 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -748,7 +748,7 @@
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 @@
} 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 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 bdc8ba8..df89130 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -153,7 +153,7 @@
@Override
protected boolean isRequestedVisibleAwaitingControl() {
- return mIsRequestedVisibleAwaitingControl;
+ return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
}
private boolean isDummyOrEmptyEditor(EditorInfo info) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 68a185d..9d275cd 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3779,7 +3779,9 @@
mNextReportConsumeBLAST = true;
mNextDrawUseBLASTSyncTransaction = false;
- mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+ }
}
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc380f3..e4dbd637 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -514,33 +514,24 @@
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}
+ * Enum listing the possible sources from which a screenshot was originated. Used for logging.
+ *
* @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.
- * @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 7f6c0d2..7016c5c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1213,7 +1213,7 @@
BitmapReflectionAction(Parcel in) {
viewId = in.readInt();
- methodName = in.readString();
+ methodName = in.readString8();
bitmapId = in.readInt();
bitmap = mBitmapCache.getBitmapForId(bitmapId);
}
@@ -1221,7 +1221,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- dest.writeString(methodName);
+ dest.writeString8(methodName);
dest.writeInt(bitmapId);
}
@@ -1282,7 +1282,7 @@
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 @@
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 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 @@
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/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index b1e356d..bcb32fb 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -437,6 +437,9 @@
resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
emptyStateView.setVisibility(View.VISIBLE);
+ View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
+ setupContainerPadding(container);
+
TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
title.setText(titleRes);
@@ -463,6 +466,12 @@
activeListAdapter.markTabLoaded();
}
+ /**
+ * Sets up the padding of the view containing the empty state screens.
+ * <p>This method is meant to be overridden so that subclasses can customize the padding.
+ */
+ protected void setupContainerPadding(View container) {}
+
private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
ProfileDescriptor descriptor = getItem(
userHandleToPageIndex(activeListAdapter.getUserHandle()));
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 3eb0923..2c48925 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.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 @@
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 @@
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 @@
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 @@
}
@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 @@
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/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 1bc982c..e20e99b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -146,6 +146,7 @@
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
+ private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
private boolean mRegistered;
@@ -844,9 +845,19 @@
}
@Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ ViewPager viewPager = findViewById(R.id.profile_pager);
+ outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+ }
+
+ @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
resetButtonBar();
+ ViewPager viewPager = findViewById(R.id.profile_pager);
+ viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+ mMultiProfilePagerAdapter.clearInactiveProfileCache();
}
private boolean isHttpSchemeAndViewAction(Intent intent) {
@@ -1585,6 +1596,7 @@
TabHost tabHost = findViewById(R.id.profile_tabhost);
tabHost.setup();
ViewPager viewPager = findViewById(R.id.profile_pager);
+ viewPager.setSaveEnabled(false);
TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
.setContent(R.id.profile_pager)
.setIndicator(getString(R.string.resolver_personal_tab));
@@ -1745,22 +1757,33 @@
return;
}
final ViewGroup buttonLayout = findViewById(R.id.button_bar);
- if (buttonLayout != null) {
- buttonLayout.setVisibility(View.VISIBLE);
-
- if (!useLayoutWithDefault()) {
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
- }
- mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
- mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
-
- resetAlwaysOrOnceButtonBar();
- } else {
+ if (buttonLayout == null) {
Log.e(TAG, "Layout unexpectedly does not have a button bar");
+ return;
}
+ ResolverListAdapter activeListAdapter =
+ mMultiProfilePagerAdapter.getActiveListAdapter();
+ View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider);
+ if (activeListAdapter.isTabLoaded()
+ && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
+ buttonLayout.setVisibility(View.INVISIBLE);
+ buttonBarDivider.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ buttonBarDivider.setVisibility(View.VISIBLE);
+ buttonLayout.setVisibility(View.VISIBLE);
+
+ if (!useLayoutWithDefault()) {
+ int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+ buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
+ }
+ mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+ mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
+
+ resetAlwaysOrOnceButtonBar();
}
private void resetAlwaysOrOnceButtonBar() {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index ad31d8b..885d1bb 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -213,6 +213,12 @@
/* subtitleRes */ 0);
}
+ @Override
+ protected void setupContainerPadding(View container) {
+ container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
+ container.getPaddingRight(), /* bottom */ 0);
+ }
+
class ResolverProfileDescriptor extends ProfileDescriptor {
private ResolverListAdapter resolverListAdapter;
final ListView listView;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 43bd4a6..6432475 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6093,7 +6093,8 @@
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 ebfea450..56a6db9 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -46,6 +46,9 @@
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 @@
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 7cff90b..adadc5e 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.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 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 @@
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 @@
* 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 @@
}
/**
- * 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 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 @@
* 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 @@
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 @@
}
};
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/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 31527e8..fb2ecf3 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -1084,6 +1084,7 @@
protected Parcelable onSaveInstanceState() {
final SavedState ss = new SavedState(super.onSaveInstanceState());
ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0;
+ ss.mCollapsibleHeightReserved = mCollapsibleHeightReserved;
return ss;
}
@@ -1092,6 +1093,7 @@
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mOpenOnLayout = ss.open;
+ mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
}
public static class LayoutParams extends MarginLayoutParams {
@@ -1142,6 +1144,7 @@
static class SavedState extends BaseSavedState {
boolean open;
+ private int mCollapsibleHeightReserved;
SavedState(Parcelable superState) {
super(superState);
@@ -1150,12 +1153,14 @@
private SavedState(Parcel in) {
super(in);
open = in.readInt() != 0;
+ mCollapsibleHeightReserved = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(open ? 1 : 0);
+ out.writeInt(mCollapsibleHeightReserved);
}
public static final Parcelable.Creator<SavedState> CREATOR =
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ba7aef7..b51d4f5 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1244,12 +1244,11 @@
{
if (mExitWithoutCleanup) {
ALOGI("VM exiting with result code %d, cleanup skipped.", code);
- ::_exit(code);
} else {
ALOGI("VM exiting with result code %d.", code);
onExit(code);
- ::exit(code);
}
+ ::_exit(code);
}
void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 483b455..e7a2fb4 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -273,7 +273,28 @@
}
}
-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 @@
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 @@
// @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 @@
// @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 924dc4b..21985f0 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -119,6 +119,7 @@
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 @@
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 @@
} 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/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 3e007e4..997829e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2523,16 +2523,10 @@
// OS: R
PANEL_ADD_WIFI_NETWORKS = 1809;
- // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+ // OPEN: Settings > Accessibility > Enable the feature or shortcut > Show tutorial dialog
// CATEGORY: SETTINGS
// OS: R
- DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
-
- // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
- // gesture mode
- // CATEGORY: SETTINGS
- // OS: R
- DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+ DIALOG_ACCESSIBILITY_TUTORIAL = 1810;
// OPEN: Settings > Accessibility > Edit shortcut dialog
// CATEGORY: SETTINGS
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6e8b9de..ee25ac2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1111,12 +1111,13 @@
grants your app this permission. If you don't need this permission, be sure your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
targetSdkVersion}</a> is 4 or higher.
- <p>Protection level: normal
+ <p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_readPhoneState"
android:description="@string/permdesc_readPhoneState"
- android:protectionLevel="normal" />
+ android:protectionLevel="dangerous" />
<!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 5890bed..fe11769 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -24,6 +24,7 @@
android:paddingStart="24dp"
android:paddingEnd="24dp">
<RelativeLayout
+ android:id="@+id/resolver_empty_state_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="48dp"
@@ -43,6 +44,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/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index b754e0c..76ecefc 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -112,59 +112,61 @@
</FrameLayout>
</LinearLayout>
</TabHost>
-
- <View
- android:layout_alwaysShow="true"
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorBackgroundFloating"
- android:foreground="?attr/dividerVertical" />
-
<LinearLayout
- android:id="@+id/button_bar"
- android:visibility="gone"
- style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_ignoreOffset="true"
android:layout_alwaysShow="true"
- android:layout_hasNestedScrollIndicator="true"
- android:gravity="end|center_vertical"
- android:orientation="horizontal"
- android:layoutDirection="locale"
- android:measureWithLargestChild="true"
- android:background="?attr/colorBackgroundFloating"
- android:paddingTop="@dimen/resolver_button_bar_spacing"
- android:paddingBottom="@dimen/resolver_button_bar_spacing"
- android:paddingStart="@dimen/resolver_edge_margin"
- android:paddingEnd="@dimen/resolver_small_margin"
- android:elevation="@dimen/resolver_elevation">
-
- <Button
- android:id="@+id/button_once"
- android:layout_width="wrap_content"
- android:layout_gravity="start"
- android:maxLines="2"
- style="?attr/buttonBarButtonStyle"
- android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:orientation="vertical"
+ android:background="?attr/colorBackgroundFloating">
+ <View
+ android:id="@+id/resolver_button_bar_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:visibility="gone"
+ style="?attr/buttonBarStyle"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAllCaps="false"
- android:enabled="false"
- android:text="@string/activity_resolver_use_once"
- android:onClick="onButtonClick" />
+ android:layout_ignoreOffset="true"
+ android:layout_hasNestedScrollIndicator="true"
+ android:gravity="end|center_vertical"
+ android:orientation="horizontal"
+ android:layoutDirection="locale"
+ android:measureWithLargestChild="true"
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
- <Button
- android:id="@+id/button_always"
- android:layout_width="wrap_content"
- android:layout_gravity="end"
- android:maxLines="2"
- style="?attr/buttonBarButtonStyle"
- android:fontFamily="@android:string/config_headlineFontFamilyMedium"
- android:textAllCaps="false"
- android:layout_height="wrap_content"
- android:enabled="false"
- android:text="@string/activity_resolver_use_always"
- android:onClick="onButtonClick" />
+ <Button
+ android:id="@+id/button_once"
+ android:layout_width="wrap_content"
+ android:layout_gravity="start"
+ android:maxLines="2"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:layout_height="wrap_content"
+ android:textAllCaps="false"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_once"
+ android:onClick="onButtonClick" />
+
+ <Button
+ android:id="@+id/button_always"
+ android:layout_width="wrap_content"
+ android:layout_gravity="end"
+ android:maxLines="2"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
+ android:layout_height="wrap_content"
+ android:enabled="false"
+ android:text="@string/activity_resolver_use_always"
+ android:onClick="onButtonClick" />
+ </LinearLayout>
</LinearLayout>
-
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a98553..6fac46e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1908,7 +1908,7 @@
<!-- The name of the package that will hold the call screening role by default. -->
<string name="config_defaultCallScreening" translatable="false"></string>
<!-- The name of the package that will hold the system gallery role. -->
- <string name="config_systemGallery" translatable="false">com.android.gallery</string>
+ <string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
<!-- The name of the package that will be allowed to change its components' label/icon. -->
<string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -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 05c00ce..4efd7ff 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" />
@@ -3921,6 +3921,8 @@
<java-symbol type="id" name="resolver_empty_state_button" />
<java-symbol type="id" name="resolver_empty_state_progress" />
<java-symbol type="id" name="resolver_tab_divider" />
+ <java-symbol type="id" name="resolver_button_bar_divider" />
+ <java-symbol type="id" name="resolver_empty_state_container" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps" />
<java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" />
<java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
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 0000000..daa90c2
--- /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 46873b9..dcb3e2f 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -87,4 +87,27 @@
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 5424b6f..43590ba 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 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 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 @@
}
@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 @@
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 cd6b3af..fe33cd8 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.Rect;
import android.os.Handler;
import android.os.Looper;
+import android.view.WindowManager;
import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +92,8 @@
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/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index cd878c5..228d03a 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -120,7 +120,7 @@
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -143,7 +143,7 @@
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -152,7 +152,7 @@
* must be called before each ANativeWindow_lock, or that call will fail. When producing
* image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
* OpenGL ES function call each frame.
- *
+ * <p>
* Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
* this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
* in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
@@ -222,15 +222,15 @@
* method. Both video and camera based image producers do override the size. This method may
* be used to set the image size when producing images with {@link android.graphics.Canvas} (via
* {@link android.view.Surface#lockCanvas}), or OpenGL ES (via an EGLSurface).
- *
+ * <p>
* The new default buffer size will take effect the next time the image producer requests a
* buffer to fill. For {@link android.graphics.Canvas} this will be the next time {@link
* android.view.Surface#lockCanvas} is called. For OpenGL ES, the EGLSurface should be
* destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated
- * (via eglCreateWindowSurface) to ensure that the new default size has taken effect.
- *
+ * (via {@code eglCreateWindowSurface}) to ensure that the new default size has taken effect.
+ * <p>
* The width and height parameters must be no greater than the minimum of
- * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see
+ * {@code GL_MAX_VIEWPORT_DIMS} and {@code GL_MAX_TEXTURE_SIZE} (see
* {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}).
* An error due to invalid dimensions might not be reported until
* updateTexImage() is called.
@@ -242,7 +242,7 @@
/**
* Update the texture image to the most recent frame from the image stream. This may only be
* called while the OpenGL ES context that owns the texture is current on the calling thread.
- * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+ * It will implicitly bind its texture to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
*/
public void updateTexImage() {
nativeUpdateTexImage();
@@ -251,6 +251,7 @@
/**
* Releases the the texture content. This is needed in single buffered mode to allow the image
* content producer to take ownership of the image buffer.
+ * <p>
* For more information see {@link #SurfaceTexture(int, boolean)}.
*/
public void releaseTexImage() {
@@ -263,7 +264,7 @@
* ES texture object will be deleted as a result of this call. After calling this method all
* calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
* a successful call to {@link #attachToGLContext} is made.
- *
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -279,8 +280,8 @@
* Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread. A
* new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
* that was current at the time of the last call to {@link #detachFromGLContext}. This new
- * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
- *
+ * texture is bound to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -297,16 +298,16 @@
/**
* Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
- * the most recent call to updateTexImage.
- *
+ * the most recent call to {@link #updateTexImage}.
+ * <p>
* This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
* and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
* that location from the texture. Sampling the texture outside of the range of this transform
* is undefined.
- *
+ * <p>
* The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
- * the glLoadMatrixf or glUniformMatrix4fv functions.
- *
+ * the {@code glLoadMatrixf} or {@code glUniformMatrix4fv} functions.
+ * <p>
* If the underlying buffer has a crop associated with it, the transformation will also include
* a slight scale to cut off a 1-texel border around the edge of the crop. This ensures that
* when the texture is bilinear sampled that no texels outside of the buffer's valid region
@@ -326,7 +327,7 @@
/**
* Retrieve the timestamp associated with the texture image set by the most recent call to
- * updateTexImage.
+ * {@link #updateTexImage}.
*
* <p>This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
* should be unaffected by time-of-day adjustments. The specific meaning and zero point of the
@@ -337,8 +338,8 @@
*
* <p>For camera sources, timestamps should be strictly monotonic. Timestamps from MediaPlayer
* sources may be reset when the playback position is set. For EGL and Vulkan producers, the
- * timestamp is the desired present time set with the EGL_ANDROID_presentation_time or
- * VK_GOOGLE_display_timing extensions.</p>
+ * timestamp is the desired present time set with the {@code EGL_ANDROID_presentation_time} or
+ * {@code VK_GOOGLE_display_timing} extensions.</p>
*/
public long getTimestamp() {
@@ -346,16 +347,17 @@
}
/**
- * release() frees all the buffers and puts the SurfaceTexture into the
+ * {@code release()} frees all the buffers and puts the SurfaceTexture into the
* 'abandoned' state. Once put in this state the SurfaceTexture can never
* leave it. When in the 'abandoned' state, all methods of the
- * IGraphicBufferProducer interface will fail with the NO_INIT error.
- *
+ * {@code IGraphicBufferProducer} interface will fail with the {@code NO_INIT}
+ * error.
+ * <p>
* Note that while calling this method causes all the buffers to be freed
* from the perspective of the the SurfaceTexture, if there are additional
* references on the buffers (e.g. if a buffer is referenced by a client or
* by OpenGL ES as a texture) then those buffer will remain allocated.
- *
+ * <p>
* Always call this method when you are done with SurfaceTexture. Failing
* to do so may delay resource deallocation for a significant amount of
* time.
@@ -367,7 +369,7 @@
}
/**
- * Returns true if the SurfaceTexture was released.
+ * Returns {@code true} if the SurfaceTexture was released.
*
* @see #release()
*/
@@ -400,7 +402,7 @@
}
/**
- * Returns true if the SurfaceTexture is single-buffered
+ * Returns {@code true} if the SurfaceTexture is single-buffered.
* @hide
*/
public boolean isSingleBuffered() {
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 1db2f63..b351b3d 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -95,9 +95,7 @@
/**
* 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 @@
* 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 @@
* 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 @@
* ? "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 @@
* 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 @@
* 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 @@
* @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 @@
* 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 @@
* <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 a1dfc77..4f834d2 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -78,17 +78,21 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 13552d6..37de2c4 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -34,23 +34,23 @@
/** 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 @@
*
* 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 @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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 @@
/**
* 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 e2a389b..c7aa328 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -41,15 +41,16 @@
* <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 14249cb..8e8dfaf 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -78,7 +78,8 @@
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 dc06153..a8b82ba 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -24,11 +24,15 @@
* @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 0d87736..52bac67 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -59,6 +59,8 @@
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 97d32d8..c652628 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -388,7 +388,7 @@
@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 @@
}
}
- private int getSessionResourceId() throws MediaCasException {
+ private int getSessionResourceHandle() throws MediaCasException {
validateInternalStates();
int[] sessionResourceHandle = new int[1];
@@ -881,14 +881,14 @@
"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 @@
* @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 @@
@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 @@
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 bd8fb96..0ea9624 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -237,9 +237,9 @@
} catch (RemoteException ex) {
Log.e(TAG, "Unable to unregister media router.", ex);
}
+ mStub = null;
}
mShouldUpdateRoutes = true;
- mStub = null;
}
}
@@ -690,6 +690,31 @@
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 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 @@
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 @@
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/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b694fd0..3b570b6 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -147,14 +147,16 @@
}
synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
+ if (mCallbackRecords.size() == 0) {
+ if (mClient != null) {
+ try {
+ mMediaRouterService.unregisterManager(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router manager", ex);
+ }
+ mClient = null;
}
- //TODO: clear mRoutes?
- mClient = null;
+ mRoutes.clear();
mPreferredFeaturesMap.clear();
}
}
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
index adf4d3d..022cfee 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
@@ -80,7 +80,8 @@
(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 7077cd1..487b444 100644
--- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -218,11 +218,11 @@
* <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 @@
*
* <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 @@
*
* <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 b4dcc5d..be102d8 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -362,17 +362,16 @@
* 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 @@
* <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/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index e6962a1..aba74e5 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -46,6 +46,7 @@
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForNative;
+import com.android.internal.annotations.VisibleForTesting;
import dalvik.system.CloseGuard;
@@ -408,7 +409,8 @@
}
@VisibleForNative
- private int beginSendObject(String path, int format, int parent, int storageId) {
+ @VisibleForTesting
+ public int beginSendObject(String path, int format, int parent, int storageId) {
MtpStorageManager.MtpObject parentObj =
parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
if (parentObj == null) {
@@ -452,7 +454,8 @@
}
@VisibleForNative
- private int getNumObjects(int storageID, int format, int parent) {
+ @VisibleForTesting
+ public int getNumObjects(int storageID, int format, int parent) {
List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
format, storageID);
if (objs == null) {
@@ -830,7 +833,8 @@
}
@VisibleForNative
- private boolean getThumbnailInfo(int handle, long[] outLongs) {
+ @VisibleForTesting
+ public boolean getThumbnailInfo(int handle, long[] outLongs) {
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
if (obj == null) {
return false;
@@ -866,7 +870,8 @@
}
@VisibleForNative
- private byte[] getThumbnailData(int handle) {
+ @VisibleForTesting
+ public byte[] getThumbnailData(int handle) {
MtpStorageManager.MtpObject obj = mManager.getObject(handle);
if (obj == null) {
return null;
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index ba75263..88c32a3 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -36,7 +36,7 @@
public MtpStorage(StorageVolume volume, int storageId) {
mStorageId = storageId;
- mPath = volume.getInternalPath();
+ mPath = volume.getPath();
mDescription = volume.getDescription(null);
mRemovable = volume.isRemovable();
mMaxFileSize = volume.getMaxFileSize();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 6ca564f..6a1e965 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -36,6 +36,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -47,6 +48,7 @@
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;
@@ -73,6 +75,8 @@
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;
@@ -160,6 +164,7 @@
});
MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+ assertNotNull(routeToRemove);
StubMediaRoute2ProviderService sInstance =
StubMediaRoute2ProviderService.getInstance();
@@ -171,6 +176,52 @@
assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void testGetRoutes_removedRoute_returnsCorrectRoutes() throws Exception {
+ CountDownLatch addedLatch = new CountDownLatch(1);
+ CountDownLatch removedLatch = new CountDownLatch(1);
+
+ RouteCallback routeCallback = new RouteCallback() {
+ // Used to ensure the removed route is added.
+ @Override
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ if (removedLatch.getCount() > 0) {
+ return;
+ }
+ addedLatch.countDown();
+ }
+
+ @Override
+ public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+ removedLatch.countDown();
+ }
+ };
+
+ mRouter2.registerRouteCallback(mExecutor, routeCallback,
+ new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+ mRouteCallbacks.add(routeCallback);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+ assertNotNull(routeToRemove);
+
+ StubMediaRoute2ProviderService sInstance =
+ StubMediaRoute2ProviderService.getInstance();
+ assertNotNull(sInstance);
+ sInstance.removeRoute(ROUTE_ID2);
+
+ // Wait until the route is removed.
+ assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ Map<String, MediaRoute2Info> newRoutes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ assertNull(newRoutes.get(ROUTE_ID2));
+
+ // Revert the removal.
+ sInstance.addRoute(routeToRemove);
+ assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mRouter2.unregisterRouteCallback(routeCallback);
+ }
+
/**
* Tests if we get proper routes for application that has special route feature.
*/
@@ -465,6 +516,56 @@
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);
@@ -475,8 +576,8 @@
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
- for (int i = 0; i < routes.size(); i++) {
- if (!routes.get(i).isSystemRoute()) {
+ for (MediaRoute2Info route : routes) {
+ if (!route.isSystemRoute()) {
addedLatch.countDown();
break;
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index 6d46ba5..4e398f2 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -65,9 +65,9 @@
public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
public static final String FEATURE_SAMPLE =
- "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ "com.android.mediaroutertest.FEATURE_SAMPLE";
public static final String FEATURE_SPECIAL =
- "com.android.mediarouteprovider.FEATURE_SPECIAL";
+ "com.android.mediaroutertest..FEATURE_SPECIAL";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
Map<String, String> mRouteIdToSessionId = new HashMap<>();
diff --git a/media/tests/MtpTests/res/raw/test_bad_thumb.jpg b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
new file mode 100644
index 0000000..e2e8ff4
--- /dev/null
+++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.mtp;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Tests for MtpDatabase functionality.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MtpDatabaseTest {
+ private static final String TAG = MtpDatabaseTest.class.getSimpleName();
+
+ private final Context mContext = InstrumentationRegistry.getContext();
+
+ private static final File mBaseDir = InstrumentationRegistry.getContext().getExternalCacheDir();
+ private static final String MAIN_STORAGE_DIR = mBaseDir.getPath() + "/" + TAG + "/";
+ private static final String TEST_DIRNAME = "/TestIs";
+
+ private static final int MAIN_STORAGE_ID = 0x10001;
+ private static final int SCND_STORAGE_ID = 0x20001;
+ private static final String MAIN_STORAGE_ID_STR = Integer.toHexString(MAIN_STORAGE_ID);
+ private static final String SCND_STORAGE_ID_STR = Integer.toHexString(SCND_STORAGE_ID);
+
+ private static final File mMainStorageDir = new File(MAIN_STORAGE_DIR);
+
+ private static ServerHolder mServerHolder;
+ private MtpDatabase mMtpDatabase;
+
+ private static void logMethodName() {
+ Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
+ }
+
+ private static File createNewDir(File parent, String name) {
+ File ret = new File(parent, name);
+ if (!ret.mkdir())
+ throw new AssertionError(
+ "Failed to create file: name=" + name + ", " + parent.getPath());
+ return ret;
+ }
+
+ private static void writeNewFile(File newFile) {
+ try {
+ new FileOutputStream(newFile).write(new byte[] {0, 0, 0});
+ } catch (IOException e) {
+ Assert.fail();
+ }
+ }
+
+ private static void writeNewFileFromByte(File newFile, byte[] byteData) {
+ try {
+ new FileOutputStream(newFile).write(byteData);
+ } catch (IOException e) {
+ Assert.fail();
+ }
+ }
+
+ private static class ServerHolder {
+ @NonNull final MtpServer server;
+ @NonNull final MtpDatabase database;
+
+ ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
+ Preconditions.checkNotNull(server);
+ Preconditions.checkNotNull(database);
+ this.server = server;
+ this.database = database;
+ }
+
+ void close() {
+ this.database.setServer(null);
+ }
+ }
+
+ private class OnServerTerminated implements Runnable {
+ @Override
+ public void run() {
+ if (mServerHolder == null) {
+ Log.e(TAG, "mServerHolder is unexpectedly null.");
+ return;
+ }
+ mServerHolder.close();
+ mServerHolder = null;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ FileUtils.deleteContentsAndDir(mMainStorageDir);
+ Assert.assertTrue(mMainStorageDir.mkdir());
+
+ StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
+ mMainStorageDir, mMainStorageDir, "Primary Storage",
+ true, false, true, false, -1, UserHandle.CURRENT, "", "");
+
+ final StorageVolume primary = mainStorage;
+
+ mMtpDatabase = new MtpDatabase(mContext, null);
+
+ final MtpServer server =
+ new MtpServer(mMtpDatabase, null, false,
+ new OnServerTerminated(), Build.MANUFACTURER,
+ Build.MODEL, "1.0");
+ mMtpDatabase.setServer(server);
+ mServerHolder = new ServerHolder(server, mMtpDatabase);
+
+ mMtpDatabase.addStorage(mainStorage);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtils.deleteContentsAndDir(mMainStorageDir);
+ }
+
+ private File stageFile(int resId, File file) throws IOException {
+ try (InputStream source = mContext.getResources().openRawResource(resId);
+ OutputStream target = new FileOutputStream(file)) {
+ android.os.FileUtils.copy(source, target);
+ }
+ return file;
+ }
+
+ /**
+ * Refer to BitmapUtilTests, but keep here,
+ * so as to be aware of the behavior or interface change there
+ */
+ private void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
+ Assert.assertTrue(
+ "Abnormal bitmap.width: " + bitmap.getWidth(), bitmap.getWidth() >= expectedWidth);
+ Assert.assertTrue(
+ "Abnormal bitmap.height: " + bitmap.getHeight(),
+ bitmap.getHeight() >= expectedHeight);
+ }
+
+ private byte[] createJpegRawData(int sourceWidth, int sourceHeight) throws IOException {
+ return createRawData(Bitmap.CompressFormat.JPEG, sourceWidth, sourceHeight);
+ }
+
+ private byte[] createPngRawData(int sourceWidth, int sourceHeight) throws IOException {
+ return createRawData(Bitmap.CompressFormat.PNG, sourceWidth, sourceHeight);
+ }
+
+ private byte[] createRawData(Bitmap.CompressFormat format, int sourceWidth, int sourceHeight)
+ throws IOException {
+ // Create a temp bitmap as our source
+ Bitmap b = Bitmap.createBitmap(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ b.compress(format, 50, outputStream);
+ final byte[] data = outputStream.toByteArray();
+ outputStream.close();
+ return data;
+ }
+
+ /**
+ * Decodes the bitmap with the given sample size
+ */
+ public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
+ final BitmapFactory.Options options;
+ if (sampleSize <= 1) {
+ options = null;
+ } else {
+ options = new BitmapFactory.Options();
+ options.inSampleSize = sampleSize;
+ }
+ return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+ }
+
+ private void testThumbnail(int fileHandle, File imgFile, boolean isGoodThumb)
+ throws IOException {
+ boolean isValidThumb;
+ byte[] byteArray;
+ long[] outLongs = new long[3];
+
+ isValidThumb = mMtpDatabase.getThumbnailInfo(fileHandle, outLongs);
+ Assert.assertTrue(isValidThumb);
+
+ byteArray = mMtpDatabase.getThumbnailData(fileHandle);
+
+ if (isGoodThumb) {
+ Assert.assertNotNull("Fail to generate thumbnail:" + imgFile.getPath(), byteArray);
+
+ Bitmap testBitmap = decodeBitmapFromBytes(byteArray, 4);
+ assertBitmapSize(32, 16, testBitmap);
+ } else Assert.assertNull("Bad image should return null:" + imgFile.getPath(), byteArray);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpDatabaseThumbnail() throws IOException {
+ int baseHandle;
+ int handleJpgBadThumb, handleJpgNoThumb, handleJpgBad;
+ int handlePng1, handlePngBad;
+ final String baseTestDirStr = mMainStorageDir.getPath() + TEST_DIRNAME;
+
+ logMethodName();
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Generate and insert tested files.");
+
+ baseHandle = mMtpDatabase.beginSendObject(baseTestDirStr,
+ MtpConstants.FORMAT_ASSOCIATION, 0, MAIN_STORAGE_ID);
+
+ File baseDir = new File(baseTestDirStr);
+ baseDir.mkdirs();
+
+ final File jpgfileBadThumb = new File(baseDir, "jpgfileBadThumb.jpg");
+ final File jpgFileNoThumb = new File(baseDir, "jpgFileNoThumb.jpg");
+ final File jpgfileBad = new File(baseDir, "jpgfileBad.jpg");
+ final File pngFile1 = new File(baseDir, "pngFile1.png");
+ final File pngFileBad = new File(baseDir, "pngFileBad.png");
+
+ handleJpgBadThumb = mMtpDatabase.beginSendObject(jpgfileBadThumb.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ stageFile(R.raw.test_bad_thumb, jpgfileBadThumb);
+
+ handleJpgNoThumb = mMtpDatabase.beginSendObject(jpgFileNoThumb.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFileFromByte(jpgFileNoThumb, createJpegRawData(128, 64));
+
+ handleJpgBad = mMtpDatabase.beginSendObject(jpgfileBad.getPath(),
+ MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFile(jpgfileBad);
+
+ handlePng1 = mMtpDatabase.beginSendObject(pngFile1.getPath(),
+ MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFileFromByte(pngFile1, createPngRawData(128, 64));
+
+ handlePngBad = mMtpDatabase.beginSendObject(pngFileBad.getPath(),
+ MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+ writeNewFile(pngFileBad);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test bad JPG");
+
+ testThumbnail(handleJpgBadThumb, jpgfileBadThumb, false);
+
+ testThumbnail(handleJpgNoThumb, jpgFileNoThumb, false);
+
+ testThumbnail(handleJpgBad, jpgfileBad, false);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test PNG");
+
+ testThumbnail(handlePng1, pngFile1, true);
+
+ Log.d(TAG, "testMtpDatabaseThumbnail: Test bad PNG");
+
+ testThumbnail(handlePngBad, pngFileBad, false);
+ }
+
+ @Test
+ @SmallTest
+ public void testMtpDatabaseExtStorage() throws IOException {
+ int numObj;
+ StorageVolume[] mVolumes;
+
+ logMethodName();
+
+ mVolumes = StorageManager.getVolumeList(UserHandle.myUserId(), 0);
+ // Currently it may need manual setup for 2nd storage on virtual device testing.
+ // Thus only run test when 2nd storage exists.
+ Assume.assumeTrue(
+ "Skip when 2nd storage not available, volume numbers = " + mVolumes.length,
+ mVolumes.length >= 2);
+
+ for (int ii = 0; ii < mVolumes.length; ii++) {
+ StorageVolume volume = mVolumes[ii];
+ // Skip Actual Main storage (Internal Storage),
+ // since we use manipulated path as testing Main storage
+ if (ii > 0)
+ mMtpDatabase.addStorage(volume);
+ }
+
+ numObj = mMtpDatabase.getNumObjects(SCND_STORAGE_ID, 0, 0xFFFFFFFF);
+ Assert.assertTrue(
+ "Fail to get objects in 2nd storage, object numbers = " + numObj, numObj >= 0);
+ }
+}
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 7004fb6..8b235e6 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 ce0d31c..a7347f2 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 a71567c..aa0a8c5 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 e6fb501..52d13c3 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -86,29 +86,30 @@
<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.volume.VolumeUI</item>
- <item>com.android.systemui.stackdivider.Divider</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.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>
+ <item>com.android.systemui.car.volume.VolumeUI</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 59fa9d0..91e222c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -19,6 +19,8 @@
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.car.volume.VolumeUI;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -38,8 +40,6 @@
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;
@@ -65,7 +65,7 @@
@ClassKey(Divider.class)
public abstract SystemUI bindDivider(Divider sysui);
- /** */
+ /** Inject Car Navigation Bar. */
@Binds
@IntoMap
@ClassKey(CarNavigationBar.class)
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
index 908aaad..a729431 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.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
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
index fd9c488..af8ddb6 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
similarity index 96%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
index 3c6d623..a4c4573 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
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
index a40ffaf..9a7b0b9 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
similarity index 95%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
index 8ee5ef6..74d9704 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
similarity index 95%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
index ad4fcd9..521a665 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
similarity index 93%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
index 963f318..6b903fa 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 60ee19e..4fde309 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 @@
@Override
public void onCancelClicked() {
+ getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
+ getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
+
mBouncer.hide(/* destroyView= */ true);
mKeyguardCancelClickedListener.onCancelClicked();
}
@@ -226,7 +229,8 @@
@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 53e5d9f..20fcca0 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.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 @@
@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 @@
@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 @@
private void animateShow() {
if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen
- || !mCarStatusBarLazy.get().isPanelExpanded()) && isCurrentUserSetup()) {
+ || !mNotificationPanelViewControllerLazy.get().isPanelExpanded())
+ && mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
mWindow.setVisibility(View.VISIBLE);
}
}
@@ -114,9 +113,4 @@
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
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java
rename to packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
index c0dbb58..f145b14 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/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 58add17..2ff6670 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -368,7 +368,7 @@
private void applyCarSysUIDialogFlags(AlertDialog dialog) {
final Window window = dialog.getWindow();
- window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
window.getAttributes().setFitInsetsTypes(
diff --git a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java
rename to packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
index 2f79f96..c054d20 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/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
new file mode 100644
index 0000000..2bdb85f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -0,0 +1,125 @@
+/*
+ * 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.car.volume;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.volume.VolumeDialogComponent;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** The entry point for controlling the volume ui in cars. */
+@Singleton
+public class VolumeUI extends SystemUI {
+
+ private static final String TAG = "VolumeUI";
+ private final Resources mResources;
+ private final Handler mMainHandler;
+ private final CarServiceProvider mCarServiceProvider;
+ private final Lazy<VolumeDialogComponent> mVolumeDialogComponentLazy;
+
+ private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
+ new CarAudioManager.CarVolumeCallback() {
+ @Override
+ public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+ if (mVolumeDialogComponent == null) {
+ mMainHandler.post(() -> {
+ mVolumeDialogComponent = mVolumeDialogComponentLazy.get();
+ mVolumeDialogComponent.register();
+ });
+ mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
+ }
+ }
+
+ @Override
+ public void onMasterMuteChanged(int zoneId, int flags) {
+ // ignored
+ }
+ };
+
+ private boolean mEnabled;
+ private CarAudioManager mCarAudioManager;
+ private VolumeDialogComponent mVolumeDialogComponent;
+
+ @Inject
+ public VolumeUI(
+ Context context,
+ @Main Resources resources,
+ @Main Handler mainHandler,
+ CarServiceProvider carServiceProvider,
+ Lazy<VolumeDialogComponent> volumeDialogComponentLazy
+ ) {
+ super(context);
+ mResources = resources;
+ mMainHandler = mainHandler;
+ mCarServiceProvider = carServiceProvider;
+ mVolumeDialogComponentLazy = volumeDialogComponentLazy;
+ }
+
+ @Override
+ public void start() {
+ boolean enableVolumeUi = mResources.getBoolean(R.bool.enable_volume_ui);
+ mEnabled = enableVolumeUi;
+ if (!mEnabled) return;
+
+ mCarServiceProvider.addListener(car -> {
+ if (mCarAudioManager != null) {
+ return;
+ }
+
+ mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+ Log.d(TAG, "Registering mVolumeChangeCallback.");
+ // This volume call back is never unregistered because CarStatusBar is
+ // never destroyed.
+ mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
+ });
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mEnabled) return;
+ if (mVolumeDialogComponent != null) {
+ mVolumeDialogComponent.onConfigurationChanged(newConfig);
+ }
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("mEnabled="); pw.println(mEnabled);
+ if (!mEnabled) return;
+ if (mVolumeDialogComponent != null) {
+ mVolumeDialogComponent.dump(fd, pw, args);
+ }
+ }
+}
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 9fdfc0f..4e315c6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -51,8 +51,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;
@@ -79,8 +77,6 @@
private final ButtonSelectionStateListener mButtonSelectionStateListener;
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;
@@ -126,8 +122,6 @@
ButtonSelectionStateListener buttonSelectionStateListener,
@Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
- Lazy<NavigationBarController> navigationBarControllerLazy,
- SuperStatusBarViewFactory superStatusBarViewFactory,
ButtonSelectionStateController buttonSelectionStateController,
PhoneStatusBarPolicy iconPolicy,
StatusBarIconController iconController
@@ -142,8 +136,6 @@
mButtonSelectionStateListener = buttonSelectionStateListener;
mMainHandler = mainHandler;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
- mNavigationBarControllerLazy = navigationBarControllerLazy;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
mButtonSelectionStateController = buttonSelectionStateController;
mIconPolicy = iconPolicy;
mIconController = iconController;
@@ -319,11 +311,6 @@
result.mImeWindowVis, result.mImeBackDisposition,
result.mShowImeSwitcher);
}
-
- // There has been a car customized nav bar on the default display, so just create nav bars
- // on external displays.
- mNavigationBarControllerLazy.get().createNavigationBars(/* includeDefaultDisplay= */ false,
- result);
}
private void buildNavBarWindows() {
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 37a8225..8f3ae1a 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.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/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 5a34436..35ab322 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -45,6 +45,7 @@
@Override
protected VolumeDialog createDefault() {
mCarVolumeDialog = new CarVolumeDialogImpl(mContext);
+ mCarVolumeDialog.show(Events.SHOW_REASON_VOLUME_CHANGED);
return mCarVolumeDialog;
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 873d7d7..2218eb5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -234,6 +234,20 @@
cleanupAudioManager();
}
+ /**
+ * Reveals volume dialog.
+ */
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason).sendToTarget();
+ }
+
+ /**
+ * Hides volume dialog.
+ */
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason).sendToTarget();
+ }
+
private void initDialog() {
loadAudioUsageItems();
mCarVolumeLineItems.clear();
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
index 402d742..5fe03f1 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 @@
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 0dbe1a3..5df5d6e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
@@ -90,7 +90,7 @@
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
@@ -132,6 +132,16 @@
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
similarity index 97%
rename from packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
index a71d1db..7996170 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 05b8e6a..6ac72a6 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 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 @@
@Mock
private CarDeviceProvisionedController mCarDeviceProvisionedController;
@Mock
- private CarStatusBar mCarStatusBar;
+ private NotificationPanelViewController mNotificationPanelViewController;
@Mock
private WindowManager mWindowManager;
@@ -61,7 +60,7 @@
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 @@
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 @@
@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
similarity index 99%
rename from packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
index aebb0e0..80f3d1ee 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
similarity index 93%
rename from packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index 38b47d0..eca51e3 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 bbcd0d4..28c69c7 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 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 3ecb29f..f789d38 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
@@ -37,8 +37,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;
@@ -73,10 +71,6 @@
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
- private NavigationBarController mNavigationBarController;
- @Mock
- private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- @Mock
private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
private PhoneStatusBarPolicy mIconPolicy;
@@ -91,9 +85,8 @@
mCarNavigationBar = new CarNavigationBar(mContext, mTestableResources.getResources(),
mCarNavigationBarController, mWindowManager, mDeviceProvisionedController,
new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener,
- mHandler, () -> mKeyguardStateController, () -> mNavigationBarController,
- mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy,
- mIconController);
+ mHandler, () -> mKeyguardStateController, mButtonSelectionStateController,
+ mIconPolicy, mIconController);
}
@Test
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 54986a4..be79010 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -70,6 +70,7 @@
// 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 @@
},
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 @@
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/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1007d83..7baaf49 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1080,14 +1080,14 @@
<string name="power_suggestion_battery_run_out">Battery may run out by <xliff:g id="time" example="12 PM">%1$s</xliff:g></string>
<!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
- <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>
+ <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage -->
- <string name="power_remaining_less_than_duration">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string>
+ <string name="power_remaining_less_than_duration">Less than <xliff:g id="threshold">%1$s</xliff:g> left (<xliff:g id="level">%2$s</xliff:g>)</string>
<!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] -->
- <string name="power_remaining_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string>
+ <string name="power_remaining_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left (<xliff:g id="level">%2$s</xliff:g>)</string>
<!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] -->
- <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string>
+ <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
<!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
<string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index e551b69..ee8fb38 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -38,7 +38,7 @@
BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
- super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
mCachedDevice = device;
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 85fa988..83a9671 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -38,7 +38,7 @@
InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
- super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 39e6a12..6aff301 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,6 +15,16 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -38,13 +48,21 @@
private static final String TAG = "MediaDevice";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({MediaDeviceType.TYPE_CAST_DEVICE,
+ @IntDef({MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
+ MediaDeviceType.TYPE_CAST_DEVICE,
+ MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
MediaDeviceType.TYPE_PHONE_DEVICE})
public @interface MediaDeviceType {
- int TYPE_PHONE_DEVICE = 1;
- int TYPE_CAST_DEVICE = 2;
- int TYPE_BLUETOOTH_DEVICE = 3;
+ int TYPE_USB_C_AUDIO_DEVICE = 1;
+ int TYPE_3POINT5_MM_AUDIO_DEVICE = 2;
+ int TYPE_FAST_PAIR_BLUETOOTH_DEVICE = 3;
+ int TYPE_BLUETOOTH_DEVICE = 4;
+ int TYPE_CAST_DEVICE = 5;
+ int TYPE_CAST_GROUP_DEVICE = 6;
+ int TYPE_PHONE_DEVICE = 7;
}
@VisibleForTesting
@@ -58,13 +76,43 @@
protected final MediaRouter2Manager mRouterManager;
protected final String mPackageName;
- MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager,
- MediaRoute2Info info, String packageName) {
- mType = type;
+ MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName) {
mContext = context;
mRouteInfo = info;
mRouterManager = routerManager;
mPackageName = packageName;
+ setType(info);
+ }
+
+ private void setType(MediaRoute2Info info) {
+ if (info == null) {
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ return;
+ }
+
+ switch (info.getType()) {
+ case TYPE_GROUP:
+ mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ mType = MediaDeviceType.TYPE_PHONE_DEVICE;
+ break;
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ mType = MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE;
+ break;
+ case TYPE_HEARING_AID:
+ case TYPE_BLUETOOTH_A2DP:
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ break;
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ default:
+ mType = MediaDeviceType.TYPE_CAST_DEVICE;
+ break;
+ }
}
void initDeviceRecord() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index af88723..c6c5ade 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -42,7 +42,7 @@
PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
- super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 653c8ad..deccde5 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.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 @@
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 c713d78..d7e76a1 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 @@
}
}
+ /**
+ * 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/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 4b08387..db05b76 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -15,6 +15,10 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -144,12 +148,19 @@
when(mCachedDevice2.isConnected()).thenReturn(true);
when(mCachedDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mCachedDevice3.isConnected()).thenReturn(true);
+ when(mBluetoothRouteInfo1.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+ when(mBluetoothRouteInfo2.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+ when(mBluetoothRouteInfo3.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
when(mRouteInfo1.getId()).thenReturn(ROUTER_ID_1);
when(mRouteInfo2.getId()).thenReturn(ROUTER_ID_2);
when(mRouteInfo3.getId()).thenReturn(ROUTER_ID_3);
when(mRouteInfo1.getName()).thenReturn(DEVICE_NAME_1);
when(mRouteInfo2.getName()).thenReturn(DEVICE_NAME_2);
when(mRouteInfo3.getName()).thenReturn(DEVICE_NAME_3);
+ when(mRouteInfo1.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mRouteInfo2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mRouteInfo3.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mPhoneRouteInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
@@ -271,12 +282,12 @@
@Test
public void compareTo_info_bluetooth_infoFirst() {
- mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mInfoMediaDevice1);
+ mMediaDevices.add(mBluetoothMediaDevice1);
- assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
- Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
+ Collections.sort(mMediaDevices, COMPARATOR);
+ assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
@@ -327,7 +338,7 @@
// 5.mBluetoothMediaDevice2: * 2 times usage
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
- // Order: 7 -> 2 -> 1 -> 3 -> 5 -> 4 -> 6
+ // Order: 7 -> 2 -> 1 -> 5 -> 3 -> 6 -> 4
@Test
public void compareTo_mixedDevices_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -352,10 +363,10 @@
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice1);
- assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice2);
- assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice2);
- assertThat(mMediaDevices.get(5)).isEqualTo(mInfoMediaDevice3);
- assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice2);
+ assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice2);
+ assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(6)).isEqualTo(mInfoMediaDevice3);
}
// 1.mInfoMediaDevice1: Last Selected device
@@ -365,7 +376,7 @@
// 5.mBluetoothMediaDevice2: * 4 times usage not connected
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
- // Order: 7 -> 1 -> 3 -> 4 -> 6 -> 2 -> 5
+ // Order: 7 -> 1 -> 3 -> 6 -> 4 -> 2 -> 5
@Test
public void compareTo_mixedDevices_connectDeviceFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -394,8 +405,8 @@
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(1)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice2);
- assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice3);
- assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2);
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 342d127a..4b779ac 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -137,9 +137,9 @@
true /* basedOnUsage */);
// shortened string should not have percentage
- assertThat(info).isEqualTo("Less than 15 min remaining");
+ assertThat(info).isEqualTo("Less than 15 min left");
// Add percentage to string when provided
- assertThat(info2).isEqualTo("Less than 15 min remaining (10%)");
+ assertThat(info2).isEqualTo("Less than 15 min left (10%)");
}
@Test
@@ -171,9 +171,9 @@
true /* basedOnUsage */);
// shortened string should not have percentage
- assertThat(info).isEqualTo("More than 2 days remaining");
+ assertThat(info).isEqualTo("More than 2 days left");
// Add percentage to string when provided
- assertThat(info2).isEqualTo("More than 2 days remaining (10%)");
+ assertThat(info2).isEqualTo("More than 2 days left (10%)");
}
@Test
@@ -181,7 +181,7 @@
String info = PowerUtil.getBatteryTipStringFormatted(mContext,
THREE_DAYS_MILLIS);
- assertThat(info).isEqualTo("More than 3 days remaining");
+ assertThat(info).isEqualTo("More than 3 days left");
}
@Test
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 0000000..a793680
--- /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/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml
index be3c598..f0dfd21 100644
--- a/packages/SystemUI/res/drawable/screenshot_cancel.xml
+++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml
@@ -20,9 +20,9 @@
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<path
- android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
- android:fillColor="@android:color/white"/>
+ android:fillColor="@color/global_screenshot_dismiss_background"
+ android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"/>
<path
- android:fillColor="@color/GM2_grey_500"
+ android:fillColor="@color/global_screenshot_dismiss_foreground"
android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/>
</vector>
\ 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 0000000..129282d
--- /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 ae57563..6da96d1 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/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index d506e7e..db109fe 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -68,6 +68,7 @@
android:visibility="gone"
android:contentDescription="@string/screenshot_dismiss_ui_description">
<ImageView
+ android:id="@+id/global_screenshot_dismiss_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/screenshot_dismiss_button_margin"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 93aa270..2d51011 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -72,9 +72,14 @@
<color name="global_actions_alert_text">@color/GM2_red_300</color>
<!-- Global screenshot actions -->
- <color name="global_screenshot_button_background">@color/GM2_grey_900</color>
+ <color name="global_screenshot_button_background">@color/GM2_grey_800</color>
<color name="global_screenshot_button_ripple">#42FFFFFF</color>
- <color name="global_screenshot_button_text">@color/GM2_blue_300</color>
+ <color name="global_screenshot_button_text">#FFFFFF</color>
+ <color name="global_screenshot_button_border">@color/GM2_grey_600</color>
+ <color name="global_screenshot_button_icon">@color/GM2_blue_300</color>
+ <color name="global_screenshot_dismiss_background">@color/GM2_grey_800</color>
+ <color name="global_screenshot_dismiss_foreground">#FFFFFF</color>
+
<!-- Biometric dialog colors -->
<color name="biometric_dialog_gray">#ff888888</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4482cda..8b6b5f6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -198,6 +198,8 @@
<color name="global_screenshot_button_border">@color/GM2_grey_300</color>
<color name="global_screenshot_button_ripple">#1f000000</color>
<color name="global_screenshot_button_icon">@color/GM2_blue_500</color>
+ <color name="global_screenshot_dismiss_background">#FFFFFF</color>
+ <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
<!-- GM2 colors -->
<color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 599ed16..179f8b8 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 cb20e7a..d639ed0 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 7e24f5d..4ed819e 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 367058f..a96ef91 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1301,6 +1301,7 @@
private FingerprintManager mFpm;
private FaceManager mFaceManager;
private boolean mFingerprintLockedOut;
+ private TelephonyManager mTelephonyManager;
/**
* When we receive a
@@ -1728,10 +1729,22 @@
}
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 409ae3f..c92174a 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -164,7 +164,9 @@
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 7262f8c..1f27ae2 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 @@
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 71f2bc0..38bfffb 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 @@
}
private FlyoutMessage mFlyoutMessage;
+ private Drawable mBadgedAppIcon;
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
@@ -133,6 +134,10 @@
return mBadgedImage;
}
+ public Drawable getBadgedAppIcon() {
+ return mBadgedAppIcon;
+ }
+
@Override
public int getDotColor() {
return mDotColor;
@@ -239,6 +244,7 @@
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 c6883c8..e488cf2 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 @@
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 3524696..bb23655 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
* 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 @@
}
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 0aabdff..c906931 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.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.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.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.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.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.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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
+ setUpManageMenu();
+
setUpFlyout();
mFlyoutTransitionSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
@@ -838,7 +861,9 @@
// 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 @@
});
}
+ 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 @@
setUpFlyout();
setUpOverflow();
setUpUserEducation();
+ setUpManageMenu();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -960,6 +1046,9 @@
Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
+
+ mManageMenu.setVisibility(View.INVISIBLE);
+ mShowingManage = false;
}
@Override
@@ -1100,6 +1189,12 @@
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 @@
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 @@
}
private void animateCollapse() {
+ // Hide the menu if it's visible.
+ showManageMenu(false);
+
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
@@ -1570,9 +1667,9 @@
*/
@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 @@
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 @@
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 @@
&& 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 @@
}
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 c96f9a4..8a57a73 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 @@
ShortcutInfo shortcutInfo;
String appName;
Bitmap badgedBubbleImage;
+ Drawable badgedAppIcon;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
@@ -176,6 +177,7 @@
}
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 ef84c73..ca3e2e2 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.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 7e8fec7..e84f439 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.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 @@
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 @@
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 @@
}
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 79a5b0a..bc97c10 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 @@
* 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 5626a5d..8e88756 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 @@
private var userChanging: Boolean = true
- private var loadCanceller: Runnable? = null
-
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
@@ -276,28 +274,29 @@
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 @@
}
override fun error(message: String) {
- loadCanceller = null
executor.execute {
val controls = Favorites.getStructuresForComponent(componentName)
.flatMap { st ->
@@ -348,6 +346,7 @@
}
}
}
+ )
)
}
@@ -432,12 +431,6 @@
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 0000000..4ca47d1
--- /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 ee1ce7a..640c90d 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.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 @@
*/
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 @@
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 @@
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 @@
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 6f34dee..183bd7b 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.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 @@
@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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
StructureInfo(component!!, it.structureName, favoritesForStorage)
)
}
- finishAffinity()
+
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
@@ -302,15 +325,39 @@
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 3be5900..80cb9680 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 @@
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 @@
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 @@
* @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 @@
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 61a1a98..aed7cd3 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 @@
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 2adfb1b..cfd8df0 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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
}
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/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 88f96a8..8c572fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -24,7 +24,6 @@
import androidx.annotation.Nullable;
import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.plugins.qs.QSFactory;
@@ -34,7 +33,6 @@
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.stackdivider.DividerModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -138,15 +136,4 @@
@Binds
abstract KeyguardViewController bindKeyguardViewController(
StatusBarKeyguardViewManager statusBarKeyguardViewManager);
-
- @Singleton
- @Provides
- static CurrentUserContextTracker provideCurrentUserContextTracker(
- Context context,
- BroadcastDispatcher broadcastDispatcher) {
- CurrentUserContextTracker tracker =
- new CurrentUserContextTracker(context, broadcastDispatcher);
- tracker.initialize();
- return tracker;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2e9ce12..90cd13f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -29,6 +29,7 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.dagger.SettingsModule;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
@@ -61,6 +62,7 @@
ConcurrencyModule.class,
LogModule.class,
PeopleHubModule.class,
+ SettingsModule.class
},
subcomponents = {StatusBarComponent.class,
NotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ea358c7..a24fede 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 @@
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.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.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 @@
*/
@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 @@
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 @@
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 @@
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
+ private ViewGroup mContainer;
ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
GlobalActionsPanelPlugin.PanelViewController plugin,
@@ -2046,6 +2051,11 @@
});
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 @@
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,48 +2210,55 @@
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();
- resetOrientation();
- }
-
- void dismissImmediately() {
- mShowing = false;
- if (mControlsUiController != null) mControlsUiController.hide();
- dismissPanel();
- resetOrientation();
- completeDismiss();
+ animation.run();
}
private void completeDismiss() {
+ mShowing = false;
+ resetOrientation();
+ dismissPanel();
+ dismissOverflow();
+ if (mControlsUiController != null) mControlsUiController.hide();
mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
mDepthController.updateGlobalDialogVisibility(0, null /* view */);
super.dismiss();
@@ -2253,6 +2270,12 @@
}
}
+ private void dismissOverflow() {
+ if (mOverflowPopup != null) {
+ mOverflowPopup.dismiss();
+ }
+ }
+
private void setRotationSuggestionsEnabled(boolean enabled) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
@@ -2296,7 +2319,7 @@
initializeLayout();
mGlobalActionsLayout.updateList();
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
}
}
@@ -2335,10 +2358,9 @@
&& 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 14e3e93..123cf78 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 @@
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 0000000..20fc6ff
--- /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 9217eb1..233d24b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -21,20 +21,25 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
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.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -55,8 +60,10 @@
import com.android.systemui.Dependency;
import com.android.systemui.R;
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;
@@ -67,7 +74,7 @@
private static final String TAG = "MediaControlPanel";
@Nullable private final LocalMediaManager mLocalMediaManager;
private final Executor mForegroundExecutor;
- private final Executor mBackgroundExecutor;
+ protected final Executor mBackgroundExecutor;
private Context mContext;
protected LinearLayout mMediaNotifView;
@@ -76,13 +83,18 @@
private MediaController mController;
private int mForegroundColor;
private int mBackgroundColor;
- protected ComponentName mRecvComponent;
private MediaDevice mDevice;
+ protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
private String mKey;
private final int[] mActionIds;
+ public static final String MEDIA_PREFERENCES = "media_control_prefs";
+ public static final String MEDIA_PREFERENCE_KEY = "browser_components";
+ private SharedPreferences mSharedPrefs;
+ private boolean mCheckedForResumption = false;
+
// Button IDs used in notifications
protected static final int[] NOTIF_ACTION_IDS = {
com.android.internal.R.id.action0,
@@ -92,6 +104,13 @@
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() {
@@ -154,7 +173,6 @@
* Initialize a new control panel
* @param context
* @param parent
- * @param manager
* @param routeManager Manager used to listen for device change events.
* @param layoutId layout resource to use for this control panel
* @param actionIds resource IDs for action buttons in the layout
@@ -198,47 +216,52 @@
/**
* Update the media panel view for the given media session
* @param token
- * @param icon
+ * @param iconDrawable
+ * @param largeIcon
* @param iconColor
* @param bgColor
* @param contentIntent
* @param appNameString
* @param key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, PendingIntent contentIntent, String appNameString, String key) {
- mToken = token;
+ 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;
+ mServiceComponent = null;
+ mCheckedForResumption = false;
+ }
+
mForegroundColor = iconColor;
mBackgroundColor = bgColor;
mController = new MediaController(mContext, mToken);
mKey = key;
- MediaMetadata mediaMetadata = mController.getMetadata();
-
- // Try to find a receiver for the media button that matches this app
- PackageManager pm = mContext.getPackageManager();
- Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
- List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
- if (info != null) {
- for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- mRecvComponent = inf.getComponentInfo().getComponentName();
+ // Try to find a browser service component for this app
+ // TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Only check if we haven't tried yet or the session token changed
+ String pkgName = mController.getPackageName();
+ if (mServiceComponent == null && !mCheckedForResumption) {
+ Log.d(TAG, "Checking for service component");
+ PackageManager pm = mContext.getPackageManager();
+ Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
+ List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
+ if (resumeInfo != null) {
+ for (ResolveInfo inf : resumeInfo) {
+ if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
+ mBackgroundExecutor.execute(() ->
+ tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
+ break;
+ }
}
}
+ mCheckedForResumption = true;
}
mController.registerCallback(mSessionCallback);
- if (mediaMetadata == null) {
- Log.e(TAG, "Media metadata was null");
- return;
- }
-
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
- }
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
// Click action
@@ -256,32 +279,9 @@
// App icon
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
- Drawable iconDrawable = icon.loadDrawable(mContext);
iconDrawable.setTint(mForegroundColor);
appIcon.setImageDrawable(iconDrawable);
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- titleText.setText(songName);
- titleText.setTextColor(mForegroundColor);
-
- // Not in mini player:
- // App title
- TextView appName = mMediaNotifView.findViewById(R.id.app_name);
- if (appName != null) {
- appName.setText(appNameString);
- appName.setTextColor(mForegroundColor);
- }
-
- // Artist name
- TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
- if (artistText != null) {
- String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
- artistText.setText(artistName);
- artistText.setTextColor(mForegroundColor);
- }
-
// Transfer chip
mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
if (mSeamless != null && mLocalMediaManager != null) {
@@ -300,6 +300,39 @@
}
makeActive();
+
+ // App title (not in mini player)
+ TextView appName = mMediaNotifView.findViewById(R.id.app_name);
+ if (appName != null) {
+ appName.setText(appNameString);
+ appName.setTextColor(mForegroundColor);
+ }
+
+ MediaMetadata mediaMetadata = mController.getMetadata();
+ if (mediaMetadata == null) {
+ Log.e(TAG, "Media metadata was null");
+ return;
+ }
+
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(mForegroundColor);
+
+ // Artist name (not in mini player)
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ artistText.setText(artistName);
+ artistText.setTextColor(mForegroundColor);
+ }
}
/**
@@ -320,9 +353,12 @@
/**
* Get the name of the package associated with the current media controller
- * @return the package name
+ * @return the package name, or null if no controller
*/
public String getMediaPlayerPackage() {
+ if (mController == null) {
+ return null;
+ }
return mController.getPackageName();
}
@@ -370,18 +406,86 @@
/**
* Process album art for layout
- * @param metadata media metadata
+ * @param description media description
* @param albumView view to hold the album art
*/
- private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
- Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+ protected void processAlbumArt(MediaDescription description, ImageView albumView) {
+ 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, 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);
+ }
+
+ /**
+ * 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 {
@@ -449,10 +553,24 @@
}
/**
- * Put controls into a resumption state
+ * Puts controls into a resumption state if possible, or calls removePlayer if no component was
+ * found that could resume playback
*/
public void clearControls() {
Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
+ if (mServiceComponent == null) {
+ // If we don't have a way to resume, just remove the player altogether
+ Log.d(TAG, "Removing unresumable controls");
+ removePlayer();
+ return;
+ }
+ resetButtons();
+ }
+
+ /**
+ * Hide the media buttons and show only a restart button
+ */
+ protected void resetButtons() {
// Hide all the old buttons
for (int i = 0; i < mActionIds.length; i++) {
ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
@@ -465,27 +583,8 @@
ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
btn.setOnClickListener(v -> {
Log.d(TAG, "Attempting to restart session");
- // Send a media button event to previously found receiver
- if (mRecvComponent != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setComponent(mRecvComponent);
- int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
- intent.putExtra(
- Intent.EXTRA_KEY_EVENT,
- new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- mContext.sendBroadcast(intent);
- } else {
- // If we don't have a receiver, try relaunching the activity instead
- if (mController.getSessionActivity() != null) {
- try {
- mController.getSessionActivity().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled", e);
- }
- } else {
- Log.e(TAG, "No receiver or activity to restart");
- }
- }
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, null, mServiceComponent);
+ browser.restart();
});
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
@@ -514,4 +613,65 @@
}
}
+ /**
+ * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
+ * component to the list of resumption components
+ */
+ private void tryUpdateResumptionList(ComponentName componentName) {
+ Log.d(TAG, "Testing if we can connect to " + componentName);
+ QSMediaBrowser.testConnection(mContext,
+ new QSMediaBrowser.Callback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "yes we can resume with " + componentName);
+ mServiceComponent = componentName;
+ updateResumptionList(componentName);
+ }
+
+ @Override
+ public void onError() {
+ Log.d(TAG, "Cannot resume with " + componentName);
+ mServiceComponent = null;
+ clearControls();
+ // remove
+ }
+ },
+ componentName);
+ }
+
+ /**
+ * Add the component to the saved list of media browser services, checking for duplicates and
+ * removing older components that exceed the maximum limit
+ * @param componentName
+ */
+ private synchronized void updateResumptionList(ComponentName componentName) {
+ // Add to front of saved list
+ if (mSharedPrefs == null) {
+ mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0);
+ }
+ String componentString = componentName.flattenToString();
+ String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ listString = componentString;
+ } else {
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ StringBuilder updated = new StringBuilder(componentString);
+ int nBrowsers = 1;
+ for (int i = 0; i < components.length
+ && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ if (componentString.equals(components[i])) {
+ continue;
+ }
+ updated.append(QSMediaBrowser.DELIMITER).append(components[i]);
+ nBrowsers++;
+ }
+ listString = updated.toString();
+ }
+ mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply();
+ }
+
+ /**
+ * Called when a player can't be resumed to give it an opportunity to hide or remove itself
+ */
+ protected void removePlayer() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
new file mode 100644
index 0000000..302b8420
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
@@ -0,0 +1,259 @@
+/*
+ * 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.qs;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Media browser for managing resumption in QS media controls
+ */
+public class QSMediaBrowser {
+
+ /** Maximum number of controls to show on boot */
+ public static final int MAX_RESUMPTION_CONTROLS = 5;
+
+ /** Delimiter for saved component names */
+ public static final String DELIMITER = ":";
+
+ private static final String TAG = "QSMediaBrowser";
+ private final Context mContext;
+ private final Callback mCallback;
+ private MediaBrowser mMediaBrowser;
+ private ComponentName mComponentName;
+
+ /**
+ * Initialize a new media browser
+ * @param context the context
+ * @param callback used to report media items found
+ * @param componentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+ mContext = context;
+ mCallback = callback;
+ mComponentName = componentName;
+
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ }
+
+ /**
+ * Connects to the MediaBrowserService and looks for valid media. If a media item is returned
+ * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription
+ */
+ public void findRecentMedia() {
+ Log.d(TAG, "Connecting to " + mComponentName);
+ mMediaBrowser.connect();
+ }
+
+ private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+ new MediaBrowser.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId,
+ List<MediaBrowser.MediaItem> children) {
+ if (children.size() == 0) {
+ Log.e(TAG, "No children found");
+ return;
+ }
+ // We ask apps to return a playable item as the first child when sending
+ // a request with EXTRA_RECENT; if they don't, no resume controls
+ MediaBrowser.MediaItem child = children.get(0);
+ MediaDescription desc = child.getDescription();
+ if (child.isPlayable()) {
+ mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this);
+ } else {
+ Log.e(TAG, "Child found but not playable for " + mComponentName);
+ }
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId, Bundle options) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId
+ + ", options: " + options);
+ mMediaBrowser.disconnect();
+ }
+ };
+
+ private final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ /**
+ * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
+ * For resumption controls, apps are expected to return a playable media item as the first
+ * child. If there are no children or it isn't playable it will be ignored.
+ */
+ @Override
+ public void onConnected() {
+ if (mMediaBrowser.isConnected()) {
+ mCallback.onConnected();
+ Log.d(TAG, "Service connected for " + mComponentName);
+ String root = mMediaBrowser.getRoot();
+ mMediaBrowser.subscribe(root, mSubscriptionCallback);
+ }
+ }
+
+ /**
+ * Invoked when the client is disconnected from the media browser.
+ */
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "Connection suspended for " + mComponentName);
+ }
+
+ /**
+ * Invoked when the connection to the media browser failed.
+ */
+ @Override
+ public void onConnectionFailed() {
+ Log.e(TAG, "Connection failed for " + mComponentName);
+ mCallback.onError();
+ }
+ };
+
+ /**
+ * Connects to the MediaBrowserService and starts playback
+ */
+ public void restart() {
+ if (mMediaBrowser.isConnected()) {
+ mMediaBrowser.disconnect();
+ }
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
+ MediaSession.Token token = mMediaBrowser.getSessionToken();
+ MediaController controller = new MediaController(mContext, token);
+ controller.getTransportControls();
+ controller.getTransportControls().prepare();
+ controller.getTransportControls().play();
+ }
+ }, rootHints);
+ mMediaBrowser.connect();
+ }
+
+ /**
+ * Get the media session token
+ * @return the token, or null if the MediaBrowser is null or disconnected
+ */
+ public MediaSession.Token getToken() {
+ if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
+ return null;
+ }
+ return mMediaBrowser.getSessionToken();
+ }
+
+ /**
+ * Get an intent to launch the app associated with this browser service
+ * @return
+ */
+ public PendingIntent getAppIntent() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
+ return PendingIntent.getActivity(mContext, 0, launchIntent, 0);
+ }
+
+ /**
+ * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser
+ * @param mContext the context
+ * @param callback methods onConnected or onError will be called to indicate whether the
+ * connection was successful or not
+ * @param mComponentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public static MediaBrowser testConnection(Context mContext, Callback callback,
+ ComponentName mComponentName) {
+ final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "connected");
+ callback.onConnected();
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "suspended");
+ callback.onError();
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ Log.d(TAG, "failed");
+ callback.onError();
+ }
+ };
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ MediaBrowser browser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ browser.connect();
+ return browser;
+ }
+
+ /**
+ * Interface to handle results from QSMediaBrowser
+ */
+ public static class Callback {
+ /**
+ * Called when the browser has successfully connected to the service
+ */
+ public void onConnected() {
+ }
+
+ /**
+ * Called when the browser encountered an error connecting to the service
+ */
+ public void onError() {
+ }
+
+ /**
+ * Called when the browser finds a suitable track to add to the media carousel
+ * @param track media info for the item
+ * @param component component of the MediaBrowserService which returned this
+ * @param browser reference to the browser
+ */
+ public void addTrack(MediaDescription track, ComponentName component,
+ QSMediaBrowser browser) {
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 89b22bc..9e574a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -18,11 +18,13 @@
import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-import android.app.Notification;
+import android.app.PendingIntent;
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;
import android.util.Log;
@@ -60,9 +62,11 @@
};
private final QSPanel mParent;
+ private final Executor mForegroundExecutor;
private final DelayableExecutor mBackgroundExecutor;
private final SeekBarViewModel mSeekBarViewModel;
private final SeekBarObserver mSeekBarObserver;
+ private String mPackageName;
/**
* Initialize quick shade version of player
@@ -77,6 +81,7 @@
super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
foregroundExecutor, backgroundExecutor);
mParent = (QSPanel) parent;
+ mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
mSeekBarObserver = new SeekBarObserver(getView());
@@ -90,47 +95,103 @@
}
/**
+ * Add a media panel view based on a media description. Used for resumption
+ * @param description
+ * @param iconColor
+ * @param bgColor
+ * @param contentIntent
+ * @param pkgName
+ */
+ public void setMediaSession(MediaSession.Token token, MediaDescription description,
+ int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
+ mPackageName = pkgName;
+ PackageManager pm = getContext().getPackageManager();
+ Drawable icon = null;
+ CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
+ try {
+ icon = pm.getApplicationIcon(pkgName);
+ appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Error getting package information", e);
+ }
+
+ // Set what we can normally
+ super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+ appName.toString(), null);
+
+ // Then add info from MediaDescription
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ CharSequence songName = description.getTitle();
+ titleText.setText(songName);
+ titleText.setTextColor(iconColor);
+
+ // Artist name (not in mini player)
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ CharSequence artistName = description.getSubtitle();
+ artistText.setText(artistName);
+ artistText.setTextColor(iconColor);
+ }
+
+ initLongPressMenu(iconColor);
+
+ // Set buttons to resume state
+ resetButtons();
+ }
+
+ /**
* 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
- * @param notif reference to original notification
+ * @param contentIntent Intent to send when user taps on player
+ * @param appName Application title
* @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, View actionsContainer, Notification notif, String key) {
+ public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+ String appName, String key) {
- String appName = Notification.Builder.recoverBuilder(getContext(), notif)
- .loadHeaderAppName();
- super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, appName, key);
+ super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+ key);
// Media controls
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
+ if (actionsContainer != null) {
+ LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+ int i = 0;
+ for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
+ if (thatBtn == null || thatBtn.getDrawable() == null
+ || thatBtn.getVisibility() != View.VISIBLE) {
+ thisBtn.setVisibility(View.GONE);
+ continue;
+ }
+
+ Drawable thatIcon = thatBtn.getDrawable();
+ thisBtn.setImageDrawable(thatIcon.mutate());
+ thisBtn.setVisibility(View.VISIBLE);
+ thisBtn.setOnClickListener(v -> {
+ Log.d(TAG, "clicking on other button");
+ thatBtn.performClick();
+ });
}
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- Log.d(TAG, "clicking on other button");
- thatBtn.performClick();
- });
- }
-
- // Hide any unused buttons
- for (; i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
+ // Hide any unused buttons
+ for (; i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ thisBtn.setVisibility(View.GONE);
+ }
}
// Seek Bar
@@ -138,6 +199,10 @@
mBackgroundExecutor.execute(
() -> mSeekBarViewModel.updateController(controller, iconColor));
+ initLongPressMenu(iconColor);
+ }
+
+ private void initLongPressMenu(int iconColor) {
// Set up long press menu
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -145,7 +210,7 @@
View clearView = options.findViewById(R.id.remove);
clearView.setOnClickListener(b -> {
- mParent.removeMediaPlayer(QSMediaPlayer.this);
+ removePlayer();
});
ImageView removeIcon = options.findViewById(R.id.remove_icon);
removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
@@ -165,11 +230,9 @@
}
@Override
- public void clearControls() {
- super.clearControls();
-
+ protected void resetButtons() {
+ super.resetButtons();
mSeekBarViewModel.clearController();
-
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -192,4 +255,19 @@
public void setListening(boolean listening) {
mSeekBarViewModel.setListening(listening);
}
+
+ @Override
+ public void removePlayer() {
+ Log.d(TAG, "removing player from parent: " + mParent);
+ // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
+ mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
+ }
+
+ @Override
+ public String getMediaPlayerPackage() {
+ if (getController() == null) {
+ return mPackageName;
+ }
+ return super.getMediaPlayerPackage();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index fee0838..1252008 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -21,16 +21,26 @@
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
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;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
@@ -54,6 +64,7 @@
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaControlPanel;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -90,6 +101,7 @@
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ private final BroadcastDispatcher mBroadcastDispatcher;
private String mCachedSpecs = "";
protected final View mBrightnessView;
private final H mHandler = new H();
@@ -123,6 +135,19 @@
private BrightnessMirrorController mBrightnessMirrorController;
private View mDivider;
+ private boolean mHasLoadedMediaControls;
+
+ private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ if (!mHasLoadedMediaControls) {
+ loadMediaResumptionControls();
+ }
+ }
+ }
+ };
@Inject
public QSPanel(
@@ -142,6 +167,7 @@
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mLocalBluetoothManager = localBluetoothManager;
+ mBroadcastDispatcher = broadcastDispatcher;
setOrientation(VERTICAL);
@@ -176,7 +202,7 @@
updateResources();
mBrightnessController = new BrightnessController(getContext(),
- findViewById(R.id.brightness_slider), broadcastDispatcher);
+ findViewById(R.id.brightness_slider), mBroadcastDispatcher);
}
@Override
@@ -200,14 +226,16 @@
* 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, Icon 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!");
@@ -221,7 +249,14 @@
QSMediaPlayer player = null;
String packageName = notif.getPackageName();
for (QSMediaPlayer p : mMediaPlayers) {
- if (p.getMediaSessionToken().equals(token)) {
+ if (p.getKey() == null) {
+ // No notification key = loaded via mediabrowser, so just match on package
+ if (packageName.equals(p.getMediaPlayerPackage())) {
+ Log.d(TAG, "Found matching resume player by package: " + packageName);
+ player = p;
+ break;
+ }
+ } else if (p.getMediaSessionToken().equals(token)) {
Log.d(TAG, "Found matching player by token " + packageName);
player = p;
break;
@@ -262,8 +297,10 @@
}
Log.d(TAG, "setting player session");
- player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
- notif.getNotification(), key);
+ String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
+ .loadHeaderAppName();
+ player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
+ notif.getNotification().contentIntent, appName, key);
if (mMediaPlayers.size() > 0) {
((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
@@ -293,6 +330,74 @@
return true;
}
+ private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
+ @Override
+ public void addTrack(MediaDescription desc, ComponentName component,
+ QSMediaBrowser browser) {
+ if (component == null) {
+ Log.e(TAG, "Component cannot be null");
+ return;
+ }
+
+ Log.d(TAG, "adding track from browser: " + desc + ", " + component);
+ QSMediaPlayer player = new QSMediaPlayer(mContext, QSPanel.this,
+ null, mForegroundExecutor, mBackgroundExecutor);
+
+ String pkgName = component.getPackageName();
+
+ // Add controls to carousel
+ int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
+ int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(playerWidth,
+ LayoutParams.MATCH_PARENT);
+ lp.setMarginStart(padding);
+ lp.setMarginEnd(padding);
+ mMediaCarousel.addView(player.getView(), lp);
+ ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
+ mMediaPlayers.add(player);
+
+ int iconColor = Color.DKGRAY;
+ int bgColor = Color.LTGRAY;
+
+ MediaSession.Token token = browser.getToken();
+ player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
+ pkgName);
+ }
+ };
+
+ /**
+ * Load controls for resuming media, if available
+ */
+ private void loadMediaResumptionControls() {
+ if (!useQsMediaPlayer(mContext)) {
+ return;
+ }
+ Log.d(TAG, "Loading resumption controls");
+
+ // Look up saved components to resume
+ Context userContext = mContext.createContextAsUser(mContext.getUser(), 0);
+ SharedPreferences prefs = userContext.getSharedPreferences(
+ MediaControlPanel.MEDIA_PREFERENCES, Context.MODE_PRIVATE);
+ String listString = prefs.getString(MediaControlPanel.MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ Log.d(TAG, "No saved media components");
+ return;
+ }
+
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ Log.d(TAG, "components are: " + listString + " count " + components.length);
+ for (int i = 0; i < components.length && i < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ String[] info = components[i].split("/");
+ String packageName = info[0];
+ String className = info[1];
+ ComponentName component = new ComponentName(packageName, className);
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, mMediaBrowserCallback,
+ component);
+ browser.findRecentMedia();
+ }
+ mHasLoadedMediaControls = true;
+ }
+
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -343,6 +448,22 @@
mBrightnessMirrorController.addCallback(this);
}
mDumpManager.registerDumpable(getDumpableTag(), this);
+
+ if (getClass() == QSPanel.class) {
+ //TODO(ethibodeau) remove class check after media refactor in ag/11059751
+ // Only run this in QSPanel proper, not QQS
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, filter, null,
+ UserHandle.ALL);
+ mHasLoadedMediaControls = false;
+
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager.isUserUnlocked(mContext.getUserId())) {
+ // If it's already unlocked (like if dark theme was toggled), we can load now
+ loadMediaResumptionControls();
+ }
+ }
}
@Override
@@ -358,6 +479,7 @@
mBrightnessMirrorController.removeCallback(this);
}
mDumpManager.unregisterDumpable(getDumpableTag());
+ mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 6229672..89b36da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -59,6 +59,7 @@
* 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
@@ -67,8 +68,9 @@
* @param contentIntent Intent to send when user taps on the view
* @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon 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) {
@@ -83,7 +85,7 @@
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 9cee7e7..1a6a104 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 @@
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 @@
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 66bc177..fecb7b6 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.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 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 e1cc0b0..7233b72 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -36,6 +36,7 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Insets;
@@ -76,6 +77,7 @@
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 +106,6 @@
*/
static class SaveImageInBackgroundData {
public Bitmap image;
- public Uri imageUri;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
public int errorMsgResId;
@@ -112,13 +113,33 @@
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 +168,7 @@
private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final ScreenshotNotificationsController mNotificationsController;
+ private final UiEventLogger mUiEventLogger;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -163,9 +185,11 @@
private final LinearLayout mActionsView;
private final ImageView mBackgroundProtection;
private final FrameLayout mDismissButton;
+ private final ImageView mDismissImage;
private Bitmap mScreenBitmap;
private Animator mScreenshotAnimation;
+ private boolean mInDarkMode = false;
private float mScreenshotOffsetXPx;
private float mScreenshotOffsetYPx;
@@ -185,6 +209,7 @@
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 +224,11 @@
@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 +249,11 @@
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");
+ });
+ mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -329,6 +360,8 @@
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
+ updateDarkTheme();
+
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
@@ -427,6 +460,41 @@
}
/**
+ * Update assets (called when the dark theme status changes). We only need to update the dismiss
+ * button and the actions container background, since the buttons are re-inflated on demand.
+ */
+ private void reloadAssets() {
+ mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
+ mActionsContainer.setBackground(
+ mContext.getDrawable(R.drawable.action_chip_container_background));
+
+ }
+
+ /**
+ * Checks the current dark theme status and updates if it has changed.
+ */
+ private void updateDarkTheme() {
+ int currentNightMode = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ switch (currentNightMode) {
+ case Configuration.UI_MODE_NIGHT_NO:
+ // Night mode is not active, we're using the light theme
+ if (mInDarkMode) {
+ mInDarkMode = false;
+ reloadAssets();
+ }
+ break;
+ case Configuration.UI_MODE_NIGHT_YES:
+ // Night mode is active, we're using dark theme
+ if (!mInDarkMode) {
+ mInDarkMode = true;
+ reloadAssets();
+ }
+ break;
+ }
+ }
+
+ /**
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@@ -445,12 +513,14 @@
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 +528,12 @@
@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 +636,7 @@
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 +651,63 @@
} 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 f3614ff..095c32f 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.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 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 @@
// 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 c828c4c..170174d 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 @@
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 @@
SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
mContext = context;
+ mImageData = new GlobalScreenshot.SavedImageData();
// Prepare all the output metadata
mParams = data;
@@ -145,6 +147,7 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 0000000..20fa991
--- /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 811a8d9..fbcd6ba 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.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.SystemUI;
import com.android.systemui.util.NotificationChannels;
-import java.util.List;
-
import javax.inject.Inject;
/**
@@ -185,23 +182,20 @@
/**
* 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 8b8b6f8..a6e083b 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.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 @@
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 @@
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 @@
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 @@
@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/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
index fa1b026..825a7f3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -18,21 +18,31 @@
import android.content.Context
import android.os.UserHandle
+import androidx.annotation.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.util.Assert
-import javax.inject.Inject
-import javax.inject.Singleton
+import java.lang.IllegalStateException
/**
* Tracks a reference to the context for the current user
+ *
+ * Constructor is injected at SettingsModule
*/
-@Singleton
-class CurrentUserContextTracker @Inject constructor(
+class CurrentUserContextTracker internal constructor(
private val sysuiContext: Context,
broadcastDispatcher: BroadcastDispatcher
) {
private val userTracker: CurrentUserTracker
- var currentUserContext: Context
+ private var initialized = false
+
+ private var _curUserContext: Context? = null
+ val currentUserContext: Context
+ get() {
+ if (!initialized) {
+ throw IllegalStateException("Must initialize before getting context")
+ }
+ return _curUserContext!!
+ }
init {
userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -40,21 +50,21 @@
handleUserSwitched(newUserId)
}
}
-
- currentUserContext = makeUserContext(userTracker.currentUserId)
}
fun initialize() {
+ initialized = true
+ _curUserContext = makeUserContext(userTracker.currentUserId)
userTracker.startTracking()
}
- private fun handleUserSwitched(newUserId: Int) {
- currentUserContext = makeUserContext(newUserId)
+ @VisibleForTesting
+ fun handleUserSwitched(newUserId: Int) {
+ _curUserContext = makeUserContext(newUserId)
}
private fun makeUserContext(uid: Int): Context {
Assert.isMainThread()
- return sysuiContext.createContextAsUser(
- UserHandle.getUserHandleForUid(userTracker.currentUserId), 0)
+ return sysuiContext.createContextAsUser(UserHandle.of(uid), 0)
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
new file mode 100644
index 0000000..2c5c3ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -0,0 +1,48 @@
+/*
+ * 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.settings.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.CurrentUserContextTracker;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for classes found within the com.android.systemui.settings package.
+ */
+@Module
+public interface SettingsModule {
+
+ /**
+ * Provides and initializes a CurrentUserContextTracker
+ */
+ @Singleton
+ @Provides
+ static CurrentUserContextTracker provideCurrentUserContextTracker(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher) {
+ CurrentUserContextTracker tracker =
+ new CurrentUserContextTracker(context, broadcastDispatcher);
+ tracker.initialize();
+ return tracker;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 1b752452..8c24c54 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.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 @@
? 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 4e6df0a..d364689 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 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 @@
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 @@
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 @@
mBubbleController.collapseStack();
}
- mNotificationActivityStarter.onNotificationClicked(sbn, row);
+ mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
}
private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -107,4 +115,30 @@
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 0000000..fbf033b
--- /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/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 5236385..cb0c283 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -378,6 +378,7 @@
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
+ @Nullable
public Notification.BubbleMetadata getBubbleMetadata() {
return mBubbleMetadata;
}
@@ -385,7 +386,7 @@
/**
* Sets bubble metadata for this notification.
*/
- public void setBubbleMetadata(Notification.BubbleMetadata metadata) {
+ public void setBubbleMetadata(@Nullable Notification.BubbleMetadata metadata) {
mBubbleMetadata = metadata;
}
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 7f2f898..c975404 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 @@
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 @@
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/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 55a5935..bcc81a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -31,6 +31,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -98,7 +99,7 @@
private ShortcutInfo mShortcutInfo;
private String mConversationId;
private StatusBarNotification mSbn;
- private Notification.BubbleMetadata mBubbleMetadata;
+ @Nullable private Notification.BubbleMetadata mBubbleMetadata;
private Context mUserContext;
private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private boolean mIsDeviceProvisioned;
@@ -203,6 +204,7 @@
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
+ Notification.BubbleMetadata bubbleMetadata,
OnSettingsClickListener onSettingsClick,
OnSnoozeClickListener onSnoozeClickListener,
ConversationIconFactory conversationIconFactory,
@@ -224,7 +226,7 @@
mOnSnoozeClickListener = onSnoozeClickListener;
mIconFactory = conversationIconFactory;
mUserContext = userContext;
- mBubbleMetadata = entry.getBubbleMetadata();
+ mBubbleMetadata = bubbleMetadata;
mBuilderProvider = builderProvider;
mShortcutManager = shortcutManager;
@@ -538,7 +540,8 @@
Log.e(TAG, "Could not check conversation senders", e);
}
- boolean showAsBubble = mBubbleMetadata.getAutoExpandBubble()
+ boolean showAsBubble = mBubbleMetadata != null
+ && mBubbleMetadata.getAutoExpandBubble()
&& Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, 0) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 624fabc..1c808cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -366,7 +366,8 @@
final ExpandableNotificationRow row,
NotificationConversationInfo notificationInfoView) throws Exception {
NotificationGuts guts = row.getGuts();
- StatusBarNotification sbn = row.getEntry().getSbn();
+ NotificationEntry entry = row.getEntry();
+ StatusBarNotification sbn = entry.getSbn();
String packageName = sbn.getPackageName();
// Settings link is only valid for notifications that specify a non-system user
NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
@@ -407,8 +408,9 @@
mNotificationManager,
mVisualStabilityManager,
packageName,
- row.getEntry().getChannel(),
- row.getEntry(),
+ entry.getChannel(),
+ entry,
+ entry.getBubbleMetadata(),
onSettingsClick,
onSnoozeClickListener,
iconFactoryLoader,
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 2da2724..b96cff8 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
@@ -22,6 +22,7 @@
import android.app.Notification;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -187,8 +188,10 @@
com.android.systemui.R.id.quick_qs_panel);
StatusBarNotification sbn = mRow.getEntry().getSbn();
Notification notif = sbn.getNotification();
+ Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
panel.getMediaPlayer().setMediaSession(token,
- notif.getSmallIcon(),
+ iconDrawable,
+ notif.getLargeIcon(),
tintColor,
mBackgroundColor,
mActions,
@@ -198,7 +201,8 @@
QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
com.android.systemui.R.id.quick_settings_panel);
bigPanel.addMediaSession(token,
- notif.getSmallIcon(),
+ 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 7d422e3..f9119c7 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 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 @@
/**
* 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 @@
private boolean mIsGesturalModeEnabled;
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
+ private boolean mIsBackGestureAllowed;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -200,7 +203,7 @@
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 @@
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 @@
Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
updateIsEnabled();
+ startTracking();
}
/**
@@ -255,6 +269,7 @@
}
mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
updateIsEnabled();
+ stopTracking();
}
private void setRotationCallbacks(boolean enable) {
@@ -269,10 +284,13 @@
}
}
- 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 @@
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 @@
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 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 90bc075b..ae7867d 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.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 @@
}
};
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 d35e1e1..3e5eb5f 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.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 @@
private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
private int mStatusBarMode;
private int mNavigationBarMode;
+ private int mNavigationMode;
private final Color mDarkModeColor;
/**
@@ -84,11 +86,14 @@
@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 @@
}
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 84aecd4..2978772 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
@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 @@
@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 d24ccf3..6061b1e 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.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 @@
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 @@
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 @@
}
}
- 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 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 @@
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 1a6b415..bf52a7a 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 @@
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 53fa263..fbe3e9b 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.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 @@
*/
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 Lazy<AssistManager> mAssistManagerLazy;
- private final NotificationGroupManager mGroupManager;
- private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
- private final NotificationRemoteInputManager mRemoteInputManager;
- private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final ShadeController mShadeController;
- private final StatusBar mStatusBar;
- private final KeyguardStateController mKeyguardStateController;
- private final ActivityStarter mActivityStarter;
+ 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 FeatureFlags mFeatureFlags;
- private final StatusBarStateController mStatusBarStateController;
- private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
- private final MetricsLogger mMetricsLogger;
- private final Context mContext;
- private final NotificationPanelViewController mNotificationPanel;
- private final NotificationPresenter mPresenter;
- private final LockPatternUtils mLockPatternUtils;
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 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 final Lazy<AssistManager> mAssistManagerLazy;
+ private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotificationGroupManager mGroupManager;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ShadeController mShadeController;
+ private final KeyguardStateController mKeyguardStateController;
+ 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 StatusBarNotificationActivityStarterLogger mLogger;
+
+ private final StatusBar mStatusBar;
+ private final NotificationPresenter mPresenter;
+ private final NotificationPanelViewController mNotificationPanel;
+ private final ActivityLaunchAnimator mActivityLaunchAnimator;
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;
- mHeadsUpManager = headsUpManager;
- mActivityLaunchAnimator = activityLaunchAnimator;
- mBarService = statusBarService;
mCommandQueue = commandQueue;
+ mMainThreadHandler = mainThreadHandler;
+ mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
+ mEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
+ mHeadsUpManager = headsUpManager;
+ mActivityStarter = activityStarter;
+ mBarService = statusBarService;
+ mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
+ mBubbleController = bubbleController;
+ mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
+ mGroupManager = groupManager;
mLockscreenUserManager = lockscreenUserManager;
mShadeController = shadeController;
+ mKeyguardStateController = keyguardStateController;
+ mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+ mLockPatternUtils = lockPatternUtils;
+ mStatusBarRemoteInputCallback = remoteInputCallback;
+ mActivityIntentHelper = activityIntentHelper;
+
+ mFeatureFlags = featureFlags;
+ mMetricsLogger = metricsLogger;
+ mLogger = logger;
+
// 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;
- mFeatureFlags = featureFlags;
- mNotifPipeline = notifPipeline;
- mNotifCollection = notifCollection;
+ mPresenter = presenter;
+ mNotificationPanel = panel;
+ mActivityLaunchAnimator = activityLaunchAnimator;
+
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -192,11 +218,6 @@
}
});
}
-
- mStatusBarRemoteInputCallback = remoteInputCallback;
- mMainThreadHandler = mainThreadHandler;
- mActivityIntentHelper = activityIntentHelper;
- mBubbleController = bubbleController;
}
/**
@@ -207,6 +228,8 @@
*/
@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 @@
// 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
} 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 @@
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 @@
});
// 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 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 @@
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 KeyguardStateController mKeyguardStateController;
- private final MetricsLogger mMetricsLogger;
- private final LockPatternUtils mLockPatternUtils;
- private final Handler mMainThreadHandler;
- private final Handler mBackgroundHandler;
- private final Executor mUiBgExecutor;
- private final ActivityIntentHelper mActivityIntentHelper;
- private final BubbleController mBubbleController;
- private NotificationPanelViewController mNotificationPanelViewController;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final ShadeController mShadeController;
- private NotificationPresenter mNotificationPresenter;
- private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final KeyguardStateController mKeyguardStateController;
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ private final LockPatternUtils mLockPatternUtils;
+ private final StatusBarRemoteInputCallback mRemoteInputCallback;
+ private final ActivityIntentHelper mActivityIntentHelper;
+
+ private final FeatureFlags mFeatureFlags;
+ private final MetricsLogger mMetricsLogger;
+ private final StatusBarNotificationActivityStarterLogger mLogger;
+
private StatusBar mStatusBar;
+ private NotificationPresenter mNotificationPresenter;
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private ActivityLaunchAnimator mActivityLaunchAnimator;
@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 @@
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 @@
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 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 0000000..d118747
--- /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 051bd29..a284335 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 @@
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 b258fd4..5257ce4 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 @@
}
/**
+ * 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 d65b285..8880df9 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 @@
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 8625d63..db08d64 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 @@
/**
* 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 @@
/** 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 @@
}
/**
+ * 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 @@
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 @@
) {
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 @@
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 cc6d607..8acfbf2 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 @@
}
/**
+ * 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 0000000..aefdc99
--- /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 0000000..c03e10e
--- /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 7403a11..6c00eca 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.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 @@
@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 @@
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 @@
}
@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 9e18e61..3ef693a 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.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 @@
private DumpManager mDumpManager;
@Mock
private LockscreenLockIconController mLockIconController;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private BubbleData mBubbleData;
@@ -219,8 +222,7 @@
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 49236e0..8e6fc8a 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.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 @@
private KeyguardBypassController mKeyguardBypassController;
@Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
private SysUiState mSysUiState = new SysUiState();
@@ -206,8 +209,7 @@
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 f6ee46b..e3f25c6 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 @@
@Captor
private lateinit var controlLoadCallbackCaptor:
ArgumentCaptor<ControlsBindingController.LoadCallback>
+
@Captor
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
@Captor
@@ -97,6 +98,7 @@
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 @@
}
}
+ canceller = DidRunRunnable()
+ `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+
controller = ControlsControllerImpl(
wrapper,
delayableExecutor,
@@ -266,7 +271,7 @@
assertTrue(favorites.isEmpty())
assertFalse(data.errorOnLoad)
- })
+ }, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -301,7 +306,7 @@
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 @@
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 @@
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 @@
@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 @@
}
@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 @@
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 @@
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 e8e98b4..4ac5912 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 @@
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 11649ca..ffe3cd5 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 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 @@
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 @@
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 @@
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 @@
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 @@
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));
+ 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());
+ }
- 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());
- }
+ // 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/settings/CurrentUserContextTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
new file mode 100644
index 0000000..628c06a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.settings
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class CurrentUserContextTrackerTest : SysuiTestCase() {
+
+ private lateinit var tracker: CurrentUserContextTracker
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ allowTestableLooperAsMainThread()
+
+ // wrap Context so that tests don't throw for missing package errors
+ val wrapped = object : ContextWrapper(context) {
+ override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+ val mockContext = mock(Context::class.java)
+ `when`(mockContext.user).thenReturn(user)
+ `when`(mockContext.userId).thenReturn(user.identifier)
+ return mockContext
+ }
+ }
+
+ tracker = CurrentUserContextTracker(wrapped, broadcastDispatcher)
+ tracker.initialize()
+ }
+
+ @Test
+ fun testContextExistsAfterInit_noCrash() {
+ tracker.currentUserContext
+ }
+
+ @Test
+ fun testUserContextIsCorrectAfterUserSwitch() {
+ // We always start out with system ui test
+ assertTrue("Starting userId should be 0", tracker.currentUserContext.userId == 0)
+
+ // WHEN user changes
+ tracker.handleUserSwitched(1)
+
+ // THEN user context should have the correct userId
+ assertTrue("User has changed to userId 1, the context should reflect that",
+ tracker.currentUserContext.userId == 1)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Test(expected = IllegalStateException::class)
+ fun testContextTrackerThrowsExceptionWhenNotInitialized() {
+ // GIVEN an uninitialized CurrentUserContextTracker
+ val userTracker = CurrentUserContextTracker(context, broadcastDispatcher)
+
+ // WHEN client asks for a context
+ val userContext = userTracker.currentUserContext
+
+ // THEN an exception is thrown
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 61388b6..6db8685 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -48,9 +48,9 @@
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -91,6 +91,7 @@
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
@@ -147,14 +148,15 @@
private ShadeController mShadeController;
@Mock
private ConversationIconFactory mIconFactory;
- @Mock
- private Context mUserContext;
@Mock(answer = Answers.RETURNS_SELF)
private PriorityOnboardingDialogController.Builder mBuilder;
private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder;
+ @Mock
+ private Notification.BubbleMetadata mBubbleMetadata;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
@@ -228,6 +230,11 @@
when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
.thenReturn(mConversationChannel);
+
+ when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+ .thenReturn(mock(NotificationManager.Policy.class));
+
+ when(mBuilder.build()).thenReturn(mock(PriorityOnboardingDialogController.class));
}
@Test
@@ -240,10 +247,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
@@ -261,10 +269,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
@@ -283,7 +292,8 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
- null,
+ mBubbleMetadata,
+ null,
null,
null,
true);
@@ -308,10 +318,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
@@ -331,10 +342,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
@@ -353,10 +365,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
@@ -382,10 +395,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
entry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
@@ -404,13 +418,14 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mConversationChannel, c);
latch.countDown();
},
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -430,10 +445,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -451,13 +467,14 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
latch.countDown();
},
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
false);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -476,10 +493,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.silence);
@@ -501,10 +519,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
@@ -529,10 +548,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
@@ -556,10 +576,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -596,10 +617,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -635,10 +657,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -675,10 +698,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -709,10 +733,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -741,10 +766,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -774,10 +800,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -807,10 +834,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -839,10 +867,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -870,10 +899,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -892,10 +922,11 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -910,13 +941,12 @@
// GIVEN the priority onboarding screen is present
PriorityOnboardingDialogController.Builder b =
- new PriorityOnboardingDialogController.Builder();
+ mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
PriorityOnboardingDialogController controller =
mock(PriorityOnboardingDialogController.class);
when(b.build()).thenReturn(controller);
// GIVEN the user is changing conversation settings
- when(mBuilderProvider.get()).thenReturn(b);
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
@@ -925,11 +955,12 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
- mBuilderProvider,
+ mContext,
+ () -> b,
true);
// WHEN user clicks "priority"
@@ -945,12 +976,11 @@
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true);
PriorityOnboardingDialogController.Builder b =
- new PriorityOnboardingDialogController.Builder();
+ mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
PriorityOnboardingDialogController controller =
mock(PriorityOnboardingDialogController.class);
when(b.build()).thenReturn(controller);
- when(mBuilderProvider.get()).thenReturn(b);
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
@@ -959,11 +989,12 @@
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
- mBuilderProvider,
+ mContext,
+ () -> b,
true);
// WHEN user clicks "priority"
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 2b091f2..210744e 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 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 @@
@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 8ab660c..3e46907 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 @@
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 27a5002..14c6e9f9 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 @@
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 @@
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 dd28687..1afe132 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 @@
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 962d773..aef454f 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.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 @@
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 @@
}
protected void verifyHasNoSims(boolean hasNoSimsVisible) {
- Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
eq(hasNoSimsVisible), eq(false));
}
@@ -395,7 +396,7 @@
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 @@
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 @@
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 9c250c5..988e022 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 @@
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 @@
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 0000000..00f37ae
--- /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/packages/Tethering/res/values-mcc310-mnc004/strings.xml b/packages/Tethering/res/values-mcc310-mnc004/strings.xml
index 9dadd49..ce9ff60 100644
--- a/packages/Tethering/res/values-mcc310-mnc004/strings.xml
+++ b/packages/Tethering/res/values-mcc310-mnc004/strings.xml
@@ -25,6 +25,4 @@
<string name="upstream_roaming_notification_title">Hotspot or tethering is on</string>
<!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
<string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string>
- <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
- <string name="upstream_roaming_notification_continue_button">Continue</string>
</resources>
diff --git a/packages/Tethering/res/values-mcc310-mnc120/strings.xml b/packages/Tethering/res/values-mcc310-mnc120/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc310-mnc120/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- String for tethered notification title with client number info. -->
- <plurals name="tethered_notification_title_with_client_number">
- <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
- <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
- </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values-mcc311-mnc480/strings.xml b/packages/Tethering/res/values-mcc311-mnc480/strings.xml
index 9dadd49..ce9ff60 100644
--- a/packages/Tethering/res/values-mcc311-mnc480/strings.xml
+++ b/packages/Tethering/res/values-mcc311-mnc480/strings.xml
@@ -25,6 +25,4 @@
<string name="upstream_roaming_notification_title">Hotspot or tethering is on</string>
<!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
<string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string>
- <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
- <string name="upstream_roaming_notification_continue_button">Continue</string>
</resources>
diff --git a/packages/Tethering/res/values-mcc311-mnc490/strings.xml b/packages/Tethering/res/values-mcc311-mnc490/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc311-mnc490/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- String for tethered notification title with client number info. -->
- <plurals name="tethered_notification_title_with_client_number">
- <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
- <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
- </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values-mcc312-mnc530/strings.xml b/packages/Tethering/res/values-mcc312-mnc530/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc312-mnc530/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- String for tethered notification title with client number info. -->
- <plurals name="tethered_notification_title_with_client_number">
- <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
- <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
- </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 66fbefc..83c99d2 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -158,51 +158,6 @@
<!-- ComponentName of the service used to run no ui tether provisioning. -->
<string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
- <!-- Enable tethering notification -->
- <!-- Icons for showing tether enable notification.
- Each item should have two elements and be separated with ";".
-
- The first element is downstream types which is one of tethering. This element has to be
- made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
- types and use "," to separate each combinations. Such as
-
- USB|BT,WIFI|USB|BT
-
- The second element is icon for the item. This element has to be composed by
- <package name>:drawable/<resource name>. Such as
-
- 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
- 2. android:drawable/xxx
-
- So the entire string of each item would be
-
- USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
-
- NOTE: One config can be separated into two or more for readability. Such as
-
- WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
-
- can be separated into
-
- WIFI|USB;android:drawable/xxx
- WIFI|BT;android:drawable/xxx
- USB|BT;android:drawable/xxx
- WIFI|USB|BT;android:drawable/xxx
-
- Notification will not show if the downstream type isn't listed in array.
- Empty array means disable notifications. -->
- <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed
- an icon on the right side already -->
- <string-array translatable="false" name="tethering_notification_icons">
- <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item>
- <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item>
- <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item>
- </string-array>
- <!-- String for tether enable notification title. -->
- <string name="tethering_notification_title">@string/tethered_notification_title</string>
- <!-- String for tether enable notification message. -->
- <string name="tethering_notification_message">@string/tethered_notification_message</string>
-
<!-- No upstream notification is shown when there is a downstream but no upstream that is able
to do the tethering. -->
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index bbba3f3..16ae8ad 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -32,44 +32,6 @@
<item type="string" name="config_mobile_hotspot_provision_response"/>
<item type="integer" name="config_mobile_hotspot_provision_check_period"/>
<item type="string" name="config_wifi_tether_enable"/>
- <!-- Configuration values for TetheringNotificationUpdater -->
- <!-- Icons for showing tether enable notification.
- Each item should have two elements and be separated with ";".
-
- The first element is downstream types which is one of tethering. This element has to be
- made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
- types and use "," to separate each combinations. Such as
-
- USB|BT,WIFI|USB|BT
-
- The second element is icon for the item. This element has to be composed by
- <package name>:drawable/<resource name>. Such as
-
- 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
- 2. android:drawable/xxx
-
- So the entire string of each item would be
-
- USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
-
- NOTE: One config can be separated into two or more for readability. Such as
-
- WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
-
- can be separated into
-
- WIFI|USB;android:drawable/xxx
- WIFI|BT;android:drawable/xxx
- USB|BT;android:drawable/xxx
- WIFI|USB|BT;android:drawable/xxx
-
- Notification will not show if the downstream type isn't listed in array.
- Empty array means disable notifications. -->
- <item type="array" name="tethering_notification_icons"/>
- <!-- String for tether enable notification title. -->
- <item type="string" name="tethering_notification_title"/>
- <!-- String for tether enable notification message. -->
- <item type="string" name="tethering_notification_message"/>
<!-- Params from config.xml that can be overlaid -->
</policy>
</overlayable>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index 4fa60d4..d63c7c5 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -19,9 +19,6 @@
<string name="tethered_notification_title">Tethering or hotspot active</string>
<!-- String for tethered notification message [CHAR LIMIT=200] -->
<string name="tethered_notification_message">Tap to set up.</string>
- <!-- String for tethered notification title with client number info. -->
- <plurals name="tethered_notification_title_with_client_number">
- </plurals>
<!-- This notification is shown when tethering has been disabled on a user's device.
The device is managed by the user's employer. Tethering can't be turned on unless the
@@ -47,6 +44,4 @@
<string name="upstream_roaming_notification_title"></string>
<!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
<string name="upstream_roaming_notification_message"></string>
- <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
- <string name="upstream_roaming_notification_continue_button"></string>
</resources>
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 85a23fb..55344fc 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -142,7 +142,7 @@
public boolean initOffloadConfig() {
IOffloadConfig offloadConfig;
try {
- offloadConfig = IOffloadConfig.getService();
+ offloadConfig = IOffloadConfig.getService(true /*retry*/);
} catch (RemoteException e) {
mLog.e("getIOffloadConfig error " + e);
return false;
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index f490cc4..7fd6b61 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -17,9 +17,6 @@
package com.android.networkstack.tethering;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.TetheringManager.TETHERING_BLUETOOTH;
-import static android.net.TetheringManager.TETHERING_USB;
-import static android.net.TetheringManager.TETHERING_WIFI;
import static android.text.TextUtils.isEmpty;
import android.app.Notification;
@@ -39,10 +36,8 @@
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.util.Log;
import android.util.SparseArray;
-import androidx.annotation.ArrayRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
@@ -77,9 +72,6 @@
private static final boolean NO_NOTIFY = false;
@VisibleForTesting
static final int EVENT_SHOW_NO_UPSTREAM = 1;
- // Id to update and cancel enable notification. Must be unique within the tethering app.
- @VisibleForTesting
- static final int ENABLE_NOTIFICATION_ID = 1000;
// Id to update and cancel restricted notification. Must be unique within the tethering app.
@VisibleForTesting
static final int RESTRICTED_NOTIFICATION_ID = 1001;
@@ -120,7 +112,6 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {
- ENABLE_NOTIFICATION_ID,
RESTRICTED_NOTIFICATION_ID,
NO_UPSTREAM_NOTIFICATION_ID,
ROAMING_NOTIFICATION_ID
@@ -223,7 +214,6 @@
final boolean tetheringActiveChanged =
(downstreamTypes == DOWNSTREAM_NONE) != (mDownstreamTypesMask == DOWNSTREAM_NONE);
final boolean subIdChanged = subId != mActiveDataSubId;
- final boolean downstreamChanged = downstreamTypes != mDownstreamTypesMask;
final boolean upstreamChanged = noUpstream != mNoUpstream;
final boolean roamingChanged = isRoaming != mRoaming;
final boolean updateAll = tetheringActiveChanged || subIdChanged;
@@ -232,19 +222,10 @@
mNoUpstream = noUpstream;
mRoaming = isRoaming;
- if (updateAll || downstreamChanged) updateEnableNotification();
if (updateAll || upstreamChanged) updateNoUpstreamNotification();
if (updateAll || roamingChanged) updateRoamingNotification();
}
- private void updateEnableNotification() {
- final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
-
- if (tetheringInactive || setupNotification() == NO_NOTIFY) {
- clearNotification(ENABLE_NOTIFICATION_ID);
- }
- }
-
private void updateNoUpstreamNotification() {
final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
@@ -310,64 +291,6 @@
NO_UPSTREAM_NOTIFICATION_ID, null /* pendingIntent */, action);
}
- /**
- * Returns the downstream types mask which convert from given string.
- *
- * @param types This string has to be made by "WIFI", "USB", "BT", and OR'd with the others.
- *
- * @return downstream types mask value.
- */
- @VisibleForTesting
- @IntRange(from = 0, to = 7)
- int getDownstreamTypesMask(@NonNull final String types) {
- int downstreamTypesMask = DOWNSTREAM_NONE;
- final String[] downstreams = types.split("\\|");
- for (String downstream : downstreams) {
- if (USB_DOWNSTREAM.equals(downstream.trim())) {
- downstreamTypesMask |= (1 << TETHERING_USB);
- } else if (WIFI_DOWNSTREAM.equals(downstream.trim())) {
- downstreamTypesMask |= (1 << TETHERING_WIFI);
- } else if (BLUETOOTH_DOWNSTREAM.equals(downstream.trim())) {
- downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
- }
- }
- return downstreamTypesMask;
- }
-
- /**
- * Returns the icons {@link android.util.SparseArray} which get from given string-array resource
- * id.
- *
- * @param id String-array resource id
- *
- * @return {@link android.util.SparseArray} with downstream types and icon id info.
- */
- @NonNull
- @VisibleForTesting
- SparseArray<Integer> getIcons(@ArrayRes int id, @NonNull Resources res) {
- final String[] array = res.getStringArray(id);
- final SparseArray<Integer> icons = new SparseArray<>();
- for (String config : array) {
- if (isEmpty(config)) continue;
-
- final String[] elements = config.split(";");
- if (elements.length != 2) {
- Log.wtf(TAG,
- "Unexpected format in Tethering notification configuration : " + config);
- continue;
- }
-
- final String[] types = elements[0].split(",");
- for (String type : types) {
- int mask = getDownstreamTypesMask(type);
- if (mask == DOWNSTREAM_NONE) continue;
- icons.put(mask, res.getIdentifier(
- elements[1].trim(), null /* defType */, null /* defPackage */));
- }
- }
- return icons;
- }
-
private boolean setupRoamingNotification() {
final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
final boolean upstreamRoamingNotification =
@@ -403,29 +326,6 @@
return NOTIFY_DONE;
}
- private boolean setupNotification() {
- final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
- final SparseArray<Integer> downstreamIcons =
- getIcons(R.array.tethering_notification_icons, res);
-
- final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID);
- if (iconId == NO_ICON_ID) return NO_NOTIFY;
-
- final String title = res.getString(R.string.tethering_notification_title);
- final String message = res.getString(R.string.tethering_notification_message);
- if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY;
-
- final PendingIntent pi = PendingIntent.getActivity(
- mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
- 0 /* requestCode */,
- new Intent(Settings.ACTION_TETHER_SETTINGS),
- Intent.FLAG_ACTIVITY_NEW_TASK,
- null /* options */);
-
- showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID, pi, new Action[0]);
- return NOTIFY_DONE;
- }
-
private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
@NonNull final String message, @NotificationId final int id, @Nullable PendingIntent pi,
@NonNull final Action... actions) {
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 6b751af..3305ed0 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -69,6 +69,7 @@
test_config: "AndroidTest_Coverage.xml",
defaults: ["libnetworkstackutilsjni_deps"],
static_libs: [
+ "NetworkStaticLibTestsLib",
"NetworkStackTestsLib",
"TetheringTestsLib",
"TetheringIntegrationTestsLib",
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 04f31a7..745468f 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -20,8 +20,6 @@
import android.app.NotificationManager
import android.content.Context
import android.content.res.Resources
-import android.net.ConnectivityManager.TETHERING_BLUETOOTH
-import android.net.ConnectivityManager.TETHERING_USB
import android.net.ConnectivityManager.TETHERING_WIFI
import android.os.Handler
import android.os.HandlerThread
@@ -29,14 +27,12 @@
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
import android.os.UserHandle
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.TelephonyManager
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
import com.android.internal.util.test.BroadcastInterceptingContext
import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
-import com.android.networkstack.tethering.TetheringNotificationUpdater.ENABLE_NOTIFICATION_ID
import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
@@ -63,17 +59,9 @@
import org.mockito.MockitoAnnotations
const val TEST_SUBID = 1
-const val WIFI_ICON_ID = 1
-const val USB_ICON_ID = 2
-const val BT_ICON_ID = 3
-const val GENERAL_ICON_ID = 4
const val WIFI_MASK = 1 shl TETHERING_WIFI
-const val USB_MASK = 1 shl TETHERING_USB
-const val BT_MASK = 1 shl TETHERING_BLUETOOTH
-const val TITLE = "Tethering active"
-const val MESSAGE = "Tap here to set up."
-const val TEST_TITLE = "Hotspot active"
-const val TEST_MESSAGE = "Tap to set up hotspot."
+const val TEST_DISALLOW_TITLE = "Tether function is disallowed"
+const val TEST_DISALLOW_MESSAGE = "Please contact your admin"
const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access"
const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet."
const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot"
@@ -88,7 +76,6 @@
@Mock private lateinit var mockContext: Context
@Mock private lateinit var notificationManager: NotificationManager
@Mock private lateinit var telephonyManager: TelephonyManager
- @Mock private lateinit var defaultResources: Resources
@Mock private lateinit var testResources: Resources
// lateinit for these classes under test, as they should be reset to a different instance for
@@ -97,11 +84,6 @@
private lateinit var notificationUpdater: TetheringNotificationUpdater
private lateinit var fakeTetheringThread: HandlerThread
- private val ENABLE_ICON_CONFIGS = arrayOf(
- "USB;android.test:drawable/usb", "BT;android.test:drawable/bluetooth",
- "WIFI|BT;android.test:drawable/general", "WIFI|USB;android.test:drawable/general",
- "USB|BT;android.test:drawable/general", "WIFI|USB|BT;android.test:drawable/general")
-
private val ROAMING_CAPABILITIES = NetworkCapabilities()
private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING)
private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general
@@ -117,29 +99,19 @@
private inner class WrappedNotificationUpdater(c: Context, looper: Looper)
: TetheringNotificationUpdater(c, looper) {
- override fun getResourcesForSubId(context: Context, subId: Int) =
- when (subId) {
- TEST_SUBID -> testResources
- INVALID_SUBSCRIPTION_ID -> defaultResources
- else -> super.getResourcesForSubId(context, subId)
- }
+ override fun getResourcesForSubId(c: Context, subId: Int) =
+ if (subId == TEST_SUBID) testResources else super.getResourcesForSubId(c, subId)
}
private fun setupResources() {
- doReturn(ENABLE_ICON_CONFIGS).`when`(defaultResources)
- .getStringArray(R.array.tethering_notification_icons)
- doReturn(arrayOf("WIFI;android.test:drawable/wifi")).`when`(testResources)
- .getStringArray(R.array.tethering_notification_icons)
doReturn(5).`when`(testResources)
.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
doReturn(true).`when`(testResources)
.getBoolean(R.bool.config_upstream_roaming_notification)
- doReturn(TITLE).`when`(defaultResources).getString(R.string.tethering_notification_title)
- doReturn(MESSAGE).`when`(defaultResources)
- .getString(R.string.tethering_notification_message)
- doReturn(TEST_TITLE).`when`(testResources).getString(R.string.tethering_notification_title)
- doReturn(TEST_MESSAGE).`when`(testResources)
- .getString(R.string.tethering_notification_message)
+ doReturn(TEST_DISALLOW_TITLE).`when`(testResources)
+ .getString(R.string.disable_tether_notification_title)
+ doReturn(TEST_DISALLOW_MESSAGE).`when`(testResources)
+ .getString(R.string.disable_tether_notification_message)
doReturn(TEST_NO_UPSTREAM_TITLE).`when`(testResources)
.getString(R.string.no_upstream_notification_title)
doReturn(TEST_NO_UPSTREAM_MESSAGE).`when`(testResources)
@@ -150,14 +122,6 @@
.getString(R.string.upstream_roaming_notification_title)
doReturn(TEST_ROAMING_MESSAGE).`when`(testResources)
.getString(R.string.upstream_roaming_notification_message)
- doReturn(USB_ICON_ID).`when`(defaultResources)
- .getIdentifier(eq("android.test:drawable/usb"), any(), any())
- doReturn(BT_ICON_ID).`when`(defaultResources)
- .getIdentifier(eq("android.test:drawable/bluetooth"), any(), any())
- doReturn(GENERAL_ICON_ID).`when`(defaultResources)
- .getIdentifier(eq("android.test:drawable/general"), any(), any())
- doReturn(WIFI_ICON_ID).`when`(testResources)
- .getIdentifier(eq("android.test:drawable/wifi"), any(), any())
}
@Before
@@ -206,119 +170,27 @@
}
@Test
- fun testNotificationWithDownstreamChanged() {
- // Wifi downstream. No notification.
- notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID))
-
- // Same downstream changed. Nothing happened.
- notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyZeroInteractions(notificationManager)
-
- // Wifi and usb downstreams. Show enable notification
- notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK)
- verifyNotification(GENERAL_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
- // Usb downstream. Still show enable notification.
- notificationUpdater.onDownstreamChanged(USB_MASK)
- verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
- // No downstream. No notification.
- notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
- }
-
- @Test
- fun testNotificationWithActiveDataSubscriptionIdChanged() {
- // Usb downstream. Showed enable notification with default resource.
- notificationUpdater.onDownstreamChanged(USB_MASK)
- verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
- // Same subId changed. Nothing happened.
- notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
- verifyZeroInteractions(notificationManager)
-
- // Set test sub id. Clear notification with test resource.
+ fun testRestrictedNotification() {
+ // Set test sub id.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
-
- // Wifi downstream. Show enable notification with test resource.
- notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
-
- // No downstream. No notification.
- notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
- }
-
- private fun assertIconNumbers(number: Int, configs: Array<String?>) {
- doReturn(configs).`when`(defaultResources)
- .getStringArray(R.array.tethering_notification_icons)
- assertEquals(number, notificationUpdater.getIcons(
- R.array.tethering_notification_icons, defaultResources).size())
- }
-
- @Test
- fun testGetIcons() {
- assertIconNumbers(0, arrayOfNulls<String>(0))
- assertIconNumbers(0, arrayOf(null, ""))
- assertIconNumbers(3, arrayOf(
- // These configurations are invalid with wrong strings or symbols.
- ";", ",", "|", "|,;", "WIFI", "1;2", " U SB; ", "bt;", "WIFI;USB;BT", "WIFI|USB|BT",
- "WIFI,BT,USB", " WIFI| | | USB, test:drawable/test",
- // This configuration is valid with two downstream types (USB, BT).
- "USB|,,,,,|BT;drawable/test ",
- // This configuration is valid with one downstream types (WIFI).
- " WIFI ; android.test:drawable/xxx "))
- }
-
- @Test
- fun testGetDownstreamTypesMask() {
- assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask(""))
- assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("1"))
- assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("WIFI_P2P"))
- assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("usb"))
- assertEquals(WIFI_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI "))
- assertEquals(USB_MASK, notificationUpdater.getDownstreamTypesMask("USB | B T"))
- assertEquals(BT_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI: | BT"))
- assertEquals(WIFI_MASK or USB_MASK,
- notificationUpdater.getDownstreamTypesMask("1|2|USB|WIFI|BLUETOOTH||"))
- }
-
- @Test
- fun testSetupRestrictedNotification() {
- val title = context.resources.getString(R.string.disable_tether_notification_title)
- val message = context.resources.getString(R.string.disable_tether_notification_message)
- val disallowTitle = "Tether function is disallowed"
- val disallowMessage = "Please contact your admin"
- doReturn(title).`when`(defaultResources)
- .getString(R.string.disable_tether_notification_title)
- doReturn(message).`when`(defaultResources)
- .getString(R.string.disable_tether_notification_message)
- doReturn(disallowTitle).`when`(testResources)
- .getString(R.string.disable_tether_notification_title)
- doReturn(disallowMessage).`when`(testResources)
- .getString(R.string.disable_tether_notification_message)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
// User restrictions on. Show restricted notification.
notificationUpdater.notifyTetheringDisabledByRestriction()
- verifyNotification(NOTIFICATION_ICON_ID, title, message, RESTRICTED_NOTIFICATION_ID)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
+ RESTRICTED_NOTIFICATION_ID)
// User restrictions off. Clear notification.
notificationUpdater.tetheringRestrictionLifted()
verifyNotificationCancelled(listOf(RESTRICTED_NOTIFICATION_ID))
- // Set test sub id. No notification.
- notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
+ // No downstream.
+ notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
+ verifyZeroInteractions(notificationManager)
- // User restrictions on again. Show restricted notification with test resource.
+ // User restrictions on again. Show restricted notification.
notificationUpdater.notifyTetheringDisabledByRestriction()
- verifyNotification(NOTIFICATION_ICON_ID, disallowTitle, disallowMessage,
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
RESTRICTED_NOTIFICATION_ID)
}
@@ -356,15 +228,14 @@
}
@Test
- fun testNotificationWithUpstreamCapabilitiesChanged_NoUpstream() {
- // Set test sub id. No notification.
+ fun testNoUpstreamNotification() {
+ // Set test sub id.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
- // Wifi downstream. Show enable notification with test resource.
+ // Wifi downstream.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
// There is no upstream. Show no upstream notification.
notificationUpdater.onUpstreamCapabilitiesChanged(null)
@@ -386,15 +257,14 @@
verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
NO_UPSTREAM_NOTIFICATION_ID)
- // No downstream. No notification.
+ // No downstream.
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
- // Put up enable notification with wifi downstream and home capabilities.
+ // Wifi downstream and home capabilities.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES)
- verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
// Set R.integer.delay_to_show_no_upstream_after_no_backhaul to -1 and change to no upstream
// again. Don't put up no upstream notification.
@@ -429,15 +299,14 @@
}
@Test
- fun testNotificationWithUpstreamCapabilitiesChanged_Roaming() {
- // Set test sub id. Clear notification.
+ fun testRoamingNotification() {
+ // Set test sub id.
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
- // Wifi downstream. Show enable notification with test resource.
+ // Wifi downstream.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
// Upstream capabilities changed to roaming state. Show roaming notification.
notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
@@ -464,14 +333,16 @@
verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
NO_UPSTREAM_NOTIFICATION_ID)
- // No downstream. No notification.
+ // No downstream.
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
- verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
- ROAMING_NOTIFICATION_ID))
+ verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
- // Wifi downstream again. Show enable notification with test resource.
+ // Wifi downstream again.
notificationUpdater.onDownstreamChanged(WIFI_MASK)
- verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+ notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+ verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
+ verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
+ NO_UPSTREAM_NOTIFICATION_ID)
// Set R.bool.config_upstream_roaming_notification to false and change upstream
// network to roaming state again. No roaming notification.
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 132b692..da9bdf3 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 @@
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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f21f0e7..1a72cf0 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2732,7 +2732,7 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+ new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
callback, resultReceiver);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 20a11bd..b36626f 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -18,6 +18,8 @@
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.Process;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -28,9 +30,12 @@
*/
final class AccessibilityShellCommand extends ShellCommand {
final @NonNull AccessibilityManagerService mService;
+ final @NonNull SystemActionPerformer mSystemActionPerformer;
- AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+ AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
+ @NonNull SystemActionPerformer systemActionPerformer) {
mService = service;
+ mSystemActionPerformer = systemActionPerformer;
}
@Override
@@ -45,6 +50,9 @@
case "set-bind-instant-service-allowed": {
return runSetBindInstantServiceAllowed();
}
+ case "call-system-action": {
+ return runCallSystemAction();
+ }
}
return -1;
}
@@ -74,6 +82,22 @@
return 0;
}
+ private int runCallSystemAction() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID
+ && callingUid != Process.SYSTEM_UID
+ && callingUid != Process.SHELL_UID) {
+ return -1;
+ }
+ final String option = getNextArg();
+ if (option != null) {
+ int actionId = Integer.parseInt(option);
+ mSystemActionPerformer.performSystemAction(actionId);
+ return 0;
+ }
+ return -1;
+ }
+
private Integer parseUserId() {
final String option = getNextOption();
if (option != null) {
@@ -97,5 +121,7 @@
pw.println(" Set whether binding to services provided by instant apps is allowed.");
pw.println(" get-bind-instant-service-allowed [--user <USER_ID>]");
pw.println(" Get whether binding to services provided by instant apps is allowed.");
+ pw.println(" call-system-action <ACTION_ID>");
+ pw.println(" Calls the system action with the given action id.");
}
}
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index ef8d524..a1fc3fa 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;
@@ -301,7 +303,11 @@
return lockScreen();
case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT:
return takeScreenshot();
+ case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK :
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+ return true;
default:
+ Slog.e(TAG, "Invalid action id: " + actionId);
return false;
}
} finally {
@@ -395,7 +401,8 @@
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 b27c5d5..35089d6 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.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 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 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 @@
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 @@
}
@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 c27ec66..81de29c 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -480,6 +480,12 @@
public abstract void pruneInstantApps();
/**
+ * Prunes the cache of the APKs in the given APEXes.
+ * @param apexPackages The list of APEX packages that may contain APK-in-APEX.
+ */
+ public abstract void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages);
+
+ /**
* @return The SetupWizard package name.
*/
public abstract String getSetupWizardPackageName();
@@ -977,4 +983,9 @@
* 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 ce65110..4009caf 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -603,8 +603,7 @@
}
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 2bbf278..97a5cfe 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1146,7 +1146,7 @@
&& 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 @@
// 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 dbad562..b647818 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 @@
@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/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index f4d7f9a..36d69c9 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -89,10 +89,8 @@
private final BrightnessMappingStrategy mBrightnessMapper;
// The minimum and maximum screen brightnesses.
- private final int mScreenBrightnessRangeMinimum;
- private final int mScreenBrightnessRangeMaximum;
- private final float mScreenBrightnessRangeMinimumFloat;
- private final float mScreenBrightnessRangeMaximumFloat;
+ private final float mScreenBrightnessRangeMinimum;
+ private final float mScreenBrightnessRangeMaximum;
// How much to scale doze brightness by (should be (0, 1.0]).
private final float mDozeScaleFactor;
@@ -156,7 +154,6 @@
// The screen brightness threshold at which to brighten or darken the screen.
private float mScreenBrighteningThreshold;
private float mScreenDarkeningThreshold;
-
// The most recent light sample.
private float mLastObservedLux;
@@ -177,8 +174,9 @@
// We preserve this value even when we stop using the light sensor so
// that we can quickly revert to the previous auto-brightness level
// while the light sensor warms up.
- // Use -1 if there is no current auto-brightness value available.
- private int mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID;
+ // Use PowerManager.BRIGHTNESS_INVALID_FLOAT if there is no current auto-brightness value
+ // available.
+ private float mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
// The current display policy. This is useful, for example, for knowing when we're dozing,
// where the light sensor may not be available.
@@ -188,7 +186,7 @@
// for the initial state of the sample.
private boolean mBrightnessAdjustmentSamplePending;
private float mBrightnessAdjustmentSampleOldLux;
- private int mBrightnessAdjustmentSampleOldBrightness;
+ private float mBrightnessAdjustmentSampleOldBrightness;
// When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
// user's adjustment) immediately, but wait for a drastic enough change in the ambient light.
@@ -243,13 +241,8 @@
mCallbacks = callbacks;
mSensorManager = sensorManager;
mBrightnessMapper = mapper;
- mScreenBrightnessRangeMinimum =
- BrightnessSynchronizer.brightnessFloatToInt(mContext, brightnessMin);
- mScreenBrightnessRangeMaximum =
- com.android.internal.BrightnessSynchronizer.brightnessFloatToInt(
- mContext, brightnessMax);
- mScreenBrightnessRangeMinimumFloat = brightnessMin;
- mScreenBrightnessRangeMaximumFloat = brightnessMax;
+ mScreenBrightnessRangeMinimum = brightnessMin;
+ mScreenBrightnessRangeMaximum = brightnessMax;
mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
mDozeScaleFactor = dozeScaleFactor;
mNormalLightSensorRate = lightSensorRate;
@@ -299,12 +292,12 @@
return true;
}
- public int getAutomaticScreenBrightness() {
+ public float getAutomaticScreenBrightness() {
if (!mAmbientLuxValid) {
- return -1;
+ return PowerManager.BRIGHTNESS_INVALID_FLOAT;
}
if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
- return Math.round(mScreenAutoBrightness * mDozeScaleFactor);
+ return mScreenAutoBrightness * mDozeScaleFactor;
}
return mScreenAutoBrightness;
}
@@ -385,7 +378,7 @@
private boolean setScreenBrightnessByUser(float brightness) {
if (!mAmbientLuxValid) {
- // If we don't have a valid ambient lux then we don't have a valid brightness anyways,
+ // If we don't have a valid ambient lux then we don't have a valid brightness anyway,
// and we can't use this data to add a new control point to the short-term model.
return false;
}
@@ -486,7 +479,7 @@
} else if (mLightSensorEnabled) {
mLightSensorEnabled = false;
mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
- mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID;
+ mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mRecentLightSamples = 0;
mAmbientLightRingBuffer.clear();
mCurrentLightSensorRate = -1;
@@ -735,29 +728,28 @@
float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
mForegroundAppCategory);
- int newScreenAutoBrightness = BrightnessSynchronizer.brightnessFloatToInt(
- mContext, clampScreenBrightnessFloat(value));
+ float newScreenAutoBrightness = clampScreenBrightness(value);
// If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
// in which case we ignore the new screen brightness if it doesn't differ enough from the
// previous one.
- if (mScreenAutoBrightness != -1
+ if (!Float.isNaN(mScreenAutoBrightness)
&& !isManuallySet
&& newScreenAutoBrightness > mScreenDarkeningThreshold
&& newScreenAutoBrightness < mScreenBrighteningThreshold) {
if (mLoggingEnabled) {
- Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold
- + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold);
+ Slog.d(TAG, "ignoring newScreenAutoBrightness: "
+ + mScreenDarkeningThreshold + " < " + newScreenAutoBrightness
+ + " < " + mScreenBrighteningThreshold);
}
return;
}
-
- if (mScreenAutoBrightness != newScreenAutoBrightness) {
+ if (!BrightnessSynchronizer.floatEquals(mScreenAutoBrightness,
+ newScreenAutoBrightness)) {
if (mLoggingEnabled) {
- Slog.d(TAG, "updateAutoBrightness: " +
- "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
- "newScreenAutoBrightness=" + newScreenAutoBrightness);
+ Slog.d(TAG, "updateAutoBrightness: "
+ + "mScreenAutoBrightness=" + mScreenAutoBrightness + ", "
+ + "newScreenAutoBrightness=" + newScreenAutoBrightness);
}
-
mScreenAutoBrightness = newScreenAutoBrightness;
mScreenBrighteningThreshold = clampScreenBrightness(
mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
@@ -770,19 +762,12 @@
}
}
- // Clamps values with float range [1.0-255.0]
- // TODO(brightnessfloat): convert everything that uses this to float system
+ // Clamps values with float range [0.0-1.0]
private float clampScreenBrightness(float value) {
return MathUtils.constrain(value,
mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
}
- // Clamps values with float range [0.0-1.0]
- private float clampScreenBrightnessFloat(float value) {
- return MathUtils.constrain(value,
- mScreenBrightnessRangeMinimumFloat, mScreenBrightnessRangeMaximumFloat);
- }
-
private void prepareBrightnessAdjustmentSample() {
if (!mBrightnessAdjustmentSamplePending) {
mBrightnessAdjustmentSamplePending = true;
@@ -806,12 +791,13 @@
private void collectBrightnessAdjustmentSample() {
if (mBrightnessAdjustmentSamplePending) {
mBrightnessAdjustmentSamplePending = false;
- if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
+ if (mAmbientLuxValid && (mScreenAutoBrightness >= PowerManager.BRIGHTNESS_MIN
+ || mScreenAutoBrightness == PowerManager.BRIGHTNESS_OFF_FLOAT)) {
if (mLoggingEnabled) {
- Slog.d(TAG, "Auto-brightness adjustment changed by user: " +
- "lux=" + mAmbientLux + ", " +
- "brightness=" + mScreenAutoBrightness + ", " +
- "ring=" + mAmbientLightRingBuffer);
+ Slog.d(TAG, "Auto-brightness adjustment changed by user: "
+ + "lux=" + mAmbientLux + ", "
+ + "brightness=" + mScreenAutoBrightness + ", "
+ + "ring=" + mAmbientLightRingBuffer);
}
EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b4f7cdb..02d499f 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.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 @@
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 @@
}
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 @@
}
@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/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 48e30bf..bafeb77 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -948,8 +948,7 @@
if (Float.isNaN(brightnessState)) {
float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
if (autoBrightnessEnabled) {
- brightnessState = BrightnessSynchronizer.brightnessIntToFloat(
- mContext, mAutomaticBrightnessController.getAutomaticScreenBrightness());
+ brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness();
newAutoBrightnessAdjustment =
mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
}
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index f0a505d..2b56569 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -64,8 +64,8 @@
* Return the brightening hysteresis threshold for the given value level.
*/
public float getBrighteningThreshold(float value) {
- float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
- float brightThreshold = value * (1.0f + brightConstant);
+ final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+ final float brightThreshold = value * (1.0f + brightConstant);
if (DEBUG) {
Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+ brightThreshold + ", value=" + value);
@@ -77,8 +77,8 @@
* Return the darkening hysteresis threshold for the given value level.
*/
public float getDarkeningThreshold(float value) {
- float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
- float darkThreshold = value * (1.0f - darkConstant);
+ final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+ final float darkThreshold = value * (1.0f - darkConstant);
if (DEBUG) {
Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+ darkThreshold + ", value=" + value);
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
index 1cf27ff..cc7915c 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.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 static final int NETWORK_COUNTRY_CHANGED = 2;
+ private static final int SUBSCRIPTION_CHANGED = 3;
+ private static final int UPDATE_AIRPLANE_MODE_STATUS = 4;
+
+ // 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 final Context mContext;
- private final ArrayList<Integer> mEmergencyCallMccNumbers;
-
- private final Object mLock = new Object();
-
- private TelephonyManager mTelephonyManager;
+ // 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();
- }
- }
-
- @Override
- public void onCellLocationChanged(CellLocation location) {
- if (!isEmergencyAffordanceNeeded()) {
- requestCellScan();
- }
- }
- };
- private BroadcastReceiver mAirplaneModeReceiver = 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();
- }
- }
- };
- private boolean mSimNeedsEmergencyAffordance;
- private boolean mNetworkNeedsEmergencyAffordance;
+ private boolean mAnySimNeedsEmergencyAffordance;
+ private boolean mAnyNetworkNeedsEmergencyAffordance;
+ private boolean mEmergencyAffordanceNeeded;
+ private boolean mAirplaneModeEnabled;
private boolean mVoiceCapable;
- private void requestCellScan() {
- mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
- }
+ private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ 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 SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
= new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -116,207 +106,200 @@
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()) {
- return;
- }
+ if (DBG) Slog.d(TAG, "handleInitializeState");
+ handleUpdateAirplaneModeStatus();
+ handleUpdateSimSubscriptionInfo();
+ updateNetworkCountry();
updateEmergencyAffordanceNeeded();
}
- private boolean handleUpdateSimSubscriptionInfo() {
- boolean neededBefore = simNeededAffordanceBefore();
- boolean neededNow = neededBefore;
+ private void handleThirdPartyBootPhase() {
+ if (DBG) Slog.d(TAG, "handleThirdPartyBootPhase");
+ mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mVoiceCapable = mTelephonyManager.isVoiceCapable();
+ if (!mVoiceCapable) {
+ updateEmergencyAffordanceNeeded();
+ return;
+ }
+
+ 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 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) {
+ private void handleNetworkCountryChanged(String countryIso, int slotId) {
+ if (DBG) {
+ Slog.d(TAG, "handleNetworkCountryChanged: countryIso=" + countryIso
+ + ", slotId=" + slotId);
+ }
+
+ if (TextUtils.isEmpty(countryIso) && mAirplaneModeEnabled) {
+ Slog.w(TAG, "Ignore empty countryIso report when APM is on.");
+ return;
+ }
+
+ updateNetworkCountry();
+
+ updateEmergencyAffordanceNeeded();
+ }
+
+ 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;
+ }
+ }
+
+ mAnyNetworkNeedsEmergencyAffordance = needsAffordance;
+
+ updateEmergencyAffordanceNeeded();
+ }
+
+ private boolean isoRequiresEmergencyAffordance(String iso) {
+ return mEmergencyCallCountryIsos.contains(iso);
+ }
+
+ 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(),
- EMERGENCY_SIM_INSERTED_SETTING,
- simNeedsEmergencyAffordance ? 1 : 0);
- }
- if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
- mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
- updateEmergencyAffordanceNeeded();
+ Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+ mEmergencyAffordanceNeeded ? 1 : 0);
}
}
- private boolean simNeededAffordanceBefore() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- EMERGENCY_SIM_INSERTED_SETTING, 0) != 0;
+ 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 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();
+ private final class BinderService extends Binder {
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
}
- 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;
- }
- }
- if (stopScanningAfterScan) {
- stopScanning();
- } else {
- onCellScanFinishedUnsuccessful();
- }
- setNetworkNeedsEmergencyAffordance(false);
- return false;
- }
- private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
- synchronized (mLock) {
- mNetworkNeedsEmergencyAffordance = needsAffordance;
- updateEmergencyAffordanceNeeded();
+ dumpInternal(new IndentingPrintWriter(pw, " "));
}
}
-
- private void onCellScanFinishedUnsuccessful() {
- synchronized (mLock) {
- mScansCompleted++;
- if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
- stopScanning();
- }
- }
- }
-
- private boolean mccRequiresEmergencyAffordance(int mcc) {
- return mEmergencyCallMccNumbers.contains(mcc);
- }
}
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 28f8380..3cf22c8 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -215,7 +215,6 @@
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
.setDescription(mContext.getResources().getText(
R.string.bluetooth_a2dp_audio_route_name).toString())
- //TODO: Set type correctly (BLUETOOTH_A2DP or HEARING_AID)
.setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.build();
@@ -236,6 +235,8 @@
// Update volume when the connection state is changed.
MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
.setConnectionState(state);
+ builder.setType(btRoute.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)
+ ? MediaRoute2Info.TYPE_HEARING_AID : MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0d89997..d8bf9ed 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 @@
}
}
+ 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 @@
}
}
-
public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
MediaRoute2Info route) {
Objects.requireNonNull(router, "router must not be null");
@@ -634,12 +651,30 @@
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 @@
}
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 @@
this, provider, uniqueRequestId, sessionInfo));
}
-
@Override
public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
@@ -1267,8 +1302,26 @@
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 @@
}
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 d6bf9fb..bf2cc5e 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 @@
// 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/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 1345e37..41d7fff 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -19,6 +19,11 @@
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -194,19 +199,27 @@
private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
int name = R.string.default_audio_route_name;
+ int type = TYPE_BUILTIN_SPEAKER;
if (newRoutes != null) {
mCurAudioRoutesInfo.mainType = newRoutes.mainType;
- if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
- || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+ type = TYPE_WIRED_HEADPHONES;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ type = TYPE_WIRED_HEADSET;
name = com.android.internal.R.string.default_audio_route_name_headphones;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ type = TYPE_DOCK;
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+ type = TYPE_HDMI;
name = com.android.internal.R.string.default_audio_route_name_hdmi;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+ type = TYPE_USB_DEVICE;
name = com.android.internal.R.string.default_audio_route_name_usb;
}
}
+
mDeviceRoute = new MediaRoute2Info.Builder(
DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
.setVolumeHandling(mAudioManager.isVolumeFixed()
@@ -214,8 +227,7 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- //TODO: Guess the exact type using AudioDevice
- .setType(TYPE_BUILTIN_SPEAKER)
+ .setType(type)
.addFeature(FEATURE_LIVE_AUDIO)
.addFeature(FEATURE_LIVE_VIDEO)
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d432fc8..3e6d6f5 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 @@
}
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 @@
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 1d48438..96da649 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_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 @@
/**
* 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 7c47cf0..d36d038 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 @@
}
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 9f543b4..7adafe3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -351,6 +351,7 @@
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
@@ -24232,6 +24233,25 @@
}
@Override
+ public void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages) {
+ if (mCacheDir == null) {
+ return;
+ }
+
+ final PackageCacher cacher = new PackageCacher(mCacheDir);
+ synchronized (mLock) {
+ for (int i = 0, size = apexPackages.size(); i < size; i++) {
+ final List<String> apkNames =
+ mApexManager.getApksInApex(apexPackages.get(i).packageName);
+ for (int j = 0, apksInApex = apkNames.size(); j < apksInApex; j++) {
+ final AndroidPackage pkg = getPackage(apkNames.get(j));
+ cacher.cleanCachedResult(new File(pkg.getCodePath()));
+ }
+ }
+ }
+ }
+
+ @Override
public String getSetupWizardPackageName() {
return mSetupWizardPackage;
}
@@ -24701,6 +24721,14 @@
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 44a61d8..ddeab29 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 @@
}
}
+ 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/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index d1d9877..1c1e64d 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -1226,8 +1226,9 @@
// APEX checks. For single-package sessions, check if they contain an APEX. For
// multi-package sessions, find all the child sessions that contain an APEX.
if (hasApex) {
+ final List<PackageInfo> apexPackages;
try {
- final List<PackageInfo> apexPackages = submitSessionToApexService(session);
+ apexPackages = submitSessionToApexService(session);
for (int i = 0, size = apexPackages.size(); i < size; i++) {
validateApexSignature(apexPackages.get(i));
}
@@ -1235,6 +1236,10 @@
session.setStagedSessionFailed(e.error, e.getMessage());
return;
}
+
+ final PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ packageManagerInternal.pruneCachedApksInApex(apexPackages);
}
notifyPreRebootVerification_Apex_Complete(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fc70af4..4561d2e 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.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 @@
/**
* 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 @@
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 @@
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 @@
}
}
+ 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 @@
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 85c2306..cd1087f 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.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 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 @@
if (!isLogMode(mode) && !isEnforceMode(mode)) {
return;
}
- Slog.v(TAG, "Checking that all system packages are whitelisted.");
- final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
- PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-
- // Check whether all whitelisted packages are indeed on the system.
- for (String pkgName : allWhitelistedPackages) {
- AndroidPackage pkg = pmInt.getPackage(pkgName);
- if (pkg == null) {
- Slog.w(TAG, pkgName + " is whitelisted but not present.");
- } else if (!pkg.isSystem()) {
- Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
- }
- }
-
- // Check whether all system packages are indeed whitelisted.
- if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+ 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;
}
- final boolean doWtf = 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 (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();
+ 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) {
+ final AndroidPackage pkg = pmInt.getPackage(pkgName);
+ if (pkg == null) {
+ warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName)));
+ } else if (!pkg.isSystem()) {
+ warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName)));
+ }
+ }
+
+ // Check whether all system packages are indeed whitelisted.
+ 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()) 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 @@
}
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 @@
}
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/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index e5e1b0b..99c6dd1 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.content.pm.PackageParserCacheHelper;
+import android.os.FileUtils;
import android.os.Parcel;
import android.system.ErrnoException;
import android.system.Os;
@@ -197,4 +198,18 @@
Slog.w(TAG, "Error saving package cache.", e);
}
}
+
+ /**
+ * Delete the cache files for the given {@code packageFile}.
+ */
+ public void cleanCachedResult(@NonNull File packageFile) {
+ final String packageName = packageFile.getName();
+ final File[] files = FileUtils.listFilesOrEmpty(mCacheDir,
+ (dir, name) -> name.startsWith(packageName));
+ for (File file : files) {
+ if (!file.delete()) {
+ Slog.e(TAG, "Unable to clean cache file: " + file);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4624e9e..2f84a99 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_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 @@
mScreenshotChordVolumeDownKeyConsumed = true;
cancelPendingPowerKeyAction();
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
+ mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
@@ -1411,14 +1414,19 @@
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 @@
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 @@
} 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 1ab6ade..b69c450 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 @@
}
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 801d75b..f6d46e2 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.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 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 @@
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 @@
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ mApexManager = ApexManager.getInstance();
}
@Override
@@ -302,6 +308,18 @@
* 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 0000000..54ad1d2
--- /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 4cdc172..2b0fe8a 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 @@
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 @@
* also lose their resources.
*/
private int mGroupId = INVALID_GROUP_ID;
-
/**
* Optional nice value for TRM to reduce client’s priority.
*/
@@ -73,6 +73,11 @@
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 @@
}
/**
+ * 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 7231813..2f70840 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 @@
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 @@
@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 @@
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 @@
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");
}
-
- return true;
+ synchronized (mLock) {
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request cas from unregistered client:"
+ + request.getClientId());
+ }
+ return requestCasSessionInternal(request, casSessionHandle);
+ }
}
@Override
@@ -248,11 +261,11 @@
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 @@
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 @@
}
@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 @@
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 @@
}
@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 @@
} 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 @@
}
@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 @@
} 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 @@
}
@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 @@
}
@VisibleForTesting
- void releaseLnbInternal(LnbResource lnb) {
+ protected void releaseLnbInternal(LnbResource lnb) {
if (DEBUG) {
Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")");
}
@@ -604,7 +709,15 @@
}
@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 @@
}
@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 @@
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 @@
private void removeFrontendResource(int removingId) {
FrontendResource fe = getFrontendResource(removingId);
+ if (fe == null) {
+ return;
+ }
if (fe.isInUse()) {
releaseFrontendInternal(fe);
}
@@ -811,6 +942,9 @@
private void removeLnbResource(int removingId) {
LnbResource lnb = getLnbResource(removingId);
+ if (lnb == null) {
+ return;
+ }
if (lnb.isInUse()) {
releaseLnbInternal(lnb);
}
@@ -819,6 +953,39 @@
@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 @@
}
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 @@
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/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3f9f95c..9d63029 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -66,6 +66,7 @@
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -341,6 +342,16 @@
windowState.getTransformationMatrix(sTempFloats, outMatrix);
}
+ void dump(PrintWriter pw, String prefix) {
+ for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
+ final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
+ if (displayMagnifier != null) {
+ displayMagnifier.dump(pw, prefix
+ + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
+ }
+ }
+ }
+
/**
* This class encapsulates the functionality related to display magnification.
*/
@@ -551,6 +562,10 @@
mMagnifedViewport.drawWindowIfNeededLocked(t);
}
+ void dump(PrintWriter pw, String prefix) {
+ mMagnifedViewport.dump(pw, prefix);
+ }
+
private final class MagnifiedViewport {
private final SparseArray<WindowState> mTempWindowStates =
@@ -820,6 +835,10 @@
}, false /* traverseTopToBottom */ );
}
+ void dump(PrintWriter pw, String prefix) {
+ mWindow.dump(pw, prefix);
+ }
+
private final class ViewportWindow {
private static final String SURFACE_TITLE = "Magnification Overlay";
@@ -985,6 +1004,14 @@
mSurface.release();
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix
+ + " mBounds= " + mBounds
+ + " mDirtyRect= " + mDirtyRect
+ + " mWidth= " + mSurfaceControl.getWidth()
+ + " mHeight= " + mSurfaceControl.getHeight());
+ }
+
private final class AnimationController extends Handler {
private static final String PROPERTY_NAME_ALPHA = "alpha";
@@ -1245,6 +1272,16 @@
}
}
+ for (int i = dc.mShellRoots.size() - 1; i >= 0; --i) {
+ final WindowInfo info = dc.mShellRoots.valueAt(i).getWindowInfo();
+ if (info == null) {
+ continue;
+ }
+ info.layer = addedWindows.size();
+ windows.add(info);
+ addedWindows.add(info.token);
+ }
+
// Remove child/parent references to windows that were not added.
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e76eda0..729f549 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3137,7 +3137,6 @@
commitVisibility(false /* visible */, true /* performLayout */);
getDisplayContent().mOpeningApps.remove(this);
- getDisplayContent().mChangingContainers.remove(this);
getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
mWmService.mTaskSnapshotController.onAppRemoved(this);
mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
@@ -4712,7 +4711,7 @@
*/
private boolean shouldBeResumed(ActivityRecord activeActivity) {
return shouldMakeActive(activeActivity) && isFocusable()
- && getRootTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
+ && getTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
&& canResumeByCompat();
}
@@ -6130,19 +6129,8 @@
@Override
void cancelAnimation() {
- cancelAnimationOnly();
- clearThumbnail();
- mSurfaceFreezer.unfreeze(getPendingTransaction());
- }
-
- /**
- * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
- * or interim leashes.
- * <p>
- * Used when canceling in preparation for starting a new animation.
- */
- void cancelAnimationOnly() {
super.cancelAnimation();
+ clearThumbnail();
}
@VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 0f57496..ff43e77 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -18,11 +18,8 @@
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -142,13 +139,11 @@
import android.os.Trace;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.SurfaceControl;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -157,7 +152,6 @@
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -276,11 +270,6 @@
Rect mPreAnimationBounds = new Rect();
- /**
- * For {@link #prepareSurfaces}.
- */
- private final Point mLastSurfaceSize = new Point();
-
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -606,10 +595,6 @@
super.onConfigurationChanged(newParentConfig);
- // Only need to update surface size here since the super method will handle updating
- // surface position.
- updateSurfaceSize(getPendingTransaction());
-
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea == null) {
return;
@@ -3262,61 +3247,14 @@
scheduleAnimation();
}
- /**
- * Calculate an amount by which to expand the stack bounds in each direction.
- * Used to make room for shadows in the pinned windowing mode.
- */
- int getStackOutset() {
- // If we are drawing shadows on the task then don't outset the stack.
- if (mWmService.mRenderShadowsInCompositor) {
- return 0;
- }
- DisplayContent displayContent = getDisplayContent();
- if (inPinnedWindowingMode() && displayContent != null) {
- final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
-
- // We multiply by two to match the client logic for converting view elevation
- // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
- return (int) Math.ceil(
- mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
- * 2);
- }
- return 0;
- }
-
@Override
void getRelativePosition(Point outPos) {
super.getRelativePosition(outPos);
- final int outset = getStackOutset();
+ final int outset = getTaskOutset();
outPos.x -= outset;
outPos.y -= outset;
}
- private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
-
- final Rect stackBounds = getBounds();
- int width = stackBounds.width();
- int height = stackBounds.height();
-
- final int outset = getStackOutset();
- width += 2 * outset;
- height += 2 * outset;
-
- if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
- return;
- }
- transaction.setWindowCrop(mSurfaceControl, width, height);
- mLastSurfaceSize.set(width, height);
- }
-
- @VisibleForTesting
- Point getLastSurfaceSize() {
- return mLastSurfaceSize;
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
super.onDisplayChanged(dc);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0754a34..8edc84f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -168,7 +168,6 @@
// The task display area to launch the activity onto, barring any strong reason to do otherwise.
private TaskDisplayArea mPreferredTaskDisplayArea;
- // The windowing mode to apply to the root task, if possible
private int mPreferredWindowingMode;
private Task mInTask;
@@ -1560,9 +1559,6 @@
if (!mAvoidMoveToFront && mDoResume) {
mTargetStack.getStack().moveToFront("reuseOrNewTask", targetTask);
if (mOptions != null) {
- if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED) {
- mTargetStack.setWindowingMode(mPreferredWindowingMode);
- }
if (mOptions.getTaskAlwaysOnTop()) {
mTargetStack.setAlwaysOnTop(true);
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 736bc9f..93a7574 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 @@
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index b01acca..e261632 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -574,7 +573,7 @@
/** Corner radius that windows should have in order to match the display. */
private final float mWindowCornerRadius;
- private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+ final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
private final IBinder.DeathRecipient mRemoteInsetsDeath =
() -> {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e9d3d56..8aace21 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 @@
@Px
private int mRightGestureInset;
+ private boolean mNavButtonForcedVisible;
+
StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
if (mStatusBarManagerInternal == null) {
@@ -1046,12 +1048,14 @@
// 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 @@
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 @@
* @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/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 20738ed..803bec8 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -45,6 +45,11 @@
void setTouchRegion(Rect touchRegion) {
mTouchRegion.set(touchRegion);
+ // We need to report touchable region changes to accessibility.
+ if (mDisplayContent.mWmService.mAccessibilityController != null) {
+ mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(
+ mDisplayContent.getDisplayId());
+ }
}
void getTouchRegion(Rect outRegion) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8b34b9b..656dca5 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -487,7 +487,8 @@
|| w.cantReceiveTouchInput()) {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ mInvalidInputWindow);
}
// Skip this window because it cannot possibly receive input.
return;
@@ -560,7 +561,8 @@
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 5f33ea1..86e0818 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 @@
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");
@@ -597,8 +597,8 @@
return startAnimation(initializeBuilder()
.setSurfaceControl(mScreenshotLayer)
.setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .setWidth(mWidth)
- .setHeight(mHeight)
+ .setWidth(mDisplayContent.getSurfaceWidth())
+ .setHeight(mDisplayContent.getSurfaceHeight())
.build(),
createWindowAnimationSpec(mRotateAlphaAnimation),
this::onAnimationEnd);
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 701feff..0b1760d 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -23,12 +23,14 @@
import android.annotation.NonNull;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.SurfaceControl;
+import android.view.WindowInfo;
import android.view.animation.Animation;
/**
@@ -102,5 +104,27 @@
mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */,
ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */);
}
+
+ WindowInfo getWindowInfo() {
+ if (mToken.windowType != TYPE_DOCK_DIVIDER) {
+ return null;
+ }
+ if (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+ return null;
+ }
+ WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
+ windowInfo.type = mToken.windowType;
+ windowInfo.layer = mToken.getWindowLayerFromType();
+ windowInfo.token = mClient.asBinder();
+ windowInfo.title = "Splitscreen Divider";
+ windowInfo.focused = false;
+ windowInfo.inPictureInPicture = false;
+ windowInfo.hasFlagWatchOutsideTouch = false;
+ final Rect regionRect = new Rect();
+ mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
+ windowInfo.regionInScreen.set(regionRect);
+ return windowInfo;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 66ca0ac..b12eb70 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -120,6 +120,7 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -213,7 +214,6 @@
static final int INVALID_MIN_SIZE = -1;
private float mShadowRadius = 0;
- private final Rect mLastSurfaceCrop = new Rect();
/**
* The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -397,6 +397,7 @@
private Dimmer mDimmer = new Dimmer(this);
private final Rect mTmpDimBoundsRect = new Rect();
+ private final Point mLastSurfaceSize = new Point();
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -1943,6 +1944,10 @@
mTmpPrevBounds.set(getBounds());
final boolean wasInMultiWindowMode = inMultiWindowMode();
super.onConfigurationChanged(newParentConfig);
+ // Only need to update surface size here since the super method will handle updating
+ // surface position.
+ updateSurfaceSize(getPendingTransaction());
+
if (wasInMultiWindowMode != inMultiWindowMode()) {
mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
@@ -1987,7 +1992,8 @@
if (mWmService.mDisableTransitionAnimation
|| !isVisible()
|| getDisplayContent().mAppTransition.isTransitionSet()
- || getSurfaceControl() == null) {
+ || getSurfaceControl() == null
+ || !isLeafTask()) {
return false;
}
// Only do an animation into and out-of freeform mode for now. Other mode
@@ -1995,6 +2001,57 @@
return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
}
+ void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+ if (mSurfaceControl == null || mCreatedByOrganizer) {
+ return;
+ }
+
+ // Apply crop to root tasks only and clear the crops of the descendant tasks.
+ int width = 0;
+ int height = 0;
+ if (isRootTask()) {
+ final Rect taskBounds = getBounds();
+ width = taskBounds.width();
+ height = taskBounds.height();
+
+ final int outset = getTaskOutset();
+ width += 2 * outset;
+ height += 2 * outset;
+ }
+ if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ transaction.setWindowCrop(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
+ }
+
+ /**
+ * Calculate an amount by which to expand the task bounds in each direction.
+ * Used to make room for shadows in the pinned windowing mode.
+ */
+ int getTaskOutset() {
+ // If we are drawing shadows on the task then don't outset the stack.
+ if (mWmService.mRenderShadowsInCompositor) {
+ return 0;
+ }
+ DisplayContent displayContent = getDisplayContent();
+ if (inPinnedWindowingMode() && displayContent != null) {
+ final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
+
+ // We multiply by two to match the client logic for converting view elevation
+ // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
+ return (int) Math.ceil(
+ mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
+ * 2);
+ }
+ return 0;
+ }
+
+ @VisibleForTesting
+ Point getLastSurfaceSize() {
+ return mLastSurfaceSize;
+ }
+
@VisibleForTesting
boolean isInChangeTransition() {
return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransit(mTransit);
@@ -2225,14 +2282,16 @@
}
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ // If bounds have been overridden at this level, restrict config resources to these bounds
+ // rather than the parent because the overridden bounds can be larger than the parent.
+ boolean hasOverrideBounds = false;
+
final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
- if (resolvedBounds == null) {
- mTmpFullBounds.setEmpty();
+ if (resolvedBounds == null || resolvedBounds.isEmpty()) {
+ mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
} else {
mTmpFullBounds.set(resolvedBounds);
- }
- if (mTmpFullBounds.isEmpty()) {
- mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
+ hasOverrideBounds = true;
}
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2244,7 +2303,16 @@
// the out bounds doesn't need to be restricted by the parent.
final boolean insideParentBounds = compatInsets == null;
if (insideParentBounds && windowingMode != WINDOWING_MODE_FREEFORM) {
- final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ Rect parentAppBounds;
+ if (hasOverrideBounds) {
+ // Since we overrode the bounds, restrict appBounds to display non-decor rather
+ // than parent. Otherwise, it won't match the overridden bounds.
+ final TaskDisplayArea displayArea = getDisplayArea();
+ parentAppBounds = displayArea != null
+ ? displayArea.getConfiguration().windowConfiguration.getAppBounds() : null;
+ } else {
+ parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ }
if (parentAppBounds != null && !parentAppBounds.isEmpty()) {
outAppBounds.intersect(parentAppBounds);
}
@@ -2291,13 +2359,13 @@
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
- inOutConfig.screenWidthDp = insideParentBounds
+ inOutConfig.screenWidthDp = (insideParentBounds && !hasOverrideBounds)
? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
: overrideScreenWidthDp;
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
- inOutConfig.screenHeightDp = insideParentBounds
+ inOutConfig.screenHeightDp = (insideParentBounds && !hasOverrideBounds)
? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
: overrideScreenHeightDp;
}
@@ -2344,27 +2412,27 @@
mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
super.resolveOverrideConfiguration(newParentConfig);
- // Resolve override windowing mode to fullscreen for home task (even on freeform
- // display), or split-screen-secondary if in split-screen mode.
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+
+ // Resolve override windowing mode to fullscreen for home task (even on freeform
+ // display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
- windowingMode = parentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_FULLSCREEN;
+ windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
+ ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
- if (!isLeafTask()) {
- // Compute configuration overrides for tasks that created by organizer, so that
- // organizer can get the correct configuration from those tasks.
- if (mCreatedByOrganizer) {
- computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
- }
- return;
+ if (isLeafTask()) {
+ resolveLeafOnlyOverrideConfigs(newParentConfig);
}
+ computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+ }
+ void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig) {
+ int windowingMode =
+ getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
}
@@ -2404,7 +2472,6 @@
outOverrideBounds.offset(0, offsetTop);
}
}
- computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
/**
@@ -2803,28 +2870,6 @@
return boundsChange;
}
- private void updateSurfaceCrop() {
- // Only update the crop if we are drawing shadows on the task.
- if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor || !isRootTask()) {
- return;
- }
-
- if (inSplitScreenWindowingMode()) {
- // inherit crop from parent
- mTmpRect.setEmpty();
- } else {
- getBounds(mTmpRect);
- }
-
- mTmpRect.offsetTo(0, 0);
- if (mLastSurfaceCrop.equals(mTmpRect)) {
- return;
- }
-
- getPendingTransaction().setWindowCrop(mSurfaceControl, mTmpRect);
- mLastSurfaceCrop.set(mTmpRect);
- }
-
@Override
public boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
ConfigurationContainer requestingContainer) {
@@ -2853,22 +2898,12 @@
if (!isRootTask) {
adjustBoundsForDisplayChangeIfNeeded(dc);
}
- final DisplayContent prevDc = mDisplayContent;
super.onDisplayChanged(dc);
if (!isRootTask) {
final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
mTaskId, displayId);
}
- if (prevDc != null && prevDc.mChangingContainers.remove(this)) {
- // This gets called *after* this has been reparented to the new display.
- // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN),
- // so this token is now "frozen" while waiting for the animation to start on prevDc
- // (which will be cancelled since the window is no-longer a child). However, since this
- // is no longer a child of prevDc, this won't be notified of the cancelled animation,
- // so we need to cancel the change transition here.
- mSurfaceFreezer.unfreeze(getPendingTransaction());
- }
}
boolean isResizeable(boolean checkSupportsPip) {
@@ -3453,7 +3488,6 @@
mTmpDimBoundsRect.offsetTo(0, 0);
}
- updateSurfaceCrop();
updateShadowsRadius(isFocused(), getPendingTransaction());
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
@@ -4392,19 +4426,20 @@
// 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;
}
/**
@@ -4441,14 +4476,6 @@
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 adc50bf..306c100 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 @@
}
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 @@
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 @@
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 @@
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/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 203ca25..5f3c633 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -45,6 +46,7 @@
DisplayContent dc, boolean ownerCanManageAppTokens) {
super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens);
dc.mWallpaperController.addWallpaperToken(this);
+ setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f3e2992..591bc54 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -526,6 +526,11 @@
*/
@CallSuper
void removeImmediately() {
+ final DisplayContent dc = getDisplayContent();
+ if (dc != null) {
+ mSurfaceFreezer.unfreeze(getPendingTransaction());
+ dc.mChangingContainers.remove(this);
+ }
while (!mChildren.isEmpty()) {
final E child = mChildren.peekLast();
child.removeImmediately();
@@ -718,6 +723,10 @@
* @param dc The display this container is on after changes.
*/
void onDisplayChanged(DisplayContent dc) {
+ if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
+ // Cancel any change transition queued-up for this container on the old display.
+ mSurfaceFreezer.unfreeze(getPendingTransaction());
+ }
mDisplayContent = dc;
if (dc != null && dc != this) {
dc.getPendingTransaction().merge(mPendingTransaction);
@@ -2033,6 +2042,7 @@
void cancelAnimation() {
mSurfaceAnimator.cancelAnimation();
+ mSurfaceFreezer.unfreeze(getPendingTransaction());
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a501414..fbdea01 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 @@
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;
}
}
}
@@ -6113,15 +6111,6 @@
pw.print(" mInputMethodInputTarget in display# "); pw.print(displayId);
pw.print(' '); pw.println(inputMethodInputTarget);
}
- if (mAccessibilityController != null) {
- final Region magnificationRegion = new Region();
- mAccessibilityController.getMagnificationRegionLocked(displayId,
- magnificationRegion);
- pw.print(" mMagnificationRegion in display# ");
- pw.print(displayId);
- pw.print(' ');
- pw.println(magnificationRegion);
- }
});
pw.print(" mInTouchMode="); pw.println(mInTouchMode);
pw.print(" mLastDisplayFreezeDuration=");
@@ -6137,6 +6126,9 @@
mInputManagerCallback.dump(pw, " ");
mTaskSnapshotController.dump(pw, " ");
+ if (mAccessibilityController != null) {
+ mAccessibilityController.dump(pw, " ");
+ }
if (dumpAll) {
final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
@@ -7658,15 +7650,8 @@
Slog.w(TAG, "Cannot find window which accessibility connection is added to");
return;
}
- try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.setMetadata(
- state.mSurfaceControl,
- SurfaceControl.METADATA_ACCESSIBILITY_ID,
- accessibilityWindowId);
- t.apply();
- } finally {
- SurfaceControl.closeTransaction();
- }
+ mTransaction.setMetadata(state.mSurfaceControl,
+ SurfaceControl.METADATA_ACCESSIBILITY_ID, accessibilityWindowId).apply();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 83e7ad5..ef690e1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5289,7 +5289,7 @@
// to account for it. If we actually have shadows we will
// then un-inset ourselves by the surfaceInsets.
if (stack != null) {
- final int outset = stack.getStackOutset();
+ final int outset = stack.getTaskOutset();
outPoint.offset(outset, outset);
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9957707..a2930ab 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1476,6 +1476,7 @@
if (dumpAll) {
pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden);
+ pw.print(prefix); pw.print("mEnterAnimationPending=" + mEnterAnimationPending);
pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw);
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index 6e2e2c5..99deab4 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -106,7 +106,7 @@
static const GamepadAxis* getGamepadAxis(int32_t androidAxisCode) {
std::unordered_map<int32_t, int>::iterator it =
gamepadAndroidAxisToIndexMap.find(androidAxisCode);
- if (it == gamepadAndroidToLinuxKeyMap.end()) {
+ if (it == gamepadAndroidAxisToIndexMap.end()) {
return nullptr;
}
return &GAMEPAD_AXES[it->second];
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2c0d4c0..5d5e424 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.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.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;
@@ -399,11 +403,14 @@
private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
private static final long MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD = 3 * MS_PER_DAY;
+ /** When to warn the user about the approaching work profile off deadline: 1 day before */
+ private static final long MANAGED_PROFILE_OFF_WARNING_PERIOD = 1 * MS_PER_DAY;
private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION =
"com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
- private static final String ACTION_PROFILE_OFF_DEADLINE =
+ @VisibleForTesting
+ static final String ACTION_PROFILE_OFF_DEADLINE =
"com.android.server.ACTION_PROFILE_OFF_DEADLINE";
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
@@ -645,6 +652,13 @@
private static final boolean ENABLE_LOCK_GUARD = true;
+ /** Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away. */
+ private static final int PROFILE_OFF_DEADLINE_DEFAULT = 0;
+ /** Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. */
+ private static final int PROFILE_OFF_DEADLINE_WARNING = 1;
+ /** Profile off deadline reached, notify the user that personal apps blocked. */
+ private static final int PROFILE_OFF_DEADLINE_REACHED = 2;
+
interface Stats {
int LOCK_GUARD_GUARD = 0;
@@ -922,11 +936,12 @@
mUserData.remove(userHandle);
}
handlePackagesChanged(null /* check all admins */, userHandle);
+ updatePersonalAppsSuspensionOnUserStart(userHandle);
} else if (Intent.ACTION_USER_STOPPED.equals(action)) {
sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STOPPED, userHandle);
if (isManagedProfile(userHandle)) {
Slog.d(LOG_TAG, "Managed profile was stopped");
- updatePersonalAppSuspension(userHandle, false /* profileIsOn */);
+ updatePersonalAppsSuspension(userHandle, false /* unlocked */);
}
} else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle);
@@ -936,7 +951,7 @@
}
if (isManagedProfile(userHandle)) {
Slog.d(LOG_TAG, "Managed profile became unlocked");
- updatePersonalAppSuspension(userHandle, true /* profileIsOn */);
+ updatePersonalAppsSuspension(userHandle, true /* unlocked */);
}
} else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
handlePackagesChanged(null /* check all admins */, userHandle);
@@ -963,7 +978,7 @@
Slog.i(LOG_TAG, "Profile off deadline alarm was triggered");
final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
if (userId >= 0) {
- updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked(userId));
+ updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
} else {
Slog.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile");
}
@@ -2482,6 +2497,16 @@
public void runCryptoSelfTest() {
CryptoTestHelper.runAndLogSelfTest();
}
+
+ public String[] getPersonalAppsForSuspension(int userId) {
+ return new PersonalAppsSuspensionHelper(
+ mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
+ .getPersonalAppsForSuspension();
+ }
+
+ public long systemCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
}
/**
@@ -2703,8 +2728,9 @@
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 +2742,47 @@
.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) {
@@ -3999,10 +4066,6 @@
applyManagedProfileRestrictionIfDeviceOwnerLocked();
}
maybeStartSecurityLogMonitorOnActivityManagerReady();
- final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
- if (userId >= 0) {
- updatePersonalAppSuspension(userId, false /* running */);
- }
break;
case SystemService.PHASE_BOOT_COMPLETED:
ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -4010,6 +4073,16 @@
}
}
+ private void updatePersonalAppsSuspensionOnUserStart(int userHandle) {
+ final int profileUserHandle = getManagedUserId(userHandle);
+ if (profileUserHandle >= 0) {
+ // Given that the parent user has just started, profile should be locked.
+ updatePersonalAppsSuspension(profileUserHandle, false /* unlocked */);
+ } else {
+ suspendPersonalAppsInternal(userHandle, false);
+ }
+ }
+
private void onLockSettingsReady() {
getUserData(UserHandle.USER_SYSTEM);
loadOwners();
@@ -8766,6 +8839,8 @@
saveSettingsLocked(UserHandle.USER_SYSTEM);
clearUserPoliciesLocked(userId);
clearOverrideApnUnchecked();
+ clearApplicationRestrictions(userId);
+ mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -8779,6 +8854,19 @@
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 +8986,7 @@
policyData.mOwnerInstalledCaCerts.clear();
saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
+ clearApplicationRestrictions(userId);
mOwners.removeProfileOwner(userId);
mOwners.writeProfileOwner(userId);
deleteTransferOwnershipBundleLocked(userId);
@@ -15831,11 +15920,8 @@
}
}
- final int suspendedState = suspended
- ? PERSONAL_APPS_SUSPENDED_EXPLICITLY
- : PERSONAL_APPS_NOT_SUSPENDED;
- mInjector.binderWithCleanCallingIdentity(
- () -> applyPersonalAppsSuspension(callingUserId, suspendedState));
+ mInjector.binderWithCleanCallingIdentity(() -> updatePersonalAppsSuspension(
+ callingUserId, mUserManager.isUserUnlocked(callingUserId)));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
@@ -15845,44 +15931,54 @@
}
/**
- * Checks whether there is a policy that requires personal apps to be suspended and if so,
- * applies it.
- * @param running whether the profile is currently considered running.
+ * Checks whether personal apps should be suspended according to the policy and applies the
+ * change if needed.
+ *
+ * @param unlocked whether the profile is currently running unlocked.
*/
- private void updatePersonalAppSuspension(int profileUserId, boolean running) {
- final int suspensionState;
+ private void updatePersonalAppsSuspension(int profileUserId, boolean unlocked) {
+ final boolean suspended;
synchronized (getLockObject()) {
final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
if (profileOwner != null) {
- final boolean deadlineReached =
- updateProfileOffDeadlineLocked(profileUserId, profileOwner, running);
- suspensionState = makeSuspensionReasons(
- profileOwner.mSuspendPersonalApps, deadlineReached);
- Slog.d(LOG_TAG,
- String.format("New personal apps suspension state: %d", suspensionState));
+ final int deadlineState =
+ updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
+ suspended = profileOwner.mSuspendPersonalApps
+ || deadlineState == PROFILE_OFF_DEADLINE_REACHED;
+ Slog.d(LOG_TAG, String.format("Personal apps suspended: %b, deadline state: %d",
+ suspended, deadlineState));
+ updateProfileOffDeadlineNotificationLocked(profileUserId, profileOwner,
+ unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState);
} else {
- suspensionState = PERSONAL_APPS_NOT_SUSPENDED;
+ suspended = false;
}
}
- applyPersonalAppsSuspension(profileUserId, suspensionState);
+ final int parentUserId = getProfileParentId(profileUserId);
+ suspendPersonalAppsInternal(parentUserId, suspended);
}
/**
* Checks work profile time off policy, scheduling personal apps suspension via alarm if
* necessary.
- * @return whether the apps should be suspended based on maximum time off policy.
+ * @return profile deadline state
*/
- private boolean updateProfileOffDeadlineLocked(
+ private int updateProfileOffDeadlineLocked(
int profileUserId, ActiveAdmin profileOwner, boolean unlocked) {
- final long now = System.currentTimeMillis();
+ final long now = mInjector.systemCurrentTimeMillis();
if (profileOwner.mProfileOffDeadline != 0 && now > profileOwner.mProfileOffDeadline) {
// Profile off deadline is already reached.
Slog.i(LOG_TAG, "Profile off deadline has been reached.");
- return true;
+ return PROFILE_OFF_DEADLINE_REACHED;
}
boolean shouldSaveSettings = false;
- if (profileOwner.mProfileOffDeadline != 0
+ if (profileOwner.mSuspendPersonalApps) {
+ // When explicit suspension is active, deadline shouldn't be set.
+ if (profileOwner.mProfileOffDeadline != 0) {
+ profileOwner.mProfileOffDeadline = 0;
+ shouldSaveSettings = true;
+ }
+ } else if (profileOwner.mProfileOffDeadline != 0
&& (profileOwner.mProfileMaximumTimeOffMillis == 0 || unlocked)) {
// There is a deadline but either there is no policy or the profile is unlocked -> clear
// the deadline.
@@ -15898,52 +15994,51 @@
shouldSaveSettings = true;
}
- updateProfileOffAlarm(profileOwner.mProfileOffDeadline);
-
if (shouldSaveSettings) {
saveSettingsLocked(profileUserId);
}
- return false;
- }
- private void updateProfileOffAlarm(long profileOffDeadline) {
+ final long alarmTime;
+ final int deadlineState;
+ if (profileOwner.mProfileOffDeadline == 0) {
+ alarmTime = 0;
+ deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+ } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) {
+ // The deadline is close, upon the alarm personal apps should be suspended.
+ alarmTime = profileOwner.mProfileOffDeadline;
+ deadlineState = PROFILE_OFF_DEADLINE_WARNING;
+ } else {
+ // The deadline is quite far, upon the alarm we should warn the user first, so the
+ // alarm is scheduled earlier than the actual deadline.
+ alarmTime = profileOwner.mProfileOffDeadline - MANAGED_PROFILE_OFF_WARNING_PERIOD;
+ deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+ }
+
final AlarmManager am = mInjector.getAlarmManager();
final PendingIntent pi = mInjector.pendingIntentGetBroadcast(
mContext, REQUEST_PROFILE_OFF_DEADLINE, new Intent(ACTION_PROFILE_OFF_DEADLINE),
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
- am.cancel(pi);
- if (profileOffDeadline != 0) {
- Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
- am.set(AlarmManager.RTC, profileOffDeadline, pi);
- } else {
+
+ if (alarmTime == 0) {
Slog.i(LOG_TAG, "Profile off deadline alarm is removed.");
- }
- }
-
- private void applyPersonalAppsSuspension(
- int profileUserId, @PersonalAppsSuspensionReason int suspensionState) {
- final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mAppsSuspended;
- final boolean shouldSuspend = suspensionState != PERSONAL_APPS_NOT_SUSPENDED;
- if (suspended != shouldSuspend) {
- suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM);
- }
-
- if (suspensionState == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT) {
- sendPersonalAppsSuspendedNotification(profileUserId);
+ am.cancel(pi);
} else {
- clearPersonalAppsSuspendedNotification();
+ Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
+ am.set(AlarmManager.RTC, alarmTime, pi);
}
+
+ return deadlineState;
}
- private void suspendPersonalAppsInternal(boolean suspended, int userId) {
+ private void suspendPersonalAppsInternal(int userId, boolean suspended) {
+ if (getUserData(userId).mAppsSuspended == suspended) {
+ return;
+ }
Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
suspended ? "Suspending" : "Unsuspending", userId));
mInjector.binderWithCleanCallingIdentity(() -> {
try {
- final String[] appsToSuspend =
- new PersonalAppsSuspensionHelper(
- mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
- .getPersonalAppsForSuspension();
+ final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId);
final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
if (!ArrayUtils.isEmpty(failedPackages)) {
@@ -15962,37 +16057,38 @@
}
}
- private void clearPersonalAppsSuspendedNotification() {
- mInjector.binderWithCleanCallingIdentity(() ->
- mInjector.getNotificationManager().cancel(
- SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
- }
+ private void updateProfileOffDeadlineNotificationLocked(int profileUserId,
+ @Nullable ActiveAdmin profileOwner, int notificationState) {
- private void sendPersonalAppsSuspendedNotification(int userId) {
- final String profileOwnerPackageName;
- final long maxTimeOffDays;
- synchronized (getLockObject()) {
- profileOwnerPackageName = mOwners.getProfileOwnerComponent(userId).getPackageName();
- final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
- maxTimeOffDays = TimeUnit.MILLISECONDS.toDays(poAdmin.mProfileMaximumTimeOffMillis);
+ if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
+ mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
+ return;
}
+ final String profileOwnerPackageName = profileOwner.info.getPackageName();
+ final long maxTimeOffDays =
+ TimeUnit.MILLISECONDS.toDays(profileOwner.mProfileMaximumTimeOffMillis);
+
final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
intent.setPackage(profileOwnerPackageName);
final PendingIntent pendingIntent = mInjector.pendingIntentGetActivityAsUser(mContext,
- 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT, null /* options */,
- UserHandle.of(userId));
+ 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT,
+ null /* options */, UserHandle.of(profileUserId));
+
+ // TODO(b/149075510): Only the first of the notifications should be dismissible.
+ final String title = mContext.getString(
+ notificationState == PROFILE_OFF_DEADLINE_WARNING
+ ? R.string.personal_apps_suspended_tomorrow_title
+ : R.string.personal_apps_suspended_title);
final Notification notification =
new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setOngoing(true)
- .setContentTitle(
- mContext.getString(
- R.string.personal_apps_suspended_title))
+ .setContentTitle(title)
.setContentText(mContext.getString(
- R.string.personal_apps_suspended_text, maxTimeOffDays))
+ R.string.personal_apps_suspended_text, maxTimeOffDays))
.setColor(mContext.getColor(R.color.system_notification_accent_color))
.setContentIntent(pendingIntent)
.build();
@@ -16024,7 +16120,7 @@
}
mInjector.binderWithCleanCallingIdentity(
- () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+ () -> updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked()));
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index d9db17e..da716ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -51,6 +51,10 @@
public class PersonalAppsSuspensionHelper {
private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
+ // Flags to get all packages even if the user is still locked.
+ private static final int PACKAGE_QUERY_FLAGS =
+ PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
private final Context mContext;
private final PackageManager mPackageManager;
@@ -67,7 +71,7 @@
*/
String[] getPersonalAppsForSuspension() {
final List<PackageInfo> installedPackageInfos =
- mPackageManager.getInstalledPackages(0 /* flags */);
+ mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS);
final Set<String> result = new ArraySet<>();
for (final PackageInfo packageInfo : installedPackageInfos) {
final ApplicationInfo info = packageInfo.applicationInfo;
@@ -97,7 +101,7 @@
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
final List<ResolveInfo> matchingActivities =
- mPackageManager.queryIntentActivities(intent, 0);
+ mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS);
for (final ResolveInfo resolveInfo : matchingActivities) {
if (resolveInfo.activityInfo == null
|| TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
@@ -107,7 +111,7 @@
final String packageName = resolveInfo.activityInfo.packageName;
try {
final ApplicationInfo applicationInfo =
- mPackageManager.getApplicationInfo(packageName, 0);
+ mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS);
if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
result.add(packageName);
}
@@ -147,7 +151,8 @@
private String getSettingsPackageName() {
final Intent intent = new Intent(Settings.ACTION_SETTINGS);
intent.addCategory(Intent.CATEGORY_DEFAULT);
- final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, /* flags= */ 0);
+ final ResolveInfo resolveInfo =
+ mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS);
if (resolveInfo != null) {
return resolveInfo.activityInfo.packageName;
}
@@ -164,7 +169,7 @@
intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
intentToResolve.setPackage(packageName);
final List<ResolveInfo> resolveInfos =
- mPackageManager.queryIntentActivities(intentToResolve, /* flags= */ 0);
+ mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS);
return resolveInfos != null && !resolveInfos.isEmpty();
}
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 c87ece2..763e19b 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.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 @@
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 @@
@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 3352177..064e348 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 @@
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/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index f8bcff5..6fe259e 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -45,6 +45,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -54,9 +56,11 @@
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -70,6 +74,7 @@
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import com.android.server.LocalServices;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.appop.AppOpsService;
@@ -77,6 +82,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -115,6 +121,18 @@
UidRecord.CHANGE_ACTIVE
};
+ private static PackageManagerInternal sPackageManagerInternal;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ sPackageManagerInternal = mock(PackageManagerInternal.class);
+ doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
+ .getSystemUiServiceComponent();
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
+ }
+
@Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
private Context mContext = getInstrumentation().getTargetContext();
@@ -258,8 +276,11 @@
uidRec.hasInternetPermission = true;
mAms.mProcessList.mActiveUids.put(uid, uidRec);
- final ProcessRecord appRec = new ProcessRecord(mAms, new ApplicationInfo(), TAG, uid);
- appRec.thread = Mockito.mock(IApplicationThread.class);
+ ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "";
+
+ final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
+ appRec.thread = mock(IApplicationThread.class);
mAms.mProcessList.mLruProcesses.add(appRec);
return uidRec;
@@ -497,7 +518,7 @@
};
final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length];
for (int i = 0; i < observers.length; ++i) {
- observers[i] = Mockito.mock(IUidObserver.Stub.class);
+ observers[i] = mock(IUidObserver.Stub.class);
when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
@@ -610,7 +631,7 @@
*/
@Test
public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
- final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+ final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
@@ -704,7 +725,7 @@
assertEquals("No observers registered, so validateUids should be empty",
0, mAms.mValidateUids.size());
- final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+ final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index d12d804..b2d7177 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -22,12 +22,15 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerService;
@@ -45,6 +48,7 @@
public class OomAdjusterTests {
private static Context sContext;
private static ActivityManagerService sService;
+ private static PackageManagerInternal sPackageManagerInternal;
private ProcessRecord mProcessRecord;
@@ -56,6 +60,13 @@
public static void setUpOnce() {
sContext = getInstrumentation().getTargetContext();
+ sPackageManagerInternal = mock(PackageManagerInternal.class);
+ doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
+ .getSystemUiServiceComponent();
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
+
// We need to run with dexmaker share class loader to make use of
// ActivityTaskManagerService from wm package.
runWithDexmakerShareClassLoader(() -> {
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 74e7f8c..a0b9d9d 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 @@
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/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 3e5c21c..cbe49eb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -124,6 +124,9 @@
// Key is a pair of uri and userId
private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();
+ // Used as an override when set to nonzero.
+ private long mCurrentTimeMillis = 0;
+
public MockInjector(MockSystemServices services, DpmMockContext context) {
super(context);
this.services = services;
@@ -470,5 +473,19 @@
@Override
public void runCryptoSelfTest() {}
+
+ @Override
+ public String[] getPersonalAppsForSuspension(int userId) {
+ return new String[]{};
+ }
+
+ public void setSystemCurrentTimeMillis(long value) {
+ mCurrentTimeMillis = value;
+ }
+
+ @Override
+ public long systemCurrentTimeMillis() {
+ return mCurrentTimeMillis != 0 ? mCurrentTimeMillis : System.currentTimeMillis();
+ }
}
}
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 09d1d3a..b042e77 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -44,6 +44,7 @@
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -62,6 +63,7 @@
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.Notification;
+import android.app.PendingIntent;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
@@ -96,6 +98,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -103,6 +106,7 @@
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
+import org.hamcrest.Matcher;
import org.mockito.Mockito;
import org.mockito.internal.util.collections.Sets;
import org.mockito.stubbing.Answer;
@@ -181,6 +185,20 @@
"wQ==\n" +
"-----END CERTIFICATE-----\n";
+ // Constants for testing setManagedProfileMaximumTimeOff:
+ // Profile maximum time off value
+ private static final long PROFILE_OFF_TIMEOUT = TimeUnit.DAYS.toMillis(5);
+ // Synthetic time at the beginning of test.
+ private static final long PROFILE_OFF_START = 1;
+ // Time when warning notification should be posted,
+ private static final long PROFILE_OFF_WARNING_TIME =
+ PROFILE_OFF_START + PROFILE_OFF_TIMEOUT - TimeUnit.DAYS.toMillis(1);
+ // Time when the apps should be suspended
+ private static final long PROFILE_OFF_DEADLINE = PROFILE_OFF_START + PROFILE_OFF_TIMEOUT;
+ // Notification titles for setManagedProfileMaximumTimeOff tests:
+ private static final String PROFILE_OFF_WARNING_TITLE = "suspended_tomorrow";
+ private static final String PROFILE_OFF_SUSPENDED_TITLE = "suspended";
+
@Override
protected void setUp() throws Exception {
super.setUp();
@@ -196,6 +214,11 @@
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);
@@ -1553,20 +1576,7 @@
dpms.approveCaCert(fourCerts.getList().get(1), userId, true);
// a notification should be shown saying that there are two certificates left to approve.
verify(getServices().notificationManager, timeout(1000))
- .notifyAsUser(anyString(), anyInt(), argThat(
- new BaseMatcher<Notification>() {
- @Override
- public boolean matches(Object item) {
- final Notification noti = (Notification) item;
- return TEST_STRING.equals(
- noti.extras.getString(Notification.EXTRA_TITLE));
- }
- @Override
- public void describeTo(Description description) {
- description.appendText(
- "Notification{title=\"" + TEST_STRING + "\"}");
- }
- }), eq(user));
+ .notifyAsUser(anyString(), anyInt(), argThat(hasTitle(TEST_STRING)), eq(user));
}
/**
@@ -6267,7 +6277,214 @@
assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
}
- // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+ /**
+ * Tests the case when the user doesn't turn the profile on in time, verifies that the user is
+ * warned with a notification and then the apps get suspended.
+ */
+ public void testMaximumProfileTimeOff_profileOffTimeExceeded() throws Exception {
+ prepareMocksForSetMaximumProfileTimeOff();
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ // The profile is running, neither alarm nor notification should be posted.
+ verify(getServices().alarmManager, never())
+ .set(anyInt(), anyLong(), any(PendingIntent.class));
+ verify(getServices().notificationManager, never())
+ .notify(anyInt(), any(Notification.class));
+ // Apps shouldn't be suspended.
+ verifyZeroInteractions(getServices().ipackageManager);
+ clearInvocations(getServices().alarmManager);
+
+ sendUserStoppedBroadcastForProfile();
+
+ // Verify the alarm was scheduled for time when the warning should be shown.
+ verify(getServices().alarmManager, times(1))
+ .set(anyInt(), eq(PROFILE_OFF_WARNING_TIME), any());
+ // But still no notification should be posted at this point.
+ verify(getServices().notificationManager, never())
+ .notify(anyInt(), any(Notification.class));
+ // Apps shouldn't be suspended.
+ verifyZeroInteractions(getServices().ipackageManager);
+ clearInvocations(getServices().alarmManager);
+
+ // Pretend the alarm went off.
+ dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
+ sendProfileOffDeadlineAlarmBroadcast();
+
+ // Verify the alarm was scheduled for the actual deadline this time.
+ verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
+ // Now the user should see a warning notification.
+ verify(getServices().notificationManager, times(1))
+ .notify(anyInt(), argThat(hasTitle(PROFILE_OFF_WARNING_TITLE)));
+ // Apps shouldn't be suspended yet.
+ verifyZeroInteractions(getServices().ipackageManager);
+ clearInvocations(getServices().alarmManager);
+ clearInvocations(getServices().notificationManager);
+
+ // Pretend the alarm went off.
+ dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
+ sendProfileOffDeadlineAlarmBroadcast();
+
+ // Verify the alarm was not set.
+ verifyZeroInteractions(getServices().alarmManager);
+ // Now the user should see a notification about suspended apps.
+ verify(getServices().notificationManager, times(1))
+ .notify(anyInt(), argThat(hasTitle(PROFILE_OFF_SUSPENDED_TITLE)));
+ // Verify that the apps are suspended.
+ verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
+ any(), eq(true), any(), any(), any(), any(), anyInt());
+ }
+
+ /**
+ * Tests the case when the user turns the profile back on long before the deadline (> 1 day).
+ */
+ public void testMaximumProfileTimeOff_turnOnBeforeWarning() throws Exception {
+ prepareMocksForSetMaximumProfileTimeOff();
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ sendUserStoppedBroadcastForProfile();
+ clearInvocations(getServices().alarmManager);
+ sendUserUnlockedBroadcastForProfile();
+
+ // Verify that the alarm got discharged.
+ verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
+ }
+
+ /**
+ * Tests the case when the user turns the profile back on after the warning notification.
+ */
+ public void testMaximumProfileTimeOff_turnOnAfterWarning() throws Exception {
+ prepareMocksForSetMaximumProfileTimeOff();
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ sendUserStoppedBroadcastForProfile();
+
+ // Pretend the alarm went off.
+ dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
+ sendProfileOffDeadlineAlarmBroadcast();
+
+ clearInvocations(getServices().alarmManager);
+ clearInvocations(getServices().notificationManager);
+ sendUserUnlockedBroadcastForProfile();
+
+ // Verify that the alarm got discharged.
+ verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
+ // Verify that the notification is removed.
+ verify(getServices().notificationManager, times(1))
+ .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
+ }
+
+ /**
+ * Tests the case when the user turns the profile back on when the apps are already suspended.
+ */
+ public void testMaximumProfileTimeOff_turnOnAfterDeadline() throws Exception {
+ prepareMocksForSetMaximumProfileTimeOff();
+
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ sendUserStoppedBroadcastForProfile();
+
+ // Pretend the alarm went off after the deadline.
+ dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
+ sendProfileOffDeadlineAlarmBroadcast();
+
+ clearInvocations(getServices().alarmManager);
+ clearInvocations(getServices().notificationManager);
+ clearInvocations(getServices().ipackageManager);
+
+ sendUserUnlockedBroadcastForProfile();
+
+ // Verify that the notification is removed (at this point DPC should show it).
+ verify(getServices().notificationManager, times(1))
+ .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
+ // Verify that the apps are NOT unsuspeded.
+ verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
+ any(), eq(false), any(), any(), any(), any(), anyInt());
+ }
+
+ private void sendUserUnlockedBroadcastForProfile() throws Exception {
+ when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+ .thenReturn(true);
+ final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, DpmMockContext.CALLER_USER_HANDLE);
+ getServices().injectBroadcast(
+ mServiceContext, unlockedIntent, DpmMockContext.CALLER_USER_HANDLE);
+ flushTasks();
+ }
+
+
+ private void sendProfileOffDeadlineAlarmBroadcast() throws Exception {
+ final Intent deadlineAlarmIntent =
+ new Intent(DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE);
+ getServices().injectBroadcast(
+ mServiceContext, deadlineAlarmIntent, DpmMockContext.CALLER_USER_HANDLE);
+ flushTasks();
+ }
+
+ private void sendUserStoppedBroadcastForProfile() throws Exception {
+ when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+ .thenReturn(false);
+ final Intent stoppedIntent = new Intent(Intent.ACTION_USER_STOPPED)
+ .putExtra(Intent.EXTRA_USER_HANDLE, DpmMockContext.CALLER_USER_HANDLE);
+ getServices().injectBroadcast(mServiceContext, stoppedIntent,
+ DpmMockContext.CALLER_USER_HANDLE);
+ flushTasks();
+ }
+
+ private void prepareMocksForSetMaximumProfileTimeOff() throws Exception {
+ addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ when(getServices().userManager.isUserUnlocked()).thenReturn(true);
+
+ // Pretend our admin handles CHECK_POLICY_COMPLIANCE intent.
+ final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+ intent.setPackage(admin1.getPackageName());
+
+ doReturn(Collections.singletonList(new ResolveInfo()))
+ .when(getServices().packageManager).queryIntentActivitiesAsUser(
+ any(Intent.class), anyInt(), eq(DpmMockContext.CALLER_USER_HANDLE));
+
+ dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_START);
+ // To allow creation of Notification via Notification.Builder
+ mContext.applicationInfo = mRealTestContext.getApplicationInfo();
+
+ // Setup notification titles.
+ when(mServiceContext.resources
+ .getString(R.string.personal_apps_suspended_tomorrow_title))
+ .thenReturn(PROFILE_OFF_WARNING_TITLE);
+ when(mServiceContext.resources
+ .getString(R.string.personal_apps_suspended_title))
+ .thenReturn(PROFILE_OFF_SUSPENDED_TITLE);
+
+ clearInvocations(getServices().ipackageManager);
+ }
+
+ private static Matcher<Notification> hasTitle(String expected) {
+ return new BaseMatcher<Notification>() {
+ @Override
+ public boolean matches(Object item) {
+ final Notification notification = (Notification) item;
+ return expected.equals(notification.extras.getString(Notification.EXTRA_TITLE));
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("Notification{title=\"" + expected + "\"}");
+ }
+ };
+ }
+
+ // admin1 is the outgoing DPC, adminAnotherPackage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER);
@@ -6294,7 +6511,7 @@
mServiceContext.binder.restoreCallingIdentity(ident);
}
- // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+ // admin1 is the outgoing DPC, adminAnotherPackage is the incoming one.
private void assertProfileOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(DpmMockContext.CALLER_USER_HANDLE,
TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER);
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 8625a1e..20716ab 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 @@
}
@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/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 7b3417a..b69cc47 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -107,9 +107,10 @@
eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
SensorEventListener listener = listenerCaptor.getValue();
- // Set up system to return 5 as a brightness value
+ // Set up system to return 0.02f as a brightness value
float lux1 = 100.0f;
- float normalizedBrightness1 = 0.0158f; //float equivalent of 5 out of 255
+ // Brightness as float (from 0.0f to 1.0f)
+ float normalizedBrightness1 = 0.02f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
.thenReturn(lux1);
when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
@@ -120,14 +121,14 @@
// This is the important bit: When the new brightness is set, make sure the new
// brightening threshold is beyond the maximum brightness value...so that we can test that
// our threshold clamping works.
- when(mScreenBrightnessThresholds.getBrighteningThreshold(5)).thenReturn(1.0f);
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+ .thenReturn(1.0f);
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
- assertEquals(5, controller.getAutomaticScreenBrightness());
+ assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
-
- // Set up system to return 255 as a brightness value
+ // Set up system to return 0.0f (minimum possible brightness) as a brightness value
float lux2 = 10.0f;
float normalizedBrightness2 = 0.0f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
@@ -139,7 +140,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
- assertEquals(1, controller.getAutomaticScreenBrightness());
+ assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
}
@Test
@@ -153,9 +154,9 @@
eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
SensorEventListener listener = listenerCaptor.getValue();
- // Set up system to return 250 as a brightness value
+ // Set up system to return 0.98f as a brightness value
float lux1 = 100.0f;
- float normalizedBrightness1 = 0.981f; //float equivalent of 250 out of 255
+ float normalizedBrightness1 = 0.98f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
.thenReturn(lux1);
when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
@@ -166,14 +167,15 @@
// This is the important bit: When the new brightness is set, make sure the new
// brightening threshold is beyond the maximum brightness value...so that we can test that
// our threshold clamping works.
- when(mScreenBrightnessThresholds.getBrighteningThreshold(250)).thenReturn(260.0f);
+ when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+ .thenReturn(1.1f);
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
- assertEquals(250, controller.getAutomaticScreenBrightness());
+ assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
- // Set up system to return 255 as a brightness value
+ // Set up system to return 1.0f as a brightness value (brightness_max)
float lux2 = 110.0f;
float normalizedBrightness2 = 1.0f;
when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
@@ -185,7 +187,7 @@
// Send new sensor value and verify
listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
- assertEquals(255, controller.getAutomaticScreenBrightness());
+ assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
}
@Test
@@ -204,10 +206,10 @@
// User sets brightness to 100
controller.configure(true /* enable */, null /* configuration */,
- 100 /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+ 0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
// There should be a user data point added to the mapper.
- verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 100);
+ verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
}
}
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 0000000..d438a0e
--- /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 728e149..e16f314 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 @@
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 c4289ef..e7e8aca 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 @@
}
@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 965304f..21af356 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@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 @@
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 @@
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 @@
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 ecdd9e5..7a5e226 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 @@
// 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 @@
// 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 @@
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 @@
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 f7304bd..3095c87 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 @@
@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 @@
@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 @@
@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 @@
@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/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 6ae8313..0700f9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -329,6 +329,7 @@
private boolean mCreateStack = true;
private ActivityStack mStack;
+ private TaskDisplayArea mTaskDisplayArea;
TaskBuilder(ActivityStackSupervisor supervisor) {
mSupervisor = supervisor;
@@ -378,9 +379,16 @@
return this;
}
+ TaskBuilder setDisplay(DisplayContent display) {
+ mTaskDisplayArea = display.getDefaultTaskDisplayArea();
+ return this;
+ }
+
Task build() {
if (mStack == null && mCreateStack) {
- mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
+ TaskDisplayArea displayArea = mTaskDisplayArea != null ? mTaskDisplayArea
+ : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ mStack = displayArea.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
spyOn(mStack);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 9b7ffd6..e8fab2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -37,7 +37,6 @@
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationTarget;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import org.junit.Test;
@@ -97,7 +96,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testModeChangeRemoteAnimatorNoSnapshot() {
// setup currently defaults to no snapshot.
setUpOnDisplay(mDisplayContent);
@@ -115,7 +113,6 @@
}
@Test
- @FlakyTest(bugId = 131005232)
public void testCancelPendingChangeOnRemove() {
// setup currently defaults to no snapshot.
setUpOnDisplay(mDisplayContent);
@@ -135,8 +132,7 @@
}
@Test
- @FlakyTest(bugId = 131005232)
- public void testNoChangeWhenMoveDisplay() {
+ public void testNoChangeOnOldDisplayWhenMoveDisplay() {
mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
dc1.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -151,9 +147,8 @@
assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
- // Make sure we're not waiting for a change animation (no leash)
- assertFalse(mTask.isInChangeTransition());
- assertNull(mActivity.mSurfaceFreezer.mSnapshot);
+ // Make sure the change transition is not the old display
+ assertFalse(dc1.mChangingContainers.contains(mTask));
waitUntilHandlersIdle();
mActivity.removeImmediately();
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index be25597..e887be0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -33,6 +33,7 @@
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Build/Install/Run:
@@ -40,6 +41,7 @@
*/
@SmallTest
@Presubmit
+@RunWith(WindowTestRunner.class)
@FlakyTest
public class RefreshRatePolicyTest extends WindowTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 519ac78..dcc2ff1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -54,10 +54,7 @@
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.TaskInfo;
@@ -75,12 +72,10 @@
import android.util.Xml;
import android.view.DisplayInfo;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.wm.Task.TaskFactory;
-import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
import org.junit.Test;
@@ -368,25 +363,38 @@
@Test
public void testComputeConfigResourceOverrides() {
- final Task task = new TaskBuilder(mSupervisor).build();
+ final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
+ TestDisplayContent display = new TestDisplayContent.Builder(
+ mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
final Configuration inOutConfig = new Configuration();
final Configuration parentConfig = new Configuration();
final int longSide = 1200;
final int shortSide = 600;
+ final Rect parentBounds = new Rect(0, 0, 250, 500);
+ parentConfig.windowConfiguration.setBounds(parentBounds);
parentConfig.densityDpi = 400;
- parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
- parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
+ parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200
+ parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100
parentConfig.windowConfiguration.setRotation(ROTATION_0);
- // Portrait bounds.
- inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
- // By default, the parent bounds should limit the existing input bounds.
+ // By default, the input bounds will fill parent.
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation);
+ // If bounds are overridden, config properties should be made to match. Surface hierarchy
+ // will crop for policy.
+ inOutConfig.setToDefaults();
+ inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
+ // By default, the parent bounds should limit the existing input bounds.
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
+ assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
+
inOutConfig.setToDefaults();
// Landscape bounds.
inOutConfig.windowConfiguration.getBounds().set(0, 0, longSide, shortSide);
@@ -394,21 +402,17 @@
// Setup the display with a top stable inset. The later assertion will ensure the inset is
// excluded from screenHeightDp.
final int statusBarHeight = 100;
- final DisplayContent displayContent = task.mDisplayContent;
- final DisplayPolicy policy = mock(DisplayPolicy.class);
+ final DisplayPolicy policy = display.getDisplayPolicy();
doAnswer(invocationOnMock -> {
final Rect insets = invocationOnMock.<Rect>getArgument(0);
insets.top = statusBarHeight;
return null;
}).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
- doReturn(policy).when(displayContent).getDisplayPolicy();
- doReturn(mock(WmDisplayCutout.class)).when(displayContent)
- .calculateDisplayCutoutForRotation(anyInt());
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
final ActivityRecord.CompatDisplayInsets compatIntsets =
- new ActivityRecord.CompatDisplayInsets(displayContent, task);
+ new ActivityRecord.CompatDisplayInsets(display, task);
task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
@@ -454,7 +458,6 @@
parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
parentConfig.windowConfiguration.setRotation(ROTATION_0);
- final float density = 2.5f; // densityDpi / DENSITY_DEFAULT_SCALE = 400 / 160.0f
final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
final int screenLayout = parentConfig.screenLayout
@@ -463,31 +466,38 @@
Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
// Portrait bounds overlapping with navigation bar, without insets.
- inOutConfig.windowConfiguration.getBounds().set(0,
+ final Rect freeformBounds = new Rect(0,
displayHeight - 10 - longSide,
shortSide,
displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
// Set to freeform mode to verify bug fix.
inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
- assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
- assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ // screenW/H should not be effected by parent since overridden and freeform
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
inOutConfig.setToDefaults();
// Landscape bounds overlapping with navigtion bar, without insets.
- inOutConfig.windowConfiguration.getBounds().set(0,
+ freeformBounds.set(0,
displayHeight - 10 - shortSide,
longSide,
displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
- assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
- assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 7cb5e84..f354a04 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -186,7 +186,7 @@
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final int stackOutset = 10;
spyOn(stack);
- doReturn(stackOutset).when(stack).getStackOutset();
+ doReturn(stackOutset).when(stack).getTaskOutset();
doReturn(true).when(stack).inMultiWindowMode();
// Mock the resolved override windowing mode to non-fullscreen
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index a4f1487..520ac19 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -251,6 +254,22 @@
assertEquals(otherWindowInitialZoom, wallpaperWindow.mWallpaperZoomOut, .01f);
}
+ /**
+ * Tests that the windowing mode of the wallpaper window must always be fullscreen.
+ */
+ @Test
+ public void testWallpaperTokenWindowingMode() {
+ final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
+ final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class),
+ true, dc, true /* ownerCanManageAppTokens */);
+
+ // The wallpaper should have requested override fullscreen windowing mode, so the
+ // configuration (windowing mode) propagation from display won't change it.
+ dc.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
+ dc.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+ assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
+ }
private WindowState createWallpaperTargetWindow(DisplayContent dc) {
final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
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 53cc09b..7d2e880 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -44,7 +44,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -82,7 +81,7 @@
* Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}.
*
* Build/Install/Run:
- * atest WmTests:TaskOrganizerTests
+ * atest WmTests:WindowOrganizerTests
*/
@SmallTest
@Presubmit
@@ -265,15 +264,22 @@
// 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());
}
@@ -407,22 +413,20 @@
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = stack.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
- t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
final int origScreenWDp = task.getConfiguration().screenHeightDp;
final int origScreenHDp = task.getConfiguration().screenHeightDp;
t = new WindowContainerTransaction();
// verify that setting config overrides on parent restricts children.
t.setScreenSizeDp(stack.mRemoteToken
- .toWindowContainerToken(), origScreenWDp, origScreenHDp);
- t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200));
+ .toWindowContainerToken(), origScreenWDp, origScreenHDp / 2);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+ assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp);
t = new WindowContainerTransaction();
t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
SCREEN_HEIGHT_DP_UNDEFINED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertNotEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+ assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
}
@Test
@@ -905,12 +909,13 @@
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 9dfa3ac..fa99095 100755
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -734,6 +734,31 @@
"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 1b60e48..a716b37 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1859,9 +1859,25 @@
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 @@
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 d82e93f..8d3f4e1 100644
--- a/telecomm/java/android/telecom/Logging/Session.java
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -427,7 +427,7 @@
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 cad5b70..a083301 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -258,6 +258,9 @@
// 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 @@
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 @@
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 151fb59..ede67dd 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -49,7 +49,7 @@
*
* 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 @@
/**
* @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 @@
/**
* @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 @@
/**
* @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 @@
/**
* @hide
*/
- @Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@NonNull @AccessNetworkConstants.TransportType
@@ -347,8 +346,7 @@
* 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 @@
* 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 @@
* @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 6c8dc00..7d20d0d 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -758,6 +758,13 @@
@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 = {