Merge changes I9f44ed05,I8990bf48,Ib302cbc2 into rvc-dev
* changes:
Revamp how changes are tracked in ShadeListBuilder
Add logging to NotifCollection for lifetime extension
Tweak ShadeListBuilder to be slightly less verbose
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index edf25ae..e533b7a 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -499,20 +499,58 @@
})
public @interface ParserName {}
+ /** Parser name returned by {@link #getParserName()} when no parser has been selected yet. */
public static final String PARSER_NAME_UNKNOWN = "android.media.mediaparser.UNKNOWN";
+ /**
+ * Parser for the Matroska container format, as defined in the <a
+ * href="https://matroska.org/technical/specs/">spec</a>.
+ */
public static final String PARSER_NAME_MATROSKA = "android.media.mediaparser.MatroskaParser";
+ /**
+ * Parser for fragmented files using the MP4 container format, as defined in ISO/IEC 14496-12.
+ */
public static final String PARSER_NAME_FMP4 = "android.media.mediaparser.FragmentedMp4Parser";
+ /**
+ * Parser for non-fragmented files using the MP4 container format, as defined in ISO/IEC
+ * 14496-12.
+ */
public static final String PARSER_NAME_MP4 = "android.media.mediaparser.Mp4Parser";
+ /** Parser for the MP3 container format, as defined in ISO/IEC 11172-3. */
public static final String PARSER_NAME_MP3 = "android.media.mediaparser.Mp3Parser";
+ /** Parser for the ADTS container format, as defined in ISO/IEC 13818-7. */
public static final String PARSER_NAME_ADTS = "android.media.mediaparser.AdtsParser";
+ /**
+ * Parser for the AC-3 container format, as defined in Digital Audio Compression Standard
+ * (AC-3).
+ */
public static final String PARSER_NAME_AC3 = "android.media.mediaparser.Ac3Parser";
+ /** Parser for the TS container format, as defined in ISO/IEC 13818-1. */
public static final String PARSER_NAME_TS = "android.media.mediaparser.TsParser";
+ /**
+ * Parser for the FLV container format, as defined in Adobe Flash Video File Format
+ * Specification.
+ */
public static final String PARSER_NAME_FLV = "android.media.mediaparser.FlvParser";
+ /** Parser for the OGG container format, as defined in RFC 3533. */
public static final String PARSER_NAME_OGG = "android.media.mediaparser.OggParser";
+ /** Parser for the PS container format, as defined in ISO/IEC 11172-1. */
public static final String PARSER_NAME_PS = "android.media.mediaparser.PsParser";
+ /**
+ * Parser for the WAV container format, as defined in Multimedia Programming Interface and Data
+ * Specifications.
+ */
public static final String PARSER_NAME_WAV = "android.media.mediaparser.WavParser";
+ /** Parser for the AMR container format, as defined in RFC 4867. */
public static final String PARSER_NAME_AMR = "android.media.mediaparser.AmrParser";
+ /**
+ * Parser for the AC-4 container format, as defined by Dolby AC-4: Audio delivery for
+ * Next-Generation Entertainment Services.
+ */
public static final String PARSER_NAME_AC4 = "android.media.mediaparser.Ac4Parser";
+ /**
+ * Parser for the FLAC container format, as defined in the <a
+ * href="https://xiph.org/flac/">spec</a>.
+ */
public static final String PARSER_NAME_FLAC = "android.media.mediaparser.FlacParser";
// MediaParser parameters.
diff --git a/api/current.txt b/api/current.txt
index 1c1d142..b4db1f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6833,13 +6833,6 @@
method public final android.os.IBinder onBind(android.content.Intent);
}
- public class DevicePolicyKeyguardService extends android.app.Service {
- ctor public DevicePolicyKeyguardService();
- method @Nullable public void dismiss();
- method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
- method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
- }
-
public class DevicePolicyManager {
method public void addCrossProfileIntentFilter(@NonNull android.content.ComponentName, android.content.IntentFilter, int);
method public boolean addCrossProfileWidgetProvider(@NonNull android.content.ComponentName, String);
@@ -7055,7 +7048,6 @@
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
method public void setRestrictionsProvider(@NonNull android.content.ComponentName, @Nullable android.content.ComponentName);
method public void setScreenCaptureDisabled(@NonNull android.content.ComponentName, boolean);
- method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
method public void setSecureSetting(@NonNull android.content.ComponentName, String, String);
method public void setSecurityLoggingEnabled(@NonNull android.content.ComponentName, boolean);
method public void setShortSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
@@ -7082,7 +7074,6 @@
field public static final String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
field public static final String ACTION_ADMIN_POLICY_COMPLIANCE = "android.app.action.ADMIN_POLICY_COMPLIANCE";
field public static final String ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED = "android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED";
- field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
field public static final String ACTION_CHECK_POLICY_COMPLIANCE = "android.app.action.CHECK_POLICY_COMPLIANCE";
field public static final String ACTION_DEVICE_ADMIN_SERVICE = "android.app.action.DEVICE_ADMIN_SERVICE";
field public static final String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
@@ -30284,6 +30275,7 @@
field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
field public static final int NET_CAPABILITY_RCS = 8; // 0x8
field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+ field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
diff --git a/api/system-current.txt b/api/system-current.txt
index c664bed..c5d319c 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -848,6 +848,13 @@
package android.app.admin {
+ public class DevicePolicyKeyguardService extends android.app.Service {
+ ctor public DevicePolicyKeyguardService();
+ method @Nullable public void dismiss();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @Nullable public android.view.SurfaceControlViewHost.SurfacePackage onCreateKeyguardSurface(@NonNull android.os.IBinder);
+ }
+
public class DevicePolicyManager {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
@@ -872,8 +879,10 @@
method @Deprecated @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public boolean setActiveProfileOwner(@NonNull android.content.ComponentName, String) throws java.lang.IllegalArgumentException;
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public void setDeviceProvisioningConfigApplied();
method @Deprecated @RequiresPermission(value=android.Manifest.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS, conditional=true) public void setProfileOwnerCanAccessDeviceIds(@NonNull android.content.ComponentName);
+ method public void setSecondaryLockscreenEnabled(@NonNull android.content.ComponentName, boolean);
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
+ field public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE = "android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index a321460..20f9e76 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -337,6 +337,7 @@
"tests/external/StatsPullerManager_test.cpp",
"tests/FieldValue_test.cpp",
"tests/guardrail/StatsdStats_test.cpp",
+ "tests/HashableDimensionKey_test.cpp",
"tests/indexed_priority_queue_test.cpp",
"tests/log_event/LogEventQueue_test.cpp",
"tests/LogEntryMatcher_test.cpp",
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 23d8f59..29249f4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -230,6 +230,47 @@
}
}
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+ const HashableDimensionKey& primaryKey,
+ const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+ if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+ ALOGE("Contains linked values false: whatKey is too small");
+ return false;
+ }
+
+ for (const auto& primaryValue : primaryKey.getValues()) {
+ bool found = false;
+ for (const auto& whatValue : whatKey.getValues()) {
+ if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+ primaryValue.mValue == whatValue.mValue) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+ const Field& stateField, const Field& metricField) {
+ for (auto stateLink : stateLinks) {
+ if (stateLink.stateAtomId != stateAtomId) {
+ continue;
+ }
+
+ for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+ if (stateLink.stateFields[i].mMatcher == stateField &&
+ stateLink.metricFields[i].mMatcher == metricField) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
if (s1.size() != s2.size()) {
return s1.size() < s2.size();
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index a766bba..33a5024 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -110,6 +110,10 @@
return mStateValuesKey;
}
+ inline HashableDimensionKey* getMutableStateValuesKey() {
+ return &mStateValuesKey;
+ }
+
inline void setStateValuesKey(const HashableDimensionKey& key) {
mStateValuesKey = key;
}
@@ -169,6 +173,32 @@
void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
HashableDimensionKey* statePrimaryKey);
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+ const HashableDimensionKey& primaryKey,
+ const std::vector<Metric2State>& stateLinks,
+ const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+ const Field& stateField, const Field& metricField);
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 14585c3..97512ed 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -334,6 +334,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57d4d78..5cd00c3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -123,7 +123,8 @@
BatteryLevelChanged battery_level_changed =
30 [(module) = "framework", (module) = "statsdtest"];
ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
- PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"];
+ PluggedStateChanged plugged_state_changed = 32
+ [(module) = "framework", (module) = "statsdtest"];
InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
TouchEventReported touch_event_reported = 34;
WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index a3701a7..79a7e8d 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,15 +41,50 @@
namespace os {
namespace statsd {
+// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
+// pullAtomCallbackDied is never called.
+struct PullAtomCallbackDeathCookie {
+ PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
+ const wp<StatsPuller>& puller)
+ : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+ }
+
+ sp<StatsPullerManager> mPullerManager;
+ PullerKey mPullerKey;
+ wp<StatsPuller> mPuller;
+};
+
+void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
+ PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
+ sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+ const PullerKey& pullerKey = cookie_->mPullerKey;
+ wp<StatsPuller> puller = cookie_->mPuller;
+
+ // Erase the mapping from the puller key to the puller if the mapping still exists.
+ // Note that we are removing the StatsPuller object, which internally holds the binder
+ // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
+ lock_guard<mutex> lock(thiz->mLock);
+ const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
+ if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
+ StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
+ /*registered=*/false);
+ thiz->kAllPullAtomInfo.erase(pullerKey);
+ }
+ // The death recipient corresponding to this specific IPullAtomCallback can never
+ // be triggered again, so free up resources.
+ delete cookie_;
+}
+
// Values smaller than this may require to update the alarm.
const int64_t NO_ALARM_UPDATE = INT64_MAX;
StatsPullerManager::StatsPullerManager()
: kAllPullAtomInfo({
// TrainInfo.
- {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()},
+ {{.atomTag = util::TRAIN_INFO, .uid = AID_STATSD}, new TrainInfoPuller()},
}),
- mNextPullTimeNs(NO_ALARM_UPDATE) {
+ mNextPullTimeNs(NO_ALARM_UPDATE),
+ mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
}
bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey,
@@ -310,19 +345,28 @@
bool useUid) {
std::lock_guard<std::mutex> _l(mLock);
VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
- // TODO(b/146439412): linkToDeath with the callback so that we can remove it
- // and delete the puller.
+
StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
- kAllPullAtomInfo[{.atomTag = atomTag, .uid = useUid ? uid : -1}] = new StatsCallbackPuller(
- atomTag, callback, actualCoolDownNs, actualTimeoutNs, additiveFields);
+
+ sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
+ actualTimeoutNs, additiveFields);
+ PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
+ AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
+ new PullAtomCallbackDeathCookie(this, key, puller));
+ kAllPullAtomInfo[key] = puller;
}
-void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
+ bool useUids) {
std::lock_guard<std::mutex> _l(mLock);
- StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
- kAllPullAtomInfo.erase({.atomTag = atomTag});
+ PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
+ if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
+ StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
+ /*registered=*/false);
+ kAllPullAtomInfo.erase(key);
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index c5824a8..ab0ccee 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -101,11 +101,11 @@
// If the metric wants to make any change to the data, like timestamps, they
// should make a copy as this data may be shared with multiple metrics.
virtual bool Pull(int tagId, const ConfigKey& configKey,
- vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+ vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
// Same as above, but directly specify the allowed uids to pull from.
virtual bool Pull(int tagId, const vector<int32_t>& uids,
- vector<std::shared_ptr<LogEvent>>* data, bool useUids = false);
+ vector<std::shared_ptr<LogEvent>>* data, bool useUids = true);
// Clear pull data cache immediately.
int ForceClearPullerCache();
@@ -118,9 +118,9 @@
void RegisterPullAtomCallback(const int uid, const int32_t atomTag, const int64_t coolDownNs,
const int64_t timeoutNs, const vector<int32_t>& additiveFields,
const shared_ptr<IPullAtomCallback>& callback,
- bool useUid = false);
+ bool useUid = true);
- void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
+ void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = true);
std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
@@ -152,7 +152,7 @@
std::map<ConfigKey, wp<PullUidProvider>> mPullUidProviders;
bool PullLocked(int tagId, const ConfigKey& configKey, vector<std::shared_ptr<LogEvent>>* data,
- bool useUids = false);
+ bool useUids = true);
bool PullLocked(int tagId, const vector<int32_t>& uids, vector<std::shared_ptr<LogEvent>>* data,
bool useUids);
@@ -164,6 +164,15 @@
int64_t mNextPullTimeNs;
+ // Death recipient that is triggered when the process holding the IPullAtomCallback has died.
+ ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
+
+ /**
+ * Death recipient callback that is called when a pull atom callback dies.
+ * The cookie is a pointer to a PullAtomCallbackDeathCookie.
+ */
+ static void pullAtomCallbackDied(void* cookie);
+
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index e85b975..0de92f3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -55,6 +55,7 @@
const int FIELD_ID_DIMENSION_IN_WHAT = 1;
const int FIELD_ID_BUCKET_INFO = 3;
const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
+const int FIELD_ID_SLICE_BY_STATE = 6;
// for DurationBucketInfo
const int FIELD_ID_DURATION = 3;
const int FIELD_ID_BUCKET_NUM = 4;
@@ -115,6 +116,14 @@
}
mUnSlicedPartCondition = ConditionState::kUnknown;
+ for (const auto& stateLink : metric.state_link()) {
+ Metric2State ms;
+ ms.stateAtomId = stateLink.state_atom_id();
+ translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+ translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+ }
+
mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
mMetric2ConditionLinks.size() == 1) {
@@ -150,21 +159,49 @@
return anomalyTracker;
}
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+ const HashableDimensionKey& primaryKey,
+ const int32_t oldState, const int32_t newState) {
+ // Create a FieldValue object to hold the new state.
+ FieldValue value;
+ value.mValue.setInt(newState);
+ // Check if this metric has a StateMap. If so, map the new state value to
+ // the correct state group id.
+ mapStateValue(atomId, &value);
+
+ flushIfNeededLocked(eventTimeNs);
+
+ // Each duration tracker is mapped to a different whatKey (a set of values from the
+ // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+ // state change event are a subset of the tracker's whatKey field values.
+ //
+ // Ex. For a duration metric dimensioned on uid and tag:
+ // DurationTracker1 whatKey = uid: 1001, tag: 1
+ // DurationTracker2 whatKey = uid: 1002, tag 1
+ //
+ // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+ // change.
+ for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+ if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+ continue;
+ }
+ whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+ }
+}
+
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
const MetricDimensionKey& eventKey) const {
switch (mAggregationType) {
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
- mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mTimeBaseNs, mBucketSizeNs, mConditionSliced,
- mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+ mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
case DurationMetric_AggregationType_MAX_SPARSE:
return make_unique<MaxDurationTracker>(
- mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
- mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
- mTimeBaseNs, mBucketSizeNs, mConditionSliced,
- mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+ mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+ mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+ mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
}
}
@@ -364,6 +401,13 @@
writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
}
+ // Then fill slice_by_state.
+ for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+ uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+ FIELD_ID_SLICE_BY_STATE);
+ writeStateToProto(state, protoOutput);
+ protoOutput->end(stateToken);
+ }
// Then fill bucket_info (DurationBucketInfo).
for (const auto& bucket : pair.second) {
uint64_t bucketInfoToken = protoOutput->start(
@@ -460,7 +504,6 @@
const ConditionKey& conditionKeys,
bool condition, const LogEvent& event) {
const auto& whatKey = eventKey.getDimensionKeyInWhat();
-
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
if (hitGuardRailLocked(eventKey)) {
@@ -471,19 +514,18 @@
auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (mUseWhatDimensionAsInternalDimension) {
- it->second->noteStart(whatKey, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
return;
}
if (mInternalDimensions.empty()) {
- it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
- event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+ conditionKeys);
} else {
HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
- it->second->noteStart(
- dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+ it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+ conditionKeys);
}
}
@@ -519,6 +561,41 @@
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
}
+ // Stores atom id to primary key pairs for each state atom that the metric is
+ // sliced by.
+ std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+ // For states with primary fields, use MetricStateLinks to get the primary
+ // field values from the log event. These values will form a primary key
+ // that will be used to query StateTracker for the correct state value.
+ for (const auto& stateLink : mMetric2StateLinks) {
+ getDimensionForState(event.getValues(), stateLink,
+ &statePrimaryKeys[stateLink.stateAtomId]);
+ }
+
+ // For each sliced state, query StateTracker for the state value using
+ // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+ //
+ // Expected functionality: for any case where the MetricStateLinks are
+ // initialized incorrectly (ex. # of state links != # of primary fields, no
+ // links are provided for a state with primary fields, links are provided
+ // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+ // when queried using an incorrect key.
+ HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+ for (auto atomId : mSlicedStateAtoms) {
+ FieldValue value;
+ if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+ // found a primary key for this state, query using the key
+ queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+ } else {
+ // if no MetricStateLinks exist for this state atom,
+ // query using the default dimension key (empty HashableDimensionKey)
+ queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+ }
+ mapStateValue(atomId, &value);
+ stateValuesKey.addValue(value);
+ }
+
// Handles Stop events.
if (matcherIndex == mStopIndex) {
if (mUseWhatDimensionAsInternalDimension) {
@@ -559,8 +636,8 @@
condition = condition && mIsActive;
- handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
- condition, event);
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+ event);
}
size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 06da0f6..cc48f99 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -54,6 +54,10 @@
sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
+ void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+ const HashableDimensionKey& primaryKey, const int32_t oldState,
+ const int32_t newState) override;
+
protected:
void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
@@ -137,7 +141,7 @@
// Helper function to create a duration tracker given the metric aggregation type.
std::unique_ptr<DurationTracker> createDurationTracker(
- const MetricDimensionKey& eventKey) const;
+ const MetricDimensionKey& eventKey) const;
// This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index be754e2..2518d85 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -120,12 +120,13 @@
FieldValue value;
if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
// found a primary key for this state, query using the key
- getMappedStateValue(atomId, statePrimaryKeys[atomId], &value);
+ queryStateValue(atomId, statePrimaryKeys[atomId], &value);
} else {
// if no MetricStateLinks exist for this state atom,
// query using the default dimension key (empty HashableDimensionKey)
- getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+ queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
}
+ mapStateValue(atomId, &value);
stateValuesKey.addValue(value);
}
@@ -264,15 +265,17 @@
}
}
-void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
- FieldValue* value) {
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+ FieldValue* value) {
if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
value->mValue = Value(StateTracker::kStateUnknown);
value->mField.setTag(atomId);
ALOGW("StateTracker not found for state atom %d", atomId);
return;
}
+}
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
// check if there is a state map for this atom
auto atomIt = mStateGroupMap.find(atomId);
if (atomIt == mStateGroupMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 4c4cd89..4550e65 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -187,7 +187,8 @@
};
void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
- const HashableDimensionKey& primaryKey, int oldState, int newState){};
+ const HashableDimensionKey& primaryKey, const int32_t oldState,
+ const int32_t newState){};
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
// This method clears all the past buckets.
@@ -379,11 +380,15 @@
return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
}
- // Query StateManager for original state value.
- // If no state map exists for this atom, return the original value.
- // Otherwise, return the group_id mapped to the atom and original value.
- void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
- FieldValue* value);
+ // Query StateManager for original state value using the queryKey.
+ // The field and value are output.
+ void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+ FieldValue* value);
+
+ // If a state map exists for the given atom, replace the original state
+ // value with the group id mapped to the value.
+ // If no state map exists, keep the original state value.
+ void mapStateValue(const int32_t atomId, FieldValue* value);
DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
@@ -467,6 +472,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3fb9166..1fd6572 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -329,6 +329,11 @@
FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+ FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index afe93d4..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
int64_t mDuration;
};
+struct DurationValues {
+ // Recorded duration for current partial bucket.
+ int64_t mDuration;
+
+ // Sum of past partial bucket durations in current full bucket.
+ // Used for anomaly detection.
+ int64_t mDurationFullBucket;
+};
+
class DurationTracker {
public:
DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -73,7 +81,6 @@
mNested(nesting),
mCurrentBucketStartTimeNs(currentBucketStartNs),
mDuration(0),
- mDurationFullBucket(0),
mCurrentBucketNum(currentBucketNum),
mStartTimeNs(startTimeNs),
mConditionSliced(conditionSliced),
@@ -82,8 +89,8 @@
virtual ~DurationTracker(){};
- virtual void noteStart(const HashableDimensionKey& key, bool condition,
- const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+ virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+ const ConditionKey& conditionKey) = 0;
virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
const bool stopAll) = 0;
virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -91,6 +98,9 @@
virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
+ virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) = 0;
+
// Flush stale buckets if needed, and return true if the tracker has no on-going duration
// events, so that the owner can safely remove the tracker.
virtual bool flushIfNeeded(
@@ -109,9 +119,12 @@
// Dump internal states for debugging
virtual void dumpStates(FILE* out, bool verbose) const = 0;
- void setEventKey(const MetricDimensionKey& eventKey) {
- mEventKey = eventKey;
- }
+ virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+ virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+ // Replace old value with new value for the given state atom.
+ virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
protected:
int64_t getCurrentBucketEndTimeNs() const {
@@ -140,10 +153,11 @@
}
}
- void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+ void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+ const int64_t& bucketValue, const int64_t& bucketNum) {
for (auto& anomalyTracker : mAnomalyTrackers) {
if (anomalyTracker != nullptr) {
- anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+ anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
}
}
}
@@ -164,6 +178,10 @@
return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
}
+ void setEventKey(const MetricDimensionKey& eventKey) {
+ mEventKey = eventKey;
+ }
+
// A reference to the DurationMetricProducer's config key.
const ConfigKey& mConfigKey;
@@ -183,7 +201,8 @@
int64_t mDuration; // current recorded duration result (for partial bucket)
- int64_t mDurationFullBucket; // Sum of past partial buckets in current full bucket.
+ // Recorded duration results for each state key in the current partial bucket.
+ std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
int64_t mCurrentBucketNum;
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 2be5855..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,15 +26,14 @@
MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting,
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
int64_t currentBucketStartNs, int64_t currentBucketNum,
int64_t startTimeNs, int64_t bucketSizeNs,
bool conditionSliced, bool fullLink,
const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
- currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, fullLink, anomalyTrackers) {
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+ currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+ anomalyTrackers) {
}
bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -91,7 +90,6 @@
}
}
-
void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
bool forceStop) {
VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -240,6 +238,11 @@
}
}
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
for (auto& pair : mInfos) {
noteConditionChanged(pair.first, condition, timestamp);
@@ -309,6 +312,20 @@
fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
}
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+ return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+ return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+ ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index efb8dc7..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -54,10 +54,19 @@
void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
void onConditionChanged(bool condition, const int64_t timestamp) override;
+ void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) override;
+
int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
const int64_t currentTimestamp) const override;
void dumpStates(FILE* out, bool verbose) const override;
+ int64_t getCurrentStateKeyDuration() const override;
+
+ int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+ void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
private:
// Returns true if at least one of the mInfos is started.
bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 57f3965..19b2fe8 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,13 +26,12 @@
OringDurationTracker::OringDurationTracker(
const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
- sp<ConditionWizard> wizard, int conditionIndex,
- bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
- int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
- const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
- : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
- currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
- conditionSliced, fullLink, anomalyTrackers),
+ sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+ int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+ bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+ : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+ currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+ anomalyTrackers),
mStarted(),
mPaused() {
mLastStartTime = 0;
@@ -90,10 +89,14 @@
mConditionKeyMap.erase(key);
}
if (mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
- VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
- (long long)mDuration);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+ VLOG("record duration %lld, total duration %lld for state key %s",
+ (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
}
}
@@ -112,10 +115,14 @@
void OringDurationTracker::noteStopAll(const int64_t timestamp) {
if (!mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
- (long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+ (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
stopAnomalyAlarm(timestamp);
@@ -146,21 +153,36 @@
// Process the current bucket.
if (mStarted.size() > 0) {
- mDuration += (currentBucketEndTimeNs - mLastStartTime);
+ // Calculate the duration for the current state key.
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (currentBucketEndTimeNs - mLastStartTime);
}
- if (mDuration > 0) {
- DurationBucket current_info;
- current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
- current_info.mBucketEndNs = currentBucketEndTimeNs;
- current_info.mDuration = mDuration;
- (*output)[mEventKey].push_back(current_info);
- mDurationFullBucket += mDuration;
- VLOG(" duration: %lld", (long long)current_info.mDuration);
- }
- if (eventTimeNs > fullBucketEnd) {
- // End of full bucket, can send to anomaly tracker now.
- addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
- mDurationFullBucket = 0;
+ // Store DurationBucket info for each whatKey, stateKey pair.
+ // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+ // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+ // store durations for each stateKey, so we need to flush the bucket by creating a
+ // DurationBucket for each stateKey.
+ for (auto& durationIt : mStateKeyDurationMap) {
+ if (durationIt.second.mDuration > 0) {
+ DurationBucket current_info;
+ current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ current_info.mBucketEndNs = currentBucketEndTimeNs;
+ current_info.mDuration = durationIt.second.mDuration;
+ (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+ .push_back(current_info);
+
+ durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+ VLOG(" duration: %lld", (long long)current_info.mDuration);
+ }
+
+ if (eventTimeNs > fullBucketEnd) {
+ // End of full bucket, can send to anomaly tracker now.
+ addPastBucketToAnomalyTrackers(
+ MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+ getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+ durationIt.second.mDurationFullBucket = 0;
+ }
+ durationIt.second.mDuration = 0;
}
if (mStarted.size() > 0) {
@@ -169,20 +191,19 @@
info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
info.mDuration = mBucketSizeNs;
+ // Full duration buckets are attributed to the current stateKey.
(*output)[mEventKey].push_back(info);
// Safe to send these buckets to anomaly tracker since they must be full buckets.
// If it's a partial bucket, numBucketsForward would be 0.
- addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+ addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
VLOG(" add filling bucket with duration %lld", (long long)info.mDuration);
}
} else {
if (numBucketsForward >= 2) {
- addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+ addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
}
}
- mDuration = 0;
-
if (numBucketsForward > 0) {
mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
mCurrentBucketNum += numBucketsForward;
@@ -229,10 +250,14 @@
}
if (mStarted.empty()) {
- mDuration += (timestamp - mLastStartTime);
- VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
- (long long)mDuration);
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
+ VLOG("record duration %lld, total duration %lld for state key %s",
+ (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+ mEventKey.getStateValuesKey().toString().c_str());
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
}
@@ -288,10 +313,13 @@
} else {
if (!mStarted.empty()) {
VLOG("Condition false, all paused");
- mDuration += (timestamp - mLastStartTime);
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+ (timestamp - mLastStartTime);
mPaused.insert(mStarted.begin(), mStarted.end());
mStarted.clear();
- detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+ detectAndDeclareAnomaly(
+ timestamp, mCurrentBucketNum,
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
}
}
if (mStarted.empty()) {
@@ -299,6 +327,20 @@
}
}
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) {
+ // If no keys are being tracked, update the current state key and return.
+ if (mStarted.empty()) {
+ updateCurrentStateKey(atomId, newState);
+ return;
+ }
+ // Add the current duration length to the previous state key and then update
+ // the last start time and current state key.
+ mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+ mLastStartTime = timestamp;
+ updateCurrentStateKey(atomId, newState);
+}
+
int64_t OringDurationTracker::predictAnomalyTimestampNs(
const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
@@ -308,12 +350,13 @@
// The timestamp of the current bucket end.
const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
- // The past duration ns for the current bucket.
- int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+ // The past duration ns for the current bucket of the current stateKey.
+ int64_t currentStateBucketPastNs =
+ getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
// As we move into the future, old buckets get overwritten (so their old data is erased).
// Sum of past durations. Will change as we overwrite old buckets.
- int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+ int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
// The refractory period end timestamp for dimension mEventKey.
const int64_t refractoryPeriodEndNs =
@@ -372,7 +415,7 @@
mEventKey,
mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
} else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
- pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+ pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
}
}
@@ -382,7 +425,34 @@
void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
- fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+ fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+ auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+ if (it == mStateKeyDurationMap.end()) {
+ return 0;
+ } else {
+ return it->second.mDuration;
+ }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+ auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+ if (it == mStateKeyDurationMap.end()) {
+ return 0;
+ } else {
+ return it->second.mDurationFullBucket;
+ }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+ HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+ for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+ if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+ stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+ }
+ }
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index f44e327..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,10 +28,9 @@
public:
OringDurationTracker(const ConfigKey& key, const int64_t& id,
const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
- int conditionIndex,
- bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
- int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
- bool fullLink,
+ int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+ int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+ bool conditionSliced, bool fullLink,
const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -45,6 +44,9 @@
void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
void onConditionChanged(bool condition, const int64_t timestamp) override;
+ void onStateChanged(const int64_t timestamp, const int32_t atomId,
+ const FieldValue& newState) override;
+
bool flushCurrentBucket(
const int64_t& eventTimeNs,
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -56,6 +58,12 @@
const int64_t currentTimestamp) const override;
void dumpStates(FILE* out, bool verbose) const override;
+ int64_t getCurrentStateKeyDuration() const override;
+
+ int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+ void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
private:
// We don't need to keep track of individual durations. The information that's needed is:
// 1) which keys are started. We record the first start time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3810c48..0d0788e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -564,6 +564,33 @@
}
}
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+ ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+ return false;
+ }
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return false;
+ }
+ } else {
+ if (metric.state_link_size() > 0) {
+ ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+ return false;
+ }
+ }
+
+ // Check that all metric state links are a subset of dimensions_in_what fields.
+ std::vector<Matcher> dimensionsInWhat;
+ translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+ for (const auto& stateLink : metric.state_link()) {
+ if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+ return false;
+ }
+ }
+
unordered_map<int, shared_ptr<Activation>> eventActivationMap;
unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -575,7 +602,8 @@
sp<MetricProducer> durationMetric = new DurationMetricProducer(
key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
- currentTimeNs, eventActivationMap, eventDeactivationMap);
+ currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+ stateGroupMap);
allMetricProducers.push_back(durationMetric);
}
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index c45274e..ed98f50 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -103,12 +103,14 @@
message DurationMetricData {
optional DimensionsValue dimensions_in_what = 1;
- optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+ repeated StateValue slice_by_state = 6;
repeated DurationBucketInfo bucket_info = 3;
repeated DimensionsValue dimension_leaf_values_in_what = 4;
+ optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
}
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 83d9484..c7407bd 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -227,8 +227,12 @@
optional int64 condition = 3;
+ repeated int64 slice_by_state = 9;
+
repeated MetricConditionLink links = 4;
+ repeated MetricStateLink state_link = 10;
+
enum AggregationType {
SUM = 1;
@@ -238,9 +242,9 @@
optional FieldMatcher dimensions_in_what = 6;
- optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
-
optional TimeUnit bucket = 7;
+
+ optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
}
message GaugeMetric {
diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp
new file mode 100644
index 0000000..29adcd0
--- /dev/null
+++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * 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 "src/HashableDimensionKey.h"
+
+#include <gtest/gtest.h>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using android::util::ProtoReader;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Test that #containsLinkedStateValues returns false when the whatKey is
+ * smaller than the primaryKey.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) {
+ std::vector<Metric2State> mMetric2StateLinks;
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY;
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks,
+ UID_PROCESS_STATE_ATOM_ID));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when the linked values
+ * are not equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ FieldMatcher whatMatcher;
+ whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+ FieldMatcher* child11 = whatMatcher.add_child();
+ child11->set_field(1);
+
+ FieldMatcher stateMatcher;
+ stateMatcher.set_field(stateAtomId);
+ FieldMatcher* child21 = stateMatcher.add_child();
+ child21->set_field(1);
+
+ std::vector<Metric2State> mMetric2StateLinks;
+ Metric2State ms;
+ ms.stateAtomId = stateAtomId;
+ translateFieldMatcher(whatMatcher, &ms.metricFields);
+ translateFieldMatcher(stateMatcher, &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+
+ int32_t uid1 = 1000;
+ int32_t uid2 = 1001;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid2, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when there is no link
+ * between the key values.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ std::vector<Metric2State> mMetric2StateLinks;
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid1, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns true when the key values are
+ * linked and equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) {
+ int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+ FieldMatcher whatMatcher;
+ whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+ FieldMatcher* child11 = whatMatcher.add_child();
+ child11->set_field(1);
+
+ FieldMatcher stateMatcher;
+ stateMatcher.set_field(stateAtomId);
+ FieldMatcher* child21 = stateMatcher.add_child();
+ child21->set_field(1);
+
+ std::vector<Metric2State> mMetric2StateLinks;
+ Metric2State ms;
+ ms.stateAtomId = stateAtomId;
+ translateFieldMatcher(whatMatcher, &ms.metricFields);
+ translateFieldMatcher(stateMatcher, &ms.stateFields);
+ mMetric2StateLinks.push_back(ms);
+
+ int32_t uid1 = 1000;
+ HashableDimensionKey whatKey;
+ getOverlayKey(uid1, "package", &whatKey);
+ HashableDimensionKey primaryKey;
+ getUidProcessKey(uid1, &primaryKey);
+
+ EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index ae2a0f5..2659944 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -14,12 +14,13 @@
#include <gtest/gtest.h>
+#include <vector>
+
#include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
#include "src/stats_log_util.h"
#include "tests/statsd_test_util.h"
-#include <vector>
-
namespace android {
namespace os {
namespace statsd {
@@ -101,7 +102,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -183,7 +184,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
auto bucketInfo = data.bucket_info(0);
@@ -353,7 +354,7 @@
reports.reports(0).metrics(0).duration_metrics();
EXPECT_EQ(1, durationMetrics.data_size());
- auto data = durationMetrics.data(0);
+ DurationMetricData data = durationMetrics.data(0);
EXPECT_EQ(1, data.bucket_info_size());
auto bucketInfo = data.bucket_info(0);
@@ -434,7 +435,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate bucket info.
EXPECT_EQ(1, data.bucket_info_size());
@@ -533,7 +534,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -691,7 +692,7 @@
EXPECT_EQ(1, reports.reports(0).metrics_size());
EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
- auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
// Validate dimension value.
ValidateAttributionUidDimension(data.dimensions_in_what(),
util::WAKELOCK_STATE_CHANGED, appUid);
@@ -709,6 +710,734 @@
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos());
}
+TEST(DurationMetricE2eTest, TestWithSlicedState) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto screenState = CreateScreenState();
+ *config.add_state() = screenState;
+
+ // Create duration metric that slices by screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->add_slice_by_state(screenState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 9 10 (minutes)
+ |-----------------------------|-----------------------------|--
+ ON OFF ON (BatterySaverMode)
+ | | | (ScreenIsOnEvent)
+ | | (ScreenIsOffEvent)
+ | (ScreenDozeEvent)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 50 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 80 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 250 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary.
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 310 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:20
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer); // 6:10
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+ *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+ *config.add_predicate() = deviceUnpluggedPredicate;
+
+ auto screenState = CreateScreenState();
+ *config.add_state() = screenState;
+
+ // Create duration metric that has a condition and slices by screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->set_condition(deviceUnpluggedPredicate.id());
+ durationMetric->add_slice_by_state(screenState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 (minutes)
+ |---------------------------------------|------------------
+ ON OFF ON (BatterySaverMode)
+ T F T (DeviceUnpluggedPredicate)
+ | | | (ScreenIsOnEvent)
+ | | | (ScreenIsOffEvent)
+ | (ScreenDozeEvent)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 60 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 1:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 80 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:30
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 2:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 145 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:35
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 170 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 3:00
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_USB)); // 3:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 200 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 3:30
+ events.push_back(
+ CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC,
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE)); // 4:00
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 260 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 4:30
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary.
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC)); // 5:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 380 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 6:30
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+ *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+ auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+ *config.add_predicate() = batterySaverModePredicate;
+
+ auto screenStateWithMap = CreateScreenStateWithOnOffMap();
+ *config.add_state() = screenStateWithMap;
+
+ // Create duration metric that slices by mapped screen state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped"));
+ durationMetric->set_what(batterySaverModePredicate.id());
+ durationMetric->add_slice_by_state(screenStateWithMap.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+ /*
+ bucket #1 bucket #2
+ | 1 2 3 4 5 6 7 8 9 10 (minutes)
+ |-----------------------------|-----------------------------|--
+ ON OFF ON (BatterySaverMode)
+ ---------------------------------------------------------SCREEN_OFF events
+ | | (ScreenStateOffEvent = 1)
+ | (ScreenStateDozeEvent = 3)
+ | (ScreenStateDozeSuspendEvent = 4)
+ ---------------------------------------------------------SCREEN_ON events
+ | | | (ScreenStateOnEvent = 2)
+ | (ScreenStateVrEvent = 5)
+ | (ScreenStateOnSuspendEvent = 6)
+ */
+ // Initialize log events.
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 0:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC)); // 0:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 70 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 1:20
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 100 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE)); // 1:50
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 2:10
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 170 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_VR)); // 3:00
+ events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC)); // 3:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 250 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_OFF)); // 4:20
+ events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC)); // 4:50
+
+ // Bucket boundary 5:10.
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 320 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON)); // 5:30
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 390 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND)); // 6:40
+ events.push_back(CreateScreenStateChangedEvent(
+ bucketStartTimeNs + 430 * NS_PER_SEC,
+ android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND)); // 7:20
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+ EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto uidProcessState = CreateUidProcessState();
+ *config.add_state() = uidProcessState;
+
+ // Create duration metric that slices by uid process state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->add_slice_by_state(uidProcessState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // The state has only one primary field (uid).
+ auto stateLink = durationMetric->add_state_link();
+ stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+ auto fieldsInWhat = stateLink->mutable_fields_in_what();
+ *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ auto fieldsInState = stateLink->mutable_fields_in_state();
+ *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ // This config is rejected because the dimension in what fields are not a superset of the sliced
+ // state primary fields.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 0);
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
+ // Initialize config.
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+ *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+ auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+ *config.add_predicate() = holdingWakelockPredicate;
+
+ auto uidProcessState = CreateUidProcessState();
+ *config.add_state() = uidProcessState;
+
+ // Create duration metric that slices by uid process state.
+ auto durationMetric = config.add_duration_metric();
+ durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState"));
+ durationMetric->set_what(holdingWakelockPredicate.id());
+ durationMetric->add_slice_by_state(uidProcessState.id());
+ durationMetric->set_aggregation_type(DurationMetric::SUM);
+ durationMetric->set_bucket(FIVE_MINUTES);
+
+ // The metric is dimensioning by first uid of attribution node and tag.
+ *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions(
+ util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */});
+ // The state has only one primary field (uid).
+ auto stateLink = durationMetric->add_state_link();
+ stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+ auto fieldsInWhat = stateLink->mutable_fields_in_what();
+ *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+ auto fieldsInState = stateLink->mutable_fields_in_state();
+ *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+ // Initialize StatsLogProcessor.
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+ uint64_t bucketStartTimeNs = 10000000000; // 0:10
+ uint64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+ auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ EXPECT_TRUE(metricsManager->isActive());
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_TRUE(metricProducer->mIsActive);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+ // Check that StateTrackers were initialized correctly.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+ // Initialize log events.
+ int appUid1 = 1001;
+ int appUid2 = 1002;
+ std::vector<int> attributionUids1 = {appUid1};
+ std::vector<string> attributionTags1 = {"App1"};
+
+ std::vector<int> attributionUids2 = {appUid2};
+ std::vector<string> attributionTags2 = {"App2"};
+
+ std::vector<std::unique_ptr<LogEvent>> events;
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 10 * NS_PER_SEC, appUid1,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND)); // 0:20
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock1")); // 0:30
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock2")); // 0:35
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock1")); // 0:40
+ events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock2")); // 0:45
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 50 * NS_PER_SEC, appUid2,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:00
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 60 * NS_PER_SEC, appUid1,
+ android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND)); // 1:10
+ events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC,
+ attributionUids2, attributionTags2,
+ "wakelock1")); // 1:50
+ events.push_back(CreateUidProcessStateChangedEvent(
+ bucketStartTimeNs + 120 * NS_PER_SEC, appUid2,
+ android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE)); // 2:10
+ events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC,
+ attributionUids1, attributionTags1,
+ "wakelock2")); // 3:30
+
+ // Send log events to StatsLogProcessor.
+ for (auto& event : events) {
+ processor->OnLogEvent(event.get());
+ }
+
+ // Check dump report.
+ vector<uint8_t> buffer;
+ ConfigMetricsReportList reports;
+ processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC,
+ true /* include current partial bucket */, true, ADB_DUMP, FAST,
+ &buffer);
+ EXPECT_GT(buffer.size(), 0);
+ EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+ backfillDimensionPath(&reports);
+ backfillStringInReport(&reports);
+ backfillStartEndTimestamp(&reports);
+
+ EXPECT_EQ(1, reports.reports_size());
+ EXPECT_EQ(1, reports.reports(0).metrics_size());
+ EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+ EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+ DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(1);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(2);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(3);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(4);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(5);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(2, data.bucket_info_size());
+ EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+ EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(6);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(7);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+ "wakelock1");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+ data = reports.reports(0).metrics(0).duration_metrics().data(8);
+ ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+ "wakelock2");
+ EXPECT_EQ(1, data.slice_by_state_size());
+ EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+ EXPECT_TRUE(data.slice_by_state(0).has_value());
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+ data.slice_by_state(0).value());
+ EXPECT_EQ(1, data.bucket_info_size());
+ EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+ EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+ EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 100220b..d2f0f57 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -62,9 +62,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
// Event starts again. This would not change anything as it already starts.
@@ -97,9 +96,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
@@ -132,9 +130,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
// The event starts.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -172,9 +169,8 @@
int64_t bucketNum = 0;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- false, false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
// 2 starts
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -218,9 +214,8 @@
int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
int64_t metricId = 1;
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
- false, {});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ 0, bucketStartTimeNs, bucketSizeNs, true, false, {});
EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -267,9 +262,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -326,9 +321,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -408,9 +403,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
- true, false, {anomalyTracker});
+ MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 1cd7bdb..39d3919 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -61,9 +61,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -92,9 +92,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -124,9 +123,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey()); // overlapping wl
@@ -154,9 +152,8 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -198,9 +195,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
@@ -237,9 +234,9 @@
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
int64_t durationTimeNs = 2 * 1000;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
// condition to false; record duration 5n
@@ -275,9 +272,8 @@
int64_t bucketNum = 0;
int64_t eventStartTimeNs = bucketStartTimeNs + 1;
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -316,9 +312,9 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, true, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+ bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+ {anomalyTracker});
// Nothing in the past bucket.
tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -422,9 +418,8 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY,
- wizard, 1,
- true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+ OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
+ 1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
bucketSizeNs, true, false, {anomalyTracker});
int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
@@ -481,15 +476,15 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
- bucketSizeNs, false, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+ bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+ false, false, {anomalyTracker});
tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
EXPECT_TRUE(tracker.mStarted.empty());
- EXPECT_EQ(10LL, tracker.mDuration); // 10ns
+ EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration); // 10ns
EXPECT_EQ(0u, tracker.mStarted.size());
@@ -530,11 +525,11 @@
sp<AlarmMonitor> alarmMonitor;
sp<DurationAnomalyTracker> anomalyTracker =
new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
- OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
- true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
- bucketSizeNs, false, false, {anomalyTracker});
+ OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+ bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
+ false, {anomalyTracker});
- tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+ tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -544,13 +539,13 @@
EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
- tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+ tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
- tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+ tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
alarm = anomalyTracker->mAlarms.begin()->second;
EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a0e0095..a5b8e1c 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -105,63 +105,6 @@
}
// END: build event functions.
-// START: get primary key functions
-void getUidProcessKey(int uid, HashableDimensionKey* key) {
- int pos1[] = {1, 0, 0};
- Field field1(27 /* atom id */, pos1, 0 /* depth */);
- Value value1((int32_t)uid);
-
- key->addValue(FieldValue(field1, value1));
-}
-
-void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
- int pos1[] = {1, 0, 0};
- int pos2[] = {2, 0, 0};
-
- Field field1(59 /* atom id */, pos1, 0 /* depth */);
- Field field2(59 /* atom id */, pos2, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value2(packageName);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field2, value2));
-}
-
-void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
- int pos1[] = {1, 1, 1};
- int pos3[] = {2, 0, 0};
- int pos4[] = {3, 0, 0};
-
- Field field1(10 /* atom id */, pos1, 2 /* depth */);
-
- Field field3(10 /* atom id */, pos3, 0 /* depth */);
- Field field4(10 /* atom id */, pos4, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value3((int32_t)1 /*partial*/);
- Value value4(tag);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field3, value3));
- key->addValue(FieldValue(field4, value4));
-}
-
-void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
- int pos1[] = {1, 1, 1};
- int pos3[] = {2, 0, 0};
-
- Field field1(10 /* atom id */, pos1, 2 /* depth */);
- Field field3(10 /* atom id */, pos3, 0 /* depth */);
-
- Value value1((int32_t)uid);
- Value value3((int32_t)1 /*partial*/);
-
- key->addValue(FieldValue(field1, value1));
- key->addValue(FieldValue(field3, value3));
-}
-// END: get primary key functions
-
TEST(StateListenerTest, TestStateListenerWeakPointer) {
sp<TestStateListener> listener = new TestStateListener();
wp<TestStateListener> wListener = listener;
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 8c8836b..2f81c2d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -135,6 +135,27 @@
"BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
}
+AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name,
+ BatteryPluggedStateEnum state) {
+ AtomMatcher atom_matcher;
+ atom_matcher.set_id(StringToId(name));
+ auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+ simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED);
+ auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+ field_value_matcher->set_field(1); // State field.
+ field_value_matcher->set_eq_int(state);
+ return atom_matcher;
+}
+
+AtomMatcher CreateBatteryStateNoneMatcher() {
+ return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone",
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+}
+
+AtomMatcher CreateBatteryStateUsbMatcher() {
+ return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb",
+ BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+}
AtomMatcher CreateScreenStateChangedAtomMatcher(
const string& name, android::view::DisplayStateEnum state) {
@@ -234,6 +255,14 @@
return predicate;
}
+Predicate CreateDeviceUnpluggedPredicate() {
+ Predicate predicate;
+ predicate.set_id(StringToId("DeviceUnplugged"));
+ predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone"));
+ predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb"));
+ return predicate;
+}
+
Predicate CreateScreenIsOnPredicate() {
Predicate predicate;
predicate.set_id(StringToId("ScreenIsOn"));
@@ -410,6 +439,74 @@
return dimensions;
}
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+ const std::vector<Position>& positions,
+ const std::vector<int>& fields) {
+ FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions);
+
+ for (const int field : fields) {
+ dimensions.add_child()->set_field(field);
+ }
+ return dimensions;
+}
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 0, 0};
+ Field field1(27 /* atom id */, pos1, 0 /* depth */);
+ Value value1((int32_t)uid);
+
+ key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+ int pos1[] = {1, 0, 0};
+ int pos2[] = {2, 0, 0};
+
+ Field field1(59 /* atom id */, pos1, 0 /* depth */);
+ Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value2(packageName);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field2, value2));
+}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+ int pos4[] = {3, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+ Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+ Value value4(tag);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+ key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+}
+// END: get primary key functions
+
shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
int32_t value2) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -595,6 +692,23 @@
return logEvent;
}
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+ AStatsEvent* statsEvent = AStatsEvent_obtain();
+ AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+ AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+ AStatsEvent_writeInt32(statsEvent, state);
+ AStatsEvent_build(statsEvent);
+
+ size_t size;
+ uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+
+ std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+ logEvent->parseBuffer(buf, size);
+ AStatsEvent_release(statsEvent);
+ return logEvent;
+}
+
std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
@@ -964,6 +1078,22 @@
return static_cast<int64_t>(std::hash<std::string>()(str));
}
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+ const int uid, const string& tag) {
+ EXPECT_EQ(value.field(), atomId);
+ EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2);
+ // Attribution field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+ // Uid field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1);
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+ EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+ uid);
+ // Tag field.
+ EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3);
+ EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag);
+}
+
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 7c01755..715ba2b 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -68,6 +68,12 @@
// Create AtomMatcher proto for stopping battery save mode.
AtomMatcher CreateBatterySaverModeStopAtomMatcher();
+// Create AtomMatcher proto for battery state none mode.
+AtomMatcher CreateBatteryStateNoneMatcher();
+
+// Create AtomMatcher proto for battery state usb mode.
+AtomMatcher CreateBatteryStateUsbMatcher();
+
// Create AtomMatcher proto for process state changed.
AtomMatcher CreateUidProcessStateChangedAtomMatcher();
@@ -110,6 +116,9 @@
// Create Predicate proto for battery saver mode.
Predicate CreateBatterySaverModePredicate();
+// Create Predicate proto for device unplogged mode.
+Predicate CreateDeviceUnpluggedPredicate();
+
// Create Predicate proto for holding wakelock.
Predicate CreateHoldingWakelockPredicate();
@@ -164,6 +173,22 @@
FieldMatcher CreateAttributionUidDimensions(const int atomId,
const std::vector<Position>& positions);
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+ const std::vector<Position>& positions,
+ const std::vector<int>& fields);
+
+// START: get primary key functions
+// These functions take in atom field information and create FieldValues which are stored in the
+// given HashableDimensionKey.
+void getUidProcessKey(int uid, HashableDimensionKey* key);
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key);
+// END: get primary key functions
+
shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
int32_t value2);
@@ -213,6 +238,9 @@
// Create log event when battery saver stops.
std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
+// Create log event when battery state changes.
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+
// Create log event for app moving to background.
std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
@@ -277,6 +305,8 @@
int64_t StringToId(const string& str);
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+ const int uid, const string& tag);
void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
void ValidateAttributionUidAndTagDimension(
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2531c89..b6d519a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,8 +73,8 @@
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
-import android.window.WindowContainerToken;
import android.view.Surface;
+import android.window.WindowContainerToken;
import com.android.internal.app.LocalePicker;
import com.android.internal.app.procstats.ProcessStats;
@@ -3632,7 +3632,8 @@
* Set custom state data for this process. It will be included in the record of
* {@link ApplicationExitInfo} on the death of the current calling process; the new process
* of the app can retrieve this state data by calling
- * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
+ * {@link android.app.ApplicationExitInfo#getProcessStateSummary()
+ * ApplicationExitInfo.getProcessStateSummary()} on the record returned by
* {@link #getHistoricalProcessExitReasons}.
*
* <p> This would be useful for the calling app to save its stateful data: if it's
@@ -3657,7 +3658,7 @@
}
}
- /*
+ /**
* @return Whether or not the low memory kill will be reported in
* {@link #getHistoricalProcessExitReasons}.
*
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 0ecc003..cfe0aff 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -90,7 +90,8 @@
* {@link #REASON_SIGNALED} and {@link #getStatus} will return
* the value {@link android.system.OsConstants#SIGKILL}.
*
- * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+ * Application should use {@link android.app.ActivityManager#isLowMemoryKillReportSupported()
+ * ActivityManager.isLowMemoryKillReportSupported()} to check
* if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
* </p>
*/
@@ -523,7 +524,7 @@
return mReason;
}
- /*
+ /**
* The exit status argument of exit() if the application calls it, or the signal
* number if the application is signaled.
*/
@@ -538,7 +539,7 @@
return mImportance;
}
- /*
+ /**
* Last proportional set size of the memory that the process had used in kB.
*
* <p class="note">Note: This is the value from last sampling on the process,
@@ -562,7 +563,7 @@
/**
* The timestamp of the process's death, in milliseconds since the epoch,
- * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}.
+ * as returned by {@link java.lang.System#currentTimeMillis() System.currentTimeMillis()}.
*/
public @CurrentTimeMillisLong long getTimestamp() {
return mTimestamp;
@@ -586,8 +587,9 @@
}
/**
- * Return the state data set by calling {@link ActivityManager#setProcessStateSummary}
- * from the process before its death.
+ * Return the state data set by calling
+ * {@link android.app.ActivityManager#setProcessStateSummary(byte[])
+ * ActivityManager.setProcessStateSummary(byte[])} from the process before its death.
*
* @return The process-customized data
* @see ActivityManager#setProcessStateSummary(byte[])
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 7b45b72..ab86860 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -44,7 +44,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["INotificationManager\\.aidl"]
diff --git a/core/java/android/app/admin/DevicePolicyKeyguardService.java b/core/java/android/app/admin/DevicePolicyKeyguardService.java
index 5b7e387..db833ec 100644
--- a/core/java/android/app/admin/DevicePolicyKeyguardService.java
+++ b/core/java/android/app/admin/DevicePolicyKeyguardService.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
@@ -28,14 +29,16 @@
/**
* Client interface for providing the SystemUI with secondary lockscreen information.
*
- * <p>An implementation must be provided by the Profile Owner when
- * {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is set to true and the service must be
- * declared in the manifest as handling the action
+ * <p>An implementation must be provided by the default configured supervision app that is set as
+ * Profile Owner or Device Owner when {@link DevicePolicyManager#setSecondaryLockscreenEnabled} is
+ * set to true and the service must be declared in the manifest as handling the action
* {@link DevicePolicyManager#ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE}, otherwise the keyguard
* will fail to bind to the service and continue to unlock.
*
* @see DevicePolicyManager#setSecondaryLockscreenEnabled
+ * @hide
*/
+@SystemApi
public class DevicePolicyKeyguardService extends Service {
private static final String TAG = "DevicePolicyKeyguardService";
private IKeyguardCallback mCallback;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 10309a9..faf9ec6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2395,9 +2395,11 @@
public static final int MAX_PASSWORD_LENGTH = 16;
/**
- * Service Action: Service implemented by a device owner or profile owner to provide a
- * secondary lockscreen.
+ * Service Action: Service implemented by a device owner or profile owner supervision app to
+ * provide a secondary lockscreen.
+ * @hide
*/
+ @SystemApi
public static final String ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE =
"android.app.action.BIND_SECONDARY_LOCKSCREEN_SERVICE";
@@ -7001,6 +7003,22 @@
}
/**
+ * Returns the configured supervision app if it exists and is the device owner or policy owner.
+ * @hide
+ */
+ public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle user) {
+ if (mService != null) {
+ try {
+ return mService.getProfileOwnerOrDeviceOwnerSupervisionComponent(user);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* @hide
* @return the human readable name of the organisation associated with this DPM or {@code null}
* if one is not set.
@@ -8637,11 +8655,16 @@
* <p>Relevant interactions on the secondary lockscreen should be communicated back to the
* keyguard via {@link IKeyguardCallback}, such as when the screen is ready to be dismissed.
*
+ * <p>This API, and associated APIs, can only be called by the default supervision app when it
+ * is set as the device owner or profile owner.
+ *
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param enabled Whether or not the lockscreen needs to be shown.
* @throws SecurityException if {@code admin} is not a device or profile owner.
* @see #isSecondaryLockscreenEnabled
+ * @hide
**/
+ @SystemApi
public void setSecondaryLockscreenEnabled(@NonNull ComponentName admin, boolean enabled) {
throwIfParentInstance("setSecondaryLockscreenEnabled");
if (mService != null) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fc1eb0a..591a3f6 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -155,6 +155,7 @@
boolean setProfileOwner(in ComponentName who, String ownerName, int userHandle);
ComponentName getProfileOwnerAsUser(int userHandle);
ComponentName getProfileOwner(int userHandle);
+ ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(in UserHandle userHandle);
String getProfileOwnerName(int userHandle);
void setProfileEnabled(in ComponentName who);
void setProfileName(in ComponentName who, String profileName);
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 894ad55..be1817d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -322,7 +322,12 @@
private String className;
private int compatibleWidthLimitDp;
private int descriptionRes;
- private boolean enabled;
+
+ // Usually there's code to set this to true during parsing, but it's possible to install an APK
+ // targeting <R that doesn't contain an <application> tag. That code would be skipped and never
+ // assign this, so initialize this to true for those cases.
+ private boolean enabled = true;
+
private boolean crossProfile;
private int fullBackupContent;
private int iconRes;
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b52b437..a298c85 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -37,6 +38,7 @@
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
@@ -52,7 +54,6 @@
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Implements the internal IInputMethod interface to convert incoming calls
@@ -90,12 +91,13 @@
*
* <p>This field must be set and cleared only from the binder thread(s), where the system
* guarantees that {@link #bindInput(InputBinding)},
- * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+ * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
* {@link #unbindInput()} are called with the same order as the original calls
* in {@link com.android.server.inputmethod.InputMethodManagerService}.
* See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
*/
- AtomicBoolean mIsUnbindIssued = null;
+ @Nullable
+ CancellationGroup mCancellationGroup = null;
// NOTE: we should have a cache of these.
static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
@@ -187,11 +189,11 @@
final IBinder startInputToken = (IBinder) args.arg1;
final IInputContext inputContext = (IInputContext) args.arg2;
final EditorInfo info = (EditorInfo) args.arg3;
- final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+ final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
SomeArgs moreArgs = (SomeArgs) args.arg5;
final InputConnection ic = inputContext != null
? new InputConnectionWrapper(
- mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+ mTarget, inputContext, moreArgs.argi3, cancellationGroup)
: null;
info.makeCompatible(mTargetSdkVersion);
inputMethod.dispatchStartInputWithToken(
@@ -295,15 +297,15 @@
@BinderThread
@Override
public void bindInput(InputBinding binding) {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
Log.e(TAG, "bindInput must be paired with unbindInput.");
}
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
// This IInputContext is guaranteed to implement all the methods.
final int missingMethodFlags = 0;
InputConnection ic = new InputConnectionWrapper(mTarget,
IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
- mIsUnbindIssued);
+ mCancellationGroup);
InputBinding nu = new InputBinding(ic, binding);
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
}
@@ -311,10 +313,10 @@
@BinderThread
@Override
public void unbindInput() {
- if (mIsUnbindIssued != null) {
+ if (mCancellationGroup != null) {
// Signal the flag then forget it.
- mIsUnbindIssued.set(true);
- mIsUnbindIssued = null;
+ mCancellationGroup.cancelAll();
+ mCancellationGroup = null;
} else {
Log.e(TAG, "unbindInput must be paired with bindInput.");
}
@@ -326,16 +328,16 @@
public void startInput(IBinder startInputToken, IInputContext inputContext,
@InputConnectionInspector.MissingMethodFlags final int missingMethods,
EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
- if (mIsUnbindIssued == null) {
+ if (mCancellationGroup == null) {
Log.e(TAG, "startInput must be called after bindInput.");
- mIsUnbindIssued = new AtomicBoolean();
+ mCancellationGroup = new CancellationGroup();
}
SomeArgs args = SomeArgs.obtain();
args.argi1 = restarting ? 1 : 0;
args.argi2 = shouldPreRenderIme ? 1 : 0;
args.argi3 = missingMethods;
- mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
- DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
+ mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
+ inputContext, attribute, mCancellationGroup, args));
}
@BinderThread
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index ef138a0..dbb669b 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -39,6 +39,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IMultiClientInputMethodSession;
+import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.view.IInputContext;
@@ -46,7 +47,6 @@
import com.android.internal.view.InputConnectionWrapper;
import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
@@ -80,19 +80,19 @@
@Nullable
InputEventReceiver mInputEventReceiver;
- private final AtomicBoolean mFinished = new AtomicBoolean(false);
+ private final CancellationGroup mCancellationGroup = new CancellationGroup();
IInputMethodSession.Stub createIInputMethodSession() {
synchronized (mSessionLock) {
return new InputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
synchronized (mSessionLock) {
return new MultiClientInputMethodSessionImpl(
- mSessionLock, mCallbackImpl, mHandler, mFinished);
+ mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
}
}
@@ -105,7 +105,7 @@
mHandler = new Handler(looper, null, true);
mReadChannel = readChannel;
mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
- mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback);
+ mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
}
}
@@ -139,16 +139,17 @@
}
private static final class ImeInputEventReceiver extends InputEventReceiver {
- private final AtomicBoolean mFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
private final KeyEvent.DispatcherState mDispatcherState;
private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
- ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished,
+ ImeInputEventReceiver(InputChannel readChannel, Looper looper,
+ CancellationGroup cancellationGroupOnFinishSession,
KeyEvent.DispatcherState dispatcherState,
MultiClientInputMethodServiceDelegate.ClientCallback callback) {
super(readChannel, looper);
- mFinished = finished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
mDispatcherState = dispatcherState;
mClientCallback = callback;
mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
@@ -156,7 +157,7 @@
@Override
public void onInputEvent(InputEvent event) {
- if (mFinished.get()) {
+ if (mCancellationGroupOnFinishSession.isCanceled()) {
// The session has been finished.
finishInputEvent(event, false);
return;
@@ -187,14 +188,14 @@
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
- AtomicBoolean sessionFinished) {
+ CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -272,7 +273,7 @@
if (mCallbackImpl == null || mHandler == null) {
return;
}
- mSessionFinished.set(true);
+ mCancellationGroupOnFinishSession.cancelAll();
mHandler.sendMessage(PooledLambda.obtainMessage(
CallbackImpl::finishSession, mCallbackImpl));
mCallbackImpl = null;
@@ -311,14 +312,14 @@
private CallbackImpl mCallbackImpl;
@GuardedBy("mSessionLock")
private Handler mHandler;
- private final AtomicBoolean mSessionFinished;
+ private final CancellationGroup mCancellationGroupOnFinishSession;
MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
- Handler handler, AtomicBoolean sessionFinished) {
+ Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
mSessionLock = lock;
mCallbackImpl = callback;
mHandler = handler;
- mSessionFinished = sessionFinished;
+ mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
}
@Override
@@ -335,7 +336,7 @@
new WeakReference<>(null);
args.arg1 = (inputContext == null) ? null
: new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
- mSessionFinished);
+ mCancellationGroupOnFinishSession);
args.arg2 = editorInfo;
args.argi1 = controlFlags;
args.argi2 = softInputMode;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 9ff7ebe..73c6b3d 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -169,6 +169,7 @@
NET_CAPABILITY_OEM_PAID,
NET_CAPABILITY_MCX,
NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ NET_CAPABILITY_TEMPORARILY_NOT_METERED,
})
public @interface NetCapability { }
@@ -336,8 +337,16 @@
@SystemApi
public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
+ /**
+ * This capability will be set for networks that are generally metered, but are currently
+ * unmetered, e.g., because the user is in a particular area. This capability can be changed at
+ * any time. When it is removed, applications are responsible for stopping any data transfer
+ * that should not occur on a metered network.
+ */
+ public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
/**
* Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -353,7 +362,8 @@
| (1 << NET_CAPABILITY_FOREGROUND)
| (1 << NET_CAPABILITY_NOT_CONGESTED)
| (1 << NET_CAPABILITY_NOT_SUSPENDED)
- | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+ | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
+ | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
/**
* Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -424,6 +434,7 @@
*/
private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
(1 << NET_CAPABILITY_NOT_METERED)
+ | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
| (1 << NET_CAPABILITY_NOT_RESTRICTED)
| (1 << NET_CAPABILITY_NOT_VPN)
| (1 << NET_CAPABILITY_NOT_ROAMING)
@@ -1864,6 +1875,7 @@
case NET_CAPABILITY_OEM_PAID: return "OEM_PAID";
case NET_CAPABILITY_MCX: return "MCX";
case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+ case NET_CAPABILITY_TEMPORARILY_NOT_METERED: return "TEMPORARILY_NOT_METERED";
default: return Integer.toString(capability);
}
}
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..2e00c0c 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1274,6 +1274,8 @@
out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
} else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
+ enforceReadPermissionInner(documentUri, getCallingPackage(),
+ getCallingAttributionTag(), null);
return getDocumentMetadata(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index d0b8dbc..f089f48 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -13,7 +13,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["Toast\\.java"]
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index dca682e..c82ab6c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -124,6 +124,7 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.widget.GridLayoutManager;
import com.android.internal.widget.RecyclerView;
import com.android.internal.widget.ResolverDrawerLayout;
@@ -178,7 +179,7 @@
private static final boolean USE_PREDICTION_MANAGER_FOR_SHARE_ACTIVITIES = true;
// TODO(b/123088566) Share these in a better way.
private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
- public static final String LAUNCH_LOCATON_DIRECT_SHARE = "direct_share";
+ public static final String LAUNCH_LOCATION_DIRECT_SHARE = "direct_share";
private static final int APP_PREDICTION_SHARE_TARGET_QUERY_PACKAGE_LIMIT = 20;
public static final String APP_PREDICTION_INTENT_FILTER_KEY = "intent_filter";
@@ -194,6 +195,14 @@
public static final int TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER = 2;
public static final int TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE = 3;
+ public static final int SELECTION_TYPE_SERVICE = 1;
+ public static final int SELECTION_TYPE_APP = 2;
+ public static final int SELECTION_TYPE_STANDARD = 3;
+ public static final int SELECTION_TYPE_COPY = 4;
+
+ // statsd logger wrapper
+ protected ChooserActivityLogger mChooserActivityLogger;
+
private static final boolean USE_CHOOSER_TARGET_SERVICE_FOR_DIRECT_TARGETS = true;
@IntDef(flag = false, prefix = { "TARGET_TYPE_" }, value = {
@@ -226,7 +235,7 @@
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- false);
+ true);
private Bundle mReplacementExtras;
private IntentSender mChosenComponentSender;
@@ -270,9 +279,9 @@
// Starting at 1 since 0 is considered "undefined" for some of the database transformations
// of tron logs.
- private static final int CONTENT_PREVIEW_IMAGE = 1;
- private static final int CONTENT_PREVIEW_FILE = 2;
- private static final int CONTENT_PREVIEW_TEXT = 3;
+ protected static final int CONTENT_PREVIEW_IMAGE = 1;
+ protected static final int CONTENT_PREVIEW_FILE = 2;
+ protected static final int CONTENT_PREVIEW_TEXT = 3;
protected MetricsLogger mMetricsLogger;
private ContentPreviewCoordinator mPreviewCoord;
@@ -500,6 +509,9 @@
case CHOOSER_TARGET_SERVICE_WATCHDOG_MAX_TIMEOUT:
mMinTimeoutPassed = true;
+ if (!mServiceConnections.isEmpty()) {
+ getChooserActivityLogger().logSharesheetDirectLoadTimeout();
+ }
unbindRemainingServices();
maybeStopServiceRequestTimer();
break;
@@ -533,6 +545,7 @@
logDirectShareTargetReceived(
MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER);
sendVoiceChoicesIfNeeded();
+ getChooserActivityLogger().logSharesheetDirectLoadComplete();
break;
default:
@@ -544,6 +557,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
final long intentReceivedTime = System.currentTimeMillis();
+ getChooserActivityLogger().logSharesheetTriggered();
// This is the only place this value is being set. Effectively final.
mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
@@ -707,6 +721,8 @@
incrementNumSheetExpansions();
mWrittenOnce = true;
}
+ getChooserActivityLogger()
+ .logSharesheetExpansionChanged(isCollapsed);
}
});
}
@@ -715,6 +731,16 @@
Log.d(TAG, "System Time Cost is " + systemCost);
}
+ getChooserActivityLogger().logShareStarted(
+ FrameworkStatsLog.SHARESHEET_STARTED,
+ getReferrerPackageName(),
+ target.getType(),
+ initialIntents == null ? 0 : initialIntents.length,
+ mCallerChooserTargets == null ? 0 : mCallerChooserTargets.length,
+ isWorkProfile(),
+ findPreferredContentPreview(getTargetIntent(), getContentResolver()),
+ target.getAction()
+ );
mDirectShareShortcutInfoCache = new HashMap<>();
}
@@ -969,6 +995,10 @@
LogMaker targetLogMaker = new LogMaker(
MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET).setSubtype(1);
getMetricsLogger().write(targetLogMaker);
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_COPY,
+ "",
+ -1);
finish();
}
@@ -1644,18 +1674,33 @@
if (mCallerChooserTargets != null) {
numCallerProvided = mCallerChooserTargets.length;
}
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_SERVICE,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
case ChooserListAdapter.TARGET_CALLER:
case ChooserListAdapter.TARGET_STANDARD:
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET;
value -= currentListAdapter.getSelectableServiceTargetCount();
numCallerProvided = currentListAdapter.getCallerTargetCount();
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_APP,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
case ChooserListAdapter.TARGET_STANDARD_AZ:
// A-Z targets are unranked standard targets; we use -1 to mark that they
// are from the alphabetical pool.
value = -1;
cat = MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET;
+ getChooserActivityLogger().logShareTargetSelected(
+ SELECTION_TYPE_STANDARD,
+ targetInfo.getResolveInfo().activityInfo.processName,
+ value
+ );
break;
}
@@ -2131,7 +2176,7 @@
if (appTarget != null) {
directShareAppPredictor.notifyAppTargetEvent(
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(LAUNCH_LOCATION_DIRECT_SHARE)
.build());
}
}
@@ -2290,6 +2335,13 @@
return mMetricsLogger;
}
+ protected ChooserActivityLogger getChooserActivityLogger() {
+ if (mChooserActivityLogger == null) {
+ mChooserActivityLogger = new ChooserActivityLoggerImpl();
+ }
+ return mChooserActivityLogger;
+ }
+
public class ChooserListController extends ResolverListController {
public ChooserListController(Context context,
PackageManager pm,
@@ -2601,6 +2653,7 @@
// don't support direct share on low ram devices
if (ActivityManager.isLowRamDeviceStatic()) {
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
return;
}
@@ -2619,6 +2672,8 @@
queryTargetServices(chooserListAdapter);
}
+
+ getChooserActivityLogger().logSharesheetAppLoadComplete();
}
private void setupScrollListener() {
@@ -3777,4 +3832,9 @@
canvas.drawRoundRect(x, y, width, height, mRadius, mRadius, mRoundRectPaint);
}
}
+
+ @Override
+ protected void maybeLogProfileChange() {
+ getChooserActivityLogger().logShareheetProfileChanged();
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java
new file mode 100644
index 0000000..dc48244
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLogger.java
@@ -0,0 +1,215 @@
+/*
+ * 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.internal.app;
+
+import android.content.Intent;
+import android.provider.MediaStore;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Interface for writing Sharesheet atoms to statsd log.
+ * @hide
+ */
+public interface ChooserActivityLogger {
+ /** Logs a UiEventReported event for the system sharesheet completing initial start-up. */
+ void logShareStarted(int eventId, String packageName, String mimeType, int appProvidedDirect,
+ int appProvidedApp, boolean isWorkprofile, int previewType, String intent);
+
+ /** Logs a UiEventReported event for the system sharesheet when the user selects a target. */
+ void logShareTargetSelected(int targetType, String packageName, int positionPicked);
+
+ /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */
+ default void logSharesheetTriggered() {
+ log(SharesheetStandardEvent.SHARESHEET_TRIGGERED, getInstanceId());
+ }
+
+ /** Logs a UiEventReported event for the system sharesheet completing loading app targets. */
+ default void logSharesheetAppLoadComplete() {
+ log(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet completing loading service targets.
+ */
+ default void logSharesheetDirectLoadComplete() {
+ log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet timing out loading service targets.
+ */
+ default void logSharesheetDirectLoadTimeout() {
+ log(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for the system sharesheet switching
+ * between work and main profile.
+ */
+ default void logShareheetProfileChanged() {
+ log(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED, getInstanceId());
+ }
+
+ /** Logs a UiEventReported event for the system sharesheet getting expanded or collapsed. */
+ default void logSharesheetExpansionChanged(boolean isCollapsed) {
+ log(isCollapsed ? SharesheetStandardEvent.SHARESHEET_COLLAPSED :
+ SharesheetStandardEvent.SHARESHEET_EXPANDED, getInstanceId());
+ }
+
+ /**
+ * Logs a UiEventReported event for a given share activity
+ * @param event
+ * @param instanceId
+ */
+ void log(UiEventLogger.UiEventEnum event, InstanceId instanceId);
+
+ /**
+ *
+ * @return
+ */
+ InstanceId getInstanceId();
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetStartedEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Basic system Sharesheet has started and is visible.")
+ SHARE_STARTED(228);
+
+ private final int mId;
+ SharesheetStartedEvent(int id) {
+ mId = id;
+ }
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetTargetSelectedEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "User selected a service target.")
+ SHARESHEET_SERVICE_TARGET_SELECTED(232),
+ @UiEvent(doc = "User selected an app target.")
+ SHARESHEET_APP_TARGET_SELECTED(233),
+ @UiEvent(doc = "User selected a standard target.")
+ SHARESHEET_STANDARD_TARGET_SELECTED(234),
+ @UiEvent(doc = "User selected the copy target.")
+ SHARESHEET_COPY_TARGET_SELECTED(235);
+
+ private final int mId;
+ SharesheetTargetSelectedEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+
+ public static SharesheetTargetSelectedEvent fromTargetType(int targetType) {
+ switch(targetType) {
+ case ChooserActivity.SELECTION_TYPE_SERVICE:
+ return SHARESHEET_SERVICE_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_APP:
+ return SHARESHEET_APP_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_STANDARD:
+ return SHARESHEET_STANDARD_TARGET_SELECTED;
+ case ChooserActivity.SELECTION_TYPE_COPY:
+ return SHARESHEET_COPY_TARGET_SELECTED;
+ default:
+ return INVALID;
+ }
+ }
+ }
+
+ /**
+ * The UiEvent enums that this class can log.
+ */
+ enum SharesheetStandardEvent implements UiEventLogger.UiEventEnum {
+ INVALID(0),
+ @UiEvent(doc = "User clicked share.")
+ SHARESHEET_TRIGGERED(227),
+ @UiEvent(doc = "User changed from work to personal profile or vice versa.")
+ SHARESHEET_PROFILE_CHANGED(229),
+ @UiEvent(doc = "User expanded target list.")
+ SHARESHEET_EXPANDED(230),
+ @UiEvent(doc = "User collapsed target list.")
+ SHARESHEET_COLLAPSED(231),
+ @UiEvent(doc = "Sharesheet app targets is fully populated.")
+ SHARESHEET_APP_LOAD_COMPLETE(322),
+ @UiEvent(doc = "Sharesheet direct targets is fully populated.")
+ SHARESHEET_DIRECT_LOAD_COMPLETE(323),
+ @UiEvent(doc = "Sharesheet direct targets timed out.")
+ SHARESHEET_DIRECT_LOAD_TIMEOUT(324);
+
+ private final int mId;
+ SharesheetStandardEvent(int id) {
+ mId = id;
+ }
+ @Override public int getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * Returns the enum used in sharesheet started atom to indicate what preview type was used.
+ */
+ default int typeFromPreviewInt(int previewType) {
+ switch(previewType) {
+ case ChooserActivity.CONTENT_PREVIEW_IMAGE:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_IMAGE;
+ case ChooserActivity.CONTENT_PREVIEW_FILE:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE;
+ case ChooserActivity.CONTENT_PREVIEW_TEXT:
+ default:
+ return FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_TEXT;
+ }
+ }
+
+ /**
+ * Returns the enum used in sharesheet started atom to indicate what intent triggers the
+ * ChooserActivity.
+ */
+ default int typeFromIntentString(String intent) {
+ switch (intent) {
+ case Intent.ACTION_VIEW:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_VIEW;
+ case Intent.ACTION_EDIT:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_EDIT;
+ case Intent.ACTION_SEND:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND;
+ case Intent.ACTION_SENDTO:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO;
+ case Intent.ACTION_SEND_MULTIPLE:
+ return FrameworkStatsLog
+ .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SEND_MULTIPLE;
+ case MediaStore.ACTION_IMAGE_CAPTURE:
+ return FrameworkStatsLog
+ .SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_IMAGE_CAPTURE;
+ case Intent.ACTION_MAIN:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_MAIN;
+ default:
+ return FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_DEFAULT;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
new file mode 100644
index 0000000..48bdba3
--- /dev/null
+++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
+import com.android.internal.util.FrameworkStatsLog;
+
+/**
+ * Standard implementation of ChooserActivityLogger interface.
+ * @hide
+ */
+public class ChooserActivityLoggerImpl implements ChooserActivityLogger {
+ private static final int SHARESHEET_INSTANCE_ID_MAX = (1 << 13);
+
+ private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ // A small per-notification ID, used for statsd logging.
+ private InstanceId mInstanceId;
+ private static InstanceIdSequence sInstanceIdSequence;
+
+ @Override
+ public void logShareStarted(int eventId, String packageName, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ FrameworkStatsLog.write(FrameworkStatsLog.SHARESHEET_STARTED,
+ /* event_id = 1 */ SharesheetStartedEvent.SHARE_STARTED.getId(),
+ /* package_name = 2 */ packageName,
+ /* instance_id = 3 */ getInstanceId().getId(),
+ /* mime_type = 4 */ mimeType,
+ /* num_app_provided_direct_targets = 5 */ appProvidedDirect,
+ /* num_app_provided_app_targets = 6 */ appProvidedApp,
+ /* is_workprofile = 7 */ isWorkprofile,
+ /* previewType = 8 */ typeFromPreviewInt(previewType),
+ /* intentType = 9 */ typeFromIntentString(intent));
+ }
+
+ @Override
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED,
+ /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(),
+ /* package_name = 2 */ packageName,
+ /* instance_id = 3 */ getInstanceId().getId(),
+ /* position_picked = 4 */ positionPicked);
+ }
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+ mUiEventLogger.logWithInstanceId(
+ event,
+ 0,
+ null,
+ instanceId);
+ }
+
+ @Override
+ public InstanceId getInstanceId() {
+ if (mInstanceId == null) {
+ if (sInstanceIdSequence == null) {
+ sInstanceIdSequence = new InstanceIdSequence(SHARESHEET_INSTANCE_ID_MAX);
+ }
+ mInstanceId = sInstanceIdSequence.newInstanceId();
+ }
+ return mInstanceId;
+ }
+
+}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 0ea855a..0d90bbf 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -64,7 +64,7 @@
private boolean mAppendDirectShareEnabled = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
- false);
+ true);
private boolean mEnableStackedApps = true;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f088ab3..35253b6 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1578,6 +1578,7 @@
viewPager.setCurrentItem(1);
}
setupViewVisibilities();
+ maybeLogProfileChange();
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
.setInt(viewPager.getCurrentItem())
@@ -1998,4 +1999,6 @@
}
}
}
+
+ protected void maybeLogProfileChange() {}
}
diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java
new file mode 100644
index 0000000..09c9d12
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java
@@ -0,0 +1,348 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A utility class, which works as both a factory class of completable objects and a cancellation
+ * signal to cancel all the completable objects created by this object.
+ */
+public final class CancellationGroup {
+ private final Object mLock = new Object();
+
+ /**
+ * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to
+ * completable objects.
+ *
+ * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
+ */
+ @Nullable
+ @GuardedBy("mLock")
+ private ArrayList<CountDownLatch> mLatchList = null;
+
+ @GuardedBy("mLock")
+ private boolean mCanceled = false;
+
+ /**
+ * An inner class to consolidate completable object types supported by
+ * {@link CancellationGroup}.
+ */
+ public static final class Completable {
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private Completable() {
+ }
+
+ /**
+ * Base class of all the completable types supported by {@link CancellationGroup}.
+ */
+ protected static class ValueBase {
+ /**
+ * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}.
+ */
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ /**
+ * {@link CancellationGroup} to which this completable object belongs.
+ */
+ @NonNull
+ private final CancellationGroup mParentGroup;
+
+ /**
+ * Lock {@link Object} to guard complete operations within this class.
+ */
+ protected final Object mValueLock = new Object();
+
+ /**
+ * {@code true} after {@link #onComplete()} gets called.
+ */
+ @GuardedBy("mValueLock")
+ protected boolean mHasValue = false;
+
+ /**
+ * Base constructor.
+ *
+ * @param parentGroup {@link CancellationGroup} to which this completable object
+ * belongs.
+ */
+ protected ValueBase(@NonNull CancellationGroup parentGroup) {
+ mParentGroup = parentGroup;
+ }
+
+ /**
+ * @return {@link true} if {@link #onComplete()} gets called already.
+ */
+ @AnyThread
+ public boolean hasValue() {
+ synchronized (mValueLock) {
+ return mHasValue;
+ }
+ }
+
+ /**
+ * Called by subclasses to signale {@link #mLatch}.
+ */
+ @AnyThread
+ protected void onComplete() {
+ mLatch.countDown();
+ }
+
+ /**
+ * Blocks the calling thread until at least one of the following conditions is met.
+ *
+ * <p>
+ * <ol>
+ * <li>This object becomes ready to return the value.</li>
+ * <li>{@link CancellationGroup#cancelAll()} gets called.</li>
+ * <li>The given timeout period has passed.</li>
+ * </ol>
+ * </p>
+ *
+ * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
+ * Note that the return value of {@link #hasValue()} can change from {@code false} to
+ * {@code true} at any time, even after this methods finishes with returning
+ * {@code true}.</p>
+ *
+ * @param timeout length of the timeout.
+ * @param timeUnit unit of {@code timeout}.
+ * @return {@code false} if and only if the given timeout period has passed. Otherwise
+ * {@code true}.
+ */
+ @AnyThread
+ public boolean await(int timeout, @NonNull TimeUnit timeUnit) {
+ if (!mParentGroup.registerLatch(mLatch)) {
+ // Already canceled when this method gets called.
+ return false;
+ }
+ try {
+ return mLatch.await(timeout, timeUnit);
+ } catch (InterruptedException e) {
+ return true;
+ } finally {
+ mParentGroup.unregisterLatch(mLatch);
+ }
+ }
+ }
+
+ /**
+ * Completable object of integer primitive.
+ */
+ public static final class Int extends ValueBase {
+ @GuardedBy("mValueLock")
+ private int mValue = 0;
+
+ private Int(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+
+ /**
+ * Notify when a value is set to this completable object.
+ *
+ * @param value value to be set.
+ */
+ @AnyThread
+ void onComplete(int value) {
+ synchronized (mValueLock) {
+ if (mHasValue) {
+ throw new UnsupportedOperationException(
+ "onComplete() cannot be called multiple times");
+ }
+ mValue = value;
+ mHasValue = true;
+ }
+ onComplete();
+ }
+
+ /**
+ * @return value associated with this object.
+ * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+ * {@code false}.
+ */
+ @AnyThread
+ public int getValue() {
+ synchronized (mValueLock) {
+ if (!mHasValue) {
+ throw new UnsupportedOperationException(
+ "getValue() is allowed only if hasValue() returns true");
+ }
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Base class of completable object types.
+ *
+ * @param <T> type associated with this completable object.
+ */
+ public static class Values<T> extends ValueBase {
+ @GuardedBy("mValueLock")
+ @Nullable
+ private T mValue = null;
+
+ protected Values(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+
+ /**
+ * Notify when a value is set to this completable value object.
+ *
+ * @param value value to be set.
+ */
+ @AnyThread
+ void onComplete(@Nullable T value) {
+ synchronized (mValueLock) {
+ if (mHasValue) {
+ throw new UnsupportedOperationException(
+ "onComplete() cannot be called multiple times");
+ }
+ mValue = value;
+ mHasValue = true;
+ }
+ onComplete();
+ }
+
+ /**
+ * @return value associated with this object.
+ * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+ * {@code false}.
+ */
+ @AnyThread
+ @Nullable
+ public T getValue() {
+ synchronized (mValueLock) {
+ if (!mHasValue) {
+ throw new UnsupportedOperationException(
+ "getValue() is allowed only if hasValue() returns true");
+ }
+ return mValue;
+ }
+ }
+ }
+
+ /**
+ * Completable object of {@link java.lang.CharSequence}.
+ */
+ public static final class CharSequence extends Values<java.lang.CharSequence> {
+ private CharSequence(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+ }
+
+ /**
+ * Completable object of {@link android.view.inputmethod.ExtractedText}.
+ */
+ public static final class ExtractedText
+ extends Values<android.view.inputmethod.ExtractedText> {
+ private ExtractedText(@NonNull CancellationGroup factory) {
+ super(factory);
+ }
+ }
+ }
+
+ /**
+ * @return an instance of {@link Completable.Int} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.Int createCompletableInt() {
+ return new Completable.Int(this);
+ }
+
+ /**
+ * @return an instance of {@link Completable.CharSequence} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.CharSequence createCompletableCharSequence() {
+ return new Completable.CharSequence(this);
+ }
+
+ /**
+ * @return an instance of {@link Completable.ExtractedText} that is associated with this
+ * {@link CancellationGroup}.
+ */
+ @AnyThread
+ public Completable.ExtractedText createCompletableExtractedText() {
+ return new Completable.ExtractedText(this);
+ }
+
+ @AnyThread
+ private boolean registerLatch(@NonNull CountDownLatch latch) {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return false;
+ }
+ if (mLatchList == null) {
+ // Set the initial capacity to 1 with an assumption that usually there is up to 1
+ // on-going operation.
+ mLatchList = new ArrayList<>(1);
+ }
+ mLatchList.add(latch);
+ return true;
+ }
+ }
+
+ @AnyThread
+ private void unregisterLatch(@NonNull CountDownLatch latch) {
+ synchronized (mLock) {
+ if (mLatchList != null) {
+ mLatchList.remove(latch);
+ }
+ }
+ }
+
+ /**
+ * Cancel all the completable objects created from this {@link CancellationGroup}.
+ *
+ * <p>Secondary calls will be silently ignored.</p>
+ */
+ @AnyThread
+ public void cancelAll() {
+ synchronized (mLock) {
+ if (!mCanceled) {
+ mCanceled = true;
+ if (mLatchList != null) {
+ mLatchList.forEach(CountDownLatch::countDown);
+ mLatchList.clear();
+ mLatchList = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise.
+ */
+ @AnyThread
+ public boolean isCanceled() {
+ synchronized (mLock) {
+ return mCanceled;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
new file mode 100644
index 0000000..da56fd0
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.inputmethod;
+
+oneway interface ICharSequenceResultCallback {
+ void onResult(in CharSequence result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
new file mode 100644
index 0000000..b603f6a
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.view.inputmethod.ExtractedText;
+
+oneway interface IExtractedTextResultCallback {
+ void onResult(in ExtractedText result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
new file mode 100644
index 0000000..bc5ed0d
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.internal.inputmethod;
+
+oneway interface IIntResultCallback {
+ void onResult(int result);
+}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
new file mode 100644
index 0000000..44a8a83
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -0,0 +1,132 @@
+/*
+ * 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.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are
+ * associated with completable objects defined in {@link CancellationGroup.Completable}.
+ */
+public final class ResultCallbacks {
+
+ /**
+ * Not intended to be instantiated.
+ */
+ private ResultCallbacks() {
+ }
+
+ @AnyThread
+ @Nullable
+ private static <T> T unwrap(@NonNull AtomicReference<WeakReference<T>> atomicRef) {
+ final WeakReference<T> ref = atomicRef.getAndSet(null);
+ if (ref == null) {
+ // Double-call is guaranteed to be ignored here.
+ return null;
+ }
+ final T value = ref.get();
+ ref.clear();
+ return value;
+ }
+
+ /**
+ * Creates {@link IIntResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.Int} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.Int} to be set when receiving the result.
+ * @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter.
+ */
+ @AnyThread
+ public static IIntResultCallback.Stub of(@NonNull CancellationGroup.Completable.Int value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.Int>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IIntResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(int result) {
+ final CancellationGroup.Completable.Int value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+
+ /**
+ * Creates {@link ICharSequenceResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.CharSequence} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.CharSequence} to be set when receiving the
+ * result.
+ * @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static ICharSequenceResultCallback.Stub of(
+ @NonNull CancellationGroup.Completable.CharSequence value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.CharSequence>> atomicRef =
+ new AtomicReference<>(new WeakReference<>(value));
+
+ return new ICharSequenceResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(CharSequence result) {
+ final CancellationGroup.Completable.CharSequence value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+
+ /**
+ * Creates {@link IExtractedTextResultCallback.Stub} that is to set
+ * {@link CancellationGroup.Completable.ExtractedText} when receiving the result.
+ *
+ * @param value {@link CancellationGroup.Completable.ExtractedText} to be set when receiving the
+ * result.
+ * @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC
+ * parameter.
+ */
+ @AnyThread
+ public static IExtractedTextResultCallback.Stub of(
+ @NonNull CancellationGroup.Completable.ExtractedText value) {
+ final AtomicReference<WeakReference<CancellationGroup.Completable.ExtractedText>>
+ atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+ return new IExtractedTextResultCallback.Stub() {
+ @BinderThread
+ @Override
+ public void onResult(android.view.inputmethod.ExtractedText result) {
+ final CancellationGroup.Completable.ExtractedText value = unwrap(atomicRef);
+ if (value == null) {
+ return;
+ }
+ value.onComplete(result);
+ }
+ };
+ }
+}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 91ba0df..180ab08 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -85,7 +85,7 @@
}
@Override
- public void logWithInstanceId(UiEventLogger.UiEventEnum event, int uid, String packageName,
+ public void logWithInstanceId(UiEventEnum event, int uid, String packageName,
InstanceId instance) {
final int eventId = event.getId();
if (eventId > 0) {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6278d4a3..9257c6d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -29,6 +29,7 @@
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
@@ -36,6 +37,9 @@
import android.view.inputmethod.InputContentInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
import com.android.internal.os.SomeArgs;
public abstract class IInputConnectionWrapper extends IInputContext.Stub {
@@ -111,28 +115,31 @@
abstract protected boolean isActive();
- public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
- }
-
- public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
+ public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback));
}
- public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
+ public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback));
}
- public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+ public void getSelectedText(int flags, ICharSequenceResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback));
}
- public void getExtractedText(ExtractedTextRequest request,
- int flags, int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
- request, seq, callback));
+ public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
+ dispatchMessage(
+ mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
}
-
+
+ public void getExtractedText(ExtractedTextRequest request, int flags,
+ IExtractedTextResultCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = request;
+ args.arg2 = callback;
+ dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args));
+ }
+
public void commitText(CharSequence text, int newCursorPosition) {
dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
}
@@ -199,10 +206,9 @@
dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
}
- public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
- IInputContextCallback callback) {
- dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
- seq, callback));
+ public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback) {
+ dispatchMessage(mH.obtainMessage(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
+ 0 /* unused */, callback));
}
public void closeConnection() {
@@ -210,9 +216,12 @@
}
public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
- int seq, IInputContextCallback callback) {
- dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq,
- callback));
+ IIntResultCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = inputContentInfo;
+ args.arg2 = opts;
+ args.arg3 = callback;
+ dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args));
}
void dispatchMessage(Message msg) {
@@ -231,100 +240,97 @@
void executeMessage(Message msg) {
switch (msg.what) {
case DO_GET_TEXT_AFTER_CURSOR: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getTextAfterCursor(msg.arg1, msg.arg2);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
- callback.setTextAfterCursor(null, callbackSeq);
- return;
- }
- callback.setTextAfterCursor(ic.getTextAfterCursor(
- msg.arg1, msg.arg2), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getTextAfterCursor()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_TEXT_BEFORE_CURSOR: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getTextBeforeCursor(msg.arg1, msg.arg2);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
- callback.setTextBeforeCursor(null, callbackSeq);
- return;
- }
- callback.setTextBeforeCursor(ic.getTextBeforeCursor(
- msg.arg1, msg.arg2), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getTextBeforeCursor()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_SELECTED_TEXT: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final CharSequence result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getSelectedText on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getSelectedText(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getSelectedText on inactive InputConnection");
- callback.setSelectedText(null, callbackSeq);
- return;
- }
- callback.setSelectedText(ic.getSelectedText(
- msg.arg1), callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setSelectedText", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getSelectedText()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_CURSOR_CAPS_MODE: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final int result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+ result = 0;
+ } else {
+ result = ic.getCursorCapsMode(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
- callback.setCursorCapsMode(0, callbackSeq);
- return;
- }
- callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
- callbackSeq);
+ callback.onResult(result);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to getCursorCapsMode()."
+ + " result=" + result, e);
}
return;
}
case DO_GET_EXTRACTED_TEXT: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final SomeArgs args = (SomeArgs) msg.obj;
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
+ final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1;
+ final IExtractedTextResultCallback callback =
+ (IExtractedTextResultCallback) args.arg2;
+ final InputConnection ic = getInputConnection();
+ final ExtractedText result;
if (ic == null || !isActive()) {
Log.w(TAG, "getExtractedText on inactive InputConnection");
- callback.setExtractedText(null, callbackSeq);
- return;
+ result = null;
+ } else {
+ result = ic.getExtractedText(request, msg.arg1);
}
- callback.setExtractedText(ic.getExtractedText(
- (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq);
- } catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to return the result to getExtractedText()."
+ + " result=" + result, e);
+ }
} finally {
args.recycle();
}
@@ -494,22 +500,20 @@
return;
}
case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
- SomeArgs args = (SomeArgs)msg.obj;
+ final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+ final InputConnection ic = getInputConnection();
+ final boolean result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+ result = false;
+ } else {
+ result = ic.requestCursorUpdates(msg.arg1);
+ }
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
- if (ic == null || !isActive()) {
- Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
- callback.setRequestUpdateCursorAnchorInfoResult(false, callbackSeq);
- return;
- }
- callback.setRequestUpdateCursorAnchorInfoResult(
- ic.requestCursorUpdates(msg.arg1), callbackSeq);
+ callback.onResult(result ? 1 : 0);
} catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
- } finally {
- args.recycle();
+ Log.w(TAG, "Failed to return the result to requestCursorUpdates()."
+ + " result=" + result, e);
}
return;
}
@@ -547,26 +551,28 @@
final int flags = msg.arg1;
SomeArgs args = (SomeArgs) msg.obj;
try {
- final IInputContextCallback callback = (IInputContextCallback) args.arg6;
- final int callbackSeq = args.argi6;
- InputConnection ic = getInputConnection();
+ final IIntResultCallback callback = (IIntResultCallback) args.arg3;
+ final InputConnection ic = getInputConnection();
+ final boolean result;
if (ic == null || !isActive()) {
Log.w(TAG, "commitContent on inactive InputConnection");
- callback.setCommitContentResult(false, callbackSeq);
- return;
+ result = false;
+ } else {
+ final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+ if (inputContentInfo == null || !inputContentInfo.validate()) {
+ Log.w(TAG, "commitContent with invalid inputContentInfo="
+ + inputContentInfo);
+ result = false;
+ } else {
+ result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
+ }
}
- final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
- if (inputContentInfo == null || !inputContentInfo.validate()) {
- Log.w(TAG, "commitContent with invalid inputContentInfo="
- + inputContentInfo);
- callback.setCommitContentResult(false, callbackSeq);
- return;
+ try {
+ callback.onResult(result ? 1 : 0);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to return the result to commitContent()."
+ + " result=" + result, e);
}
- final boolean result =
- ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
- callback.setCommitContentResult(result, callbackSeq);
- } catch (RemoteException e) {
- Log.w(TAG, "Got RemoteException calling commitContent", e);
} finally {
args.recycle();
}
@@ -588,40 +594,6 @@
return mH.obtainMessage(what, 0, 0, arg1);
}
- Message obtainMessageISC(int what, int arg1, int callbackSeq, IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
- Message obtainMessageIISC(int what, int arg1, int arg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, arg2, args);
- }
-
- Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = objArg1;
- args.arg2 = objArg2;
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
- Message obtainMessageIOSC(int what, int arg1, Object arg2, int callbackSeq,
- IInputContextCallback callback) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = arg2;
- args.arg6 = callback;
- args.argi6 = callbackSeq;
- return mH.obtainMessage(what, arg1, 0, args);
- }
-
Message obtainMessageIO(int what, int arg1, Object arg2) {
return mH.obtainMessage(what, arg1, 0, arg2);
}
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..86f1293 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -23,7 +23,9 @@
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
-import com.android.internal.view.IInputContextCallback;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
/**
* Interface from an input method to the application, allowing it to perform
@@ -31,14 +33,14 @@
* {@hide}
*/
oneway interface IInputContext {
- void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback);
+ void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback);
- void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
-
- void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
-
- void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
- IInputContextCallback callback);
+ void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback);
+
+ void getCursorCapsMode(int reqModes, IIntResultCallback callback);
+
+ void getExtractedText(in ExtractedTextRequest request, int flags,
+ IExtractedTextResultCallback callback);
void deleteSurroundingText(int beforeLength, int afterLength);
void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
@@ -71,11 +73,10 @@
void setComposingRegion(int start, int end);
- void getSelectedText(int flags, int seq, IInputContextCallback callback);
+ void getSelectedText(int flags, ICharSequenceResultCallback callback);
- void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
- IInputContextCallback callback);
+ void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback);
- void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
- IInputContextCallback callback);
+ void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts,
+ IIntResultCallback callback);
}
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
deleted file mode 100644
index 0f40a83..0000000
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2008 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.internal.view;
-
-import android.view.inputmethod.ExtractedText;
-
-/**
- * {@hide}
- */
-oneway interface IInputContextCallback {
- void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
- void setTextAfterCursor(CharSequence textAfterCursor, int seq);
- void setCursorCapsMode(int capsMode, int seq);
- void setExtractedText(in ExtractedText extractedText, int seq);
- void setSelectedText(CharSequence selectedText, int seq);
- void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
- void setCommitContentResult(boolean result, int seq);
-}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a41048c..0bf5234 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -17,14 +17,12 @@
package com.android.internal.view;
import android.annotation.AnyThread;
-import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
@@ -36,10 +34,15 @@
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputContentInfo;
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.ResultCallbacks;
+
import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
public class InputConnectionWrapper implements InputConnection {
+ private static final String TAG = "InputConnectionWrapper";
+
private static final int MAX_WAIT_TIME_MILLIS = 2000;
private final IInputContext mIInputContext;
@NonNull
@@ -49,257 +52,94 @@
private final int mMissingMethods;
/**
- * {@code true} if the system already decided to take away IME focus from the target app. This
- * can be signaled even when the corresponding signal is in the task queue and
- * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+ * Signaled when the system decided to take away IME focus from the target app.
+ *
+ * <p>This is expected to be signaled immediately when the IME process receives
+ * {@link IInputMethod#unbindInput()}.</p>
*/
@NonNull
- private final AtomicBoolean mIsUnbindIssued;
-
- static class InputContextCallback extends IInputContextCallback.Stub {
- private static final String TAG = "InputConnectionWrapper.ICC";
- public int mSeq;
- public boolean mHaveValue;
- public CharSequence mTextBeforeCursor;
- public CharSequence mTextAfterCursor;
- public CharSequence mSelectedText;
- public ExtractedText mExtractedText;
- public int mCursorCapsMode;
- public boolean mRequestUpdateCursorAnchorInfoResult;
- public boolean mCommitContentResult;
-
- // A 'pool' of one InputContextCallback. Each ICW request will attempt to gain
- // exclusive access to this object.
- private static InputContextCallback sInstance = new InputContextCallback();
- private static int sSequenceNumber = 1;
-
- /**
- * Returns an InputContextCallback object that is guaranteed not to be in use by
- * any other thread. The returned object's 'have value' flag is cleared and its expected
- * sequence number is set to a new integer. We use a sequence number so that replies that
- * occur after a timeout has expired are not interpreted as replies to a later request.
- */
- @UnsupportedAppUsage
- @AnyThread
- private static InputContextCallback getInstance() {
- synchronized (InputContextCallback.class) {
- // Return sInstance if it's non-null, otherwise construct a new callback
- InputContextCallback callback;
- if (sInstance != null) {
- callback = sInstance;
- sInstance = null;
-
- // Reset the callback
- callback.mHaveValue = false;
- } else {
- callback = new InputContextCallback();
- }
-
- // Set the sequence number
- callback.mSeq = sSequenceNumber++;
- return callback;
- }
- }
-
- /**
- * Makes the given InputContextCallback available for use in the future.
- */
- @UnsupportedAppUsage
- @AnyThread
- private void dispose() {
- synchronized (InputContextCallback.class) {
- // If sInstance is non-null, just let this object be garbage-collected
- if (sInstance == null) {
- // Allow any objects being held to be gc'ed
- mTextAfterCursor = null;
- mTextBeforeCursor = null;
- mExtractedText = null;
- sInstance = this;
- }
- }
- }
-
- @BinderThread
- public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mTextBeforeCursor = textBeforeCursor;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setTextBeforeCursor, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mTextAfterCursor = textAfterCursor;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setTextAfterCursor, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setSelectedText(CharSequence selectedText, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mSelectedText = selectedText;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setSelectedText, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setCursorCapsMode(int capsMode, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mCursorCapsMode = capsMode;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCursorCapsMode, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setExtractedText(ExtractedText extractedText, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mExtractedText = extractedText;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setExtractedText, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mRequestUpdateCursorAnchorInfoResult = result;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCursorAnchorInfoRequestResult, ignoring.");
- }
- }
- }
-
- @BinderThread
- public void setCommitContentResult(boolean result, int seq) {
- synchronized (this) {
- if (seq == mSeq) {
- mCommitContentResult = result;
- mHaveValue = true;
- notifyAll();
- } else {
- Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
- + ") in setCommitContentResult, ignoring.");
- }
- }
- }
-
- /**
- * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
- *
- * <p>The caller must be synchronized on this callback object.
- */
- @AnyThread
- void waitForResultLocked() {
- long startTime = SystemClock.uptimeMillis();
- long endTime = startTime + MAX_WAIT_TIME_MILLIS;
-
- while (!mHaveValue) {
- long remainingTime = endTime - SystemClock.uptimeMillis();
- if (remainingTime <= 0) {
- Log.w(TAG, "Timed out waiting on IInputContextCallback");
- return;
- }
- try {
- wait(remainingTime);
- } catch (InterruptedException e) {
- }
- }
- }
- }
+ private final CancellationGroup mCancellationGroup;
public InputConnectionWrapper(
@NonNull WeakReference<AbstractInputMethodService> inputMethodService,
- IInputContext inputContext, @MissingMethodFlags final int missingMethods,
- @NonNull AtomicBoolean isUnbindIssued) {
+ IInputContext inputContext, @MissingMethodFlags int missingMethods,
+ @NonNull CancellationGroup cancellationGroup) {
mInputMethodService = inputMethodService;
mIInputContext = inputContext;
mMissingMethods = missingMethods;
- mIsUnbindIssued = isUnbindIssued;
+ mCancellationGroup = cancellationGroup;
+ }
+
+ @AnyThread
+ private static void logInternal(@Nullable String methodName, boolean timedOut,
+ @Nullable Object defaultValue) {
+ if (timedOut) {
+ Log.w(TAG, methodName + " didn't respond in " + MAX_WAIT_TIME_MILLIS + " msec."
+ + " Returning default: " + defaultValue);
+ } else {
+ Log.w(TAG, methodName + " was canceled before complete. Returning default: "
+ + defaultValue);
+ }
+ }
+
+ @AnyThread
+ private static int getResultOrZero(@NonNull CancellationGroup.Completable.Int value,
+ @NonNull String methodName) {
+ final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS);
+ if (value.hasValue()) {
+ return value.getValue();
+ }
+ logInternal(methodName, timedOut, 0);
+ return 0;
+ }
+
+ @AnyThread
+ @Nullable
+ private static <T> T getResultOrNull(@NonNull CancellationGroup.Completable.Values<T> value,
+ @NonNull String methodName) {
+ final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS, TimeUnit.MILLISECONDS);
+ if (value.hasValue()) {
+ return value.getValue();
+ }
+ logInternal(methodName, timedOut, null);
+ return null;
}
@AnyThread
public CharSequence getTextAfterCursor(int length, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mTextAfterCursor;
- }
- }
- callback.dispose();
+ mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getTextAfterCursor()");
}
@AnyThread
public CharSequence getTextBeforeCursor(int length, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mTextBeforeCursor;
- }
- }
- callback.dispose();
+ mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getTextBeforeCursor()");
}
@AnyThread
public CharSequence getSelectedText(int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
@@ -307,67 +147,46 @@
// This method is not implemented.
return null;
}
- CharSequence value = null;
+ final CancellationGroup.Completable.CharSequence value =
+ mCancellationGroup.createCompletableCharSequence();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getSelectedText(flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mSelectedText;
- }
- }
- callback.dispose();
+ mIInputContext.getSelectedText(flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getSelectedText()");
}
@AnyThread
public int getCursorCapsMode(int reqModes) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return 0;
}
- int value = 0;
+ final CancellationGroup.Completable.Int value =
+ mCancellationGroup.createCompletableInt();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mCursorCapsMode;
- }
- }
- callback.dispose();
+ mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value));
} catch (RemoteException e) {
return 0;
}
- return value;
+ return getResultOrZero(value, "getCursorCapsMode()");
}
@AnyThread
public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return null;
}
- ExtractedText value = null;
+ final CancellationGroup.Completable.ExtractedText value =
+ mCancellationGroup.createCompletableExtractedText();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- value = callback.mExtractedText;
- }
- }
- callback.dispose();
+ mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value));
} catch (RemoteException e) {
return null;
}
- return value;
+ return getResultOrNull(value, "getExtractedText()");
}
@AnyThread
@@ -563,29 +382,22 @@
@AnyThread
public boolean requestCursorUpdates(int cursorUpdateMode) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return false;
}
- boolean result = false;
if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
// This method is not implemented.
return false;
}
+ final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
try {
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- result = callback.mRequestUpdateCursorAnchorInfoResult;
- }
- }
- callback.dispose();
+ mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode,
+ ResultCallbacks.of(value));
} catch (RemoteException e) {
return false;
}
- return result;
+ return getResultOrZero(value, "requestUpdateCursorAnchorInfo()") != 0;
}
@AnyThread
@@ -601,38 +413,31 @@
@AnyThread
public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
- if (mIsUnbindIssued.get()) {
+ if (mCancellationGroup.isCanceled()) {
return false;
}
- boolean result = false;
if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
// This method is not implemented.
return false;
}
- try {
- if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
- final AbstractInputMethodService inputMethodService = mInputMethodService.get();
- if (inputMethodService == null) {
- // This basically should not happen, because it's the the caller of this method.
- return false;
- }
- inputMethodService.exposeContent(inputContentInfo, this);
- }
- InputContextCallback callback = InputContextCallback.getInstance();
- mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
- synchronized (callback) {
- callback.waitForResultLocked();
- if (callback.mHaveValue) {
- result = callback.mCommitContentResult;
- }
+ if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+ final AbstractInputMethodService inputMethodService = mInputMethodService.get();
+ if (inputMethodService == null) {
+ // This basically should not happen, because it's the caller of this method.
+ return false;
}
- callback.dispose();
+ inputMethodService.exposeContent(inputContentInfo, this);
+ }
+
+ final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
+ try {
+ mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value));
} catch (RemoteException e) {
return false;
}
- return result;
+ return getResultOrZero(value, "commitContent()") != 0;
}
@AnyThread
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
new file mode 100644
index 0000000..374edb8
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java
@@ -0,0 +1,123 @@
+/*
+ * 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.internal.app;
+
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChooserActivityLoggerFake implements ChooserActivityLogger {
+ static class CallRecord {
+ // shared fields between all logs
+ public int atomId;
+ public String packageName;
+ public InstanceId instanceId;
+
+ // generic log field
+ public UiEventLogger.UiEventEnum event;
+
+ // share started fields
+ public String mimeType;
+ public int appProvidedDirect;
+ public int appProvidedApp;
+ public boolean isWorkprofile;
+ public int previewType;
+ public String intent;
+
+ // share completed fields
+ public int targetType;
+ public int positionPicked;
+
+ CallRecord(int atomId, UiEventLogger.UiEventEnum eventId,
+ String packageName, InstanceId instanceId) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.event = eventId;
+ }
+
+ CallRecord(int atomId, String packageName, InstanceId instanceId, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.mimeType = mimeType;
+ this.appProvidedDirect = appProvidedDirect;
+ this.appProvidedApp = appProvidedApp;
+ this.isWorkprofile = isWorkprofile;
+ this.previewType = previewType;
+ this.intent = intent;
+ }
+
+ CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType,
+ int positionPicked) {
+ this.atomId = atomId;
+ this.packageName = packageName;
+ this.instanceId = instanceId;
+ this.targetType = targetType;
+ this.positionPicked = positionPicked;
+ }
+
+ }
+ private List<CallRecord> mCalls = new ArrayList<>();
+
+ public int numCalls() {
+ return mCalls.size();
+ }
+
+ List<CallRecord> getCalls() {
+ return mCalls;
+ }
+
+ CallRecord get(int index) {
+ return mCalls.get(index);
+ }
+
+ UiEventLogger.UiEventEnum event(int index) {
+ return mCalls.get(index).event;
+ }
+
+ @Override
+ public void logShareStarted(int eventId, String packageName, String mimeType,
+ int appProvidedDirect, int appProvidedApp, boolean isWorkprofile, int previewType,
+ String intent) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.SHARESHEET_STARTED, packageName,
+ getInstanceId(), mimeType, appProvidedDirect, appProvidedApp, isWorkprofile,
+ previewType, intent));
+ }
+
+ @Override
+ public void logShareTargetSelected(int targetType, String packageName, int positionPicked) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(),
+ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked));
+ }
+
+ @Override
+ public void log(UiEventLogger.UiEventEnum event, InstanceId instanceId) {
+ mCalls.add(new CallRecord(FrameworkStatsLog.UI_EVENT_REPORTED,
+ event, "", instanceId));
+ }
+
+ @Override
+ public InstanceId getInstanceId() {
+ return InstanceId.fakeInstanceId(-1);
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 812e2a6..74ca2036 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -67,6 +67,7 @@
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.service.chooser.ChooserTarget;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -75,8 +76,10 @@
import com.android.internal.R;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.FrameworkStatsLog;
import org.junit.Before;
import org.junit.Ignore;
@@ -140,6 +143,10 @@
public void cleanOverrideData() {
sOverrides.reset();
sOverrides.createPackageManager = mPackageManagerOverride;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.APPEND_DIRECT_SHARE_ENABLED,
+ Boolean.toString(false),
+ true /* makeDefault*/);
}
@Test
@@ -988,7 +995,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
@@ -1060,7 +1067,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
@@ -1148,7 +1155,7 @@
serviceTargets,
TARGET_TYPE_CHOOSER_TARGET,
directShareToShortcutInfos,
- null)
+ List.of())
);
// Thread.sleep shouldn't be a thing in an integration test but it's
// necessary here because of the way the code is structured
@@ -1430,6 +1437,251 @@
.check(matches(isDisplayed()));
}
+ @Test
+ public void testAppTargetLogging() throws InterruptedException {
+ Intent sendIntent = createSendTextIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ assertThat(activity.getAdapter().getCount(), is(2));
+ onView(withId(R.id.profile_button)).check(doesNotExist());
+
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
+ onView(withText(toChoose.activityInfo.name))
+ .perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_APP_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_APP_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testDirectTargetLogging() throws InterruptedException {
+ Intent sendIntent = createSendTextIntent();
+ // We need app targets for direct targets to get displayed
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ // Create direct share target
+ List<ChooserTarget> serviceTargets = createDirectShareTargets(1,
+ resolvedComponentInfos.get(0).getResolveInfoAt(0).activityInfo.packageName);
+ ResolveInfo ri = ResolverDataProvider.createResolveInfo(3, 0);
+
+ // Start activity
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+
+ // Insert the direct share target
+ Map<ChooserTarget, ShortcutInfo> directShareToShortcutInfos = new HashMap<>();
+ directShareToShortcutInfos.put(serviceTargets.get(0), null);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> activity.getAdapter().addServiceResults(
+ activity.createTestDisplayResolveInfo(sendIntent,
+ ri,
+ "testLabel",
+ "testInfo",
+ sendIntent,
+ /* resolveInfoPresentationGetter */ null),
+ serviceTargets,
+ TARGET_TYPE_CHOOSER_TARGET,
+ directShareToShortcutInfos,
+ null)
+ );
+ // Thread.sleep shouldn't be a thing in an integration test but it's
+ // necessary here because of the way the code is structured
+ // TODO: restructure the tests b/129870719
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ assertThat("Chooser should have 3 targets (2 apps, 1 direct)",
+ activity.getAdapter().getCount(), is(3));
+ assertThat("Chooser should have exactly one selectable direct target",
+ activity.getAdapter().getSelectableServiceTargetCount(), is(1));
+ assertThat("The resolver info must match the resolver info used to create the target",
+ activity.getAdapter().getItem(0).getResolveInfo(), is(ri));
+
+ // Click on the direct target
+ String name = serviceTargets.get(0).getTitle().toString();
+ onView(withText(name))
+ .perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testCopyTextToClipboardLogging() throws Exception {
+ Intent sendIntent = createSendTextIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+
+ final ChooserWrapperActivity activity = mActivityRule
+ .launchActivity(Intent.createChooser(sendIntent, null));
+ waitForIdle();
+
+ onView(withId(R.id.chooser_copy_button)).check(matches(isDisplayed()));
+ onView(withId(R.id.chooser_copy_button)).perform(click());
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(6));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("text/plain"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth and fifth are just artifacts of test set-up
+ // sixth one should be ranking atom with SHARESHEET_COPY_TARGET_SELECTED event
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.RANKING_SELECTED));
+ assertThat(logger.get(5).targetType,
+ is(ChooserActivityLogger
+ .SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()));
+ }
+
+ @Test
+ public void testSwitchProfileLogging() throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ onView(withText(R.string.resolver_personal_tab)).perform(click());
+ waitForIdle();
+
+ ChooserActivityLoggerFake logger =
+ (ChooserActivityLoggerFake) activity.getChooserActivityLogger();
+ assertThat(logger.numCalls(), is(8));
+ // first one should be SHARESHEET_TRIGGERED uievent
+ assertThat(logger.get(0).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(0).event.getId(),
+ is(ChooserActivityLogger.SharesheetStandardEvent.SHARESHEET_TRIGGERED.getId()));
+ // second one should be SHARESHEET_STARTED event
+ assertThat(logger.get(1).atomId, is(FrameworkStatsLog.SHARESHEET_STARTED));
+ assertThat(logger.get(1).intent, is(Intent.ACTION_SEND));
+ assertThat(logger.get(1).mimeType, is("TestType"));
+ assertThat(logger.get(1).packageName, is("com.android.frameworks.coretests"));
+ assertThat(logger.get(1).appProvidedApp, is(0));
+ assertThat(logger.get(1).appProvidedDirect, is(0));
+ assertThat(logger.get(1).isWorkprofile, is(false));
+ assertThat(logger.get(1).previewType, is(3));
+ // third one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(2).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(2).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // fourth one is artifact of test setup
+ // fifth one is switch to work profile
+ assertThat(logger.get(4).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(4).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+ // sixth one should be SHARESHEET_APP_LOAD_COMPLETE uievent
+ assertThat(logger.get(5).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(5).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE.getId()));
+ // seventh one is artifact of test setup
+ // eigth one is switch to work profile
+ assertThat(logger.get(7).atomId, is(FrameworkStatsLog.UI_EVENT_REPORTED));
+ assertThat(logger.get(7).event.getId(),
+ is(ChooserActivityLogger
+ .SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED.getId()));
+ }
+
private Intent createSendTextIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 363551b..5b83f95 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -146,6 +146,11 @@
}
@Override
+ protected ChooserActivityLogger getChooserActivityLogger() {
+ return sOverrides.chooserActivityLogger;
+ }
+
+ @Override
public Cursor queryResolver(ContentResolver resolver, Uri uri) {
if (sOverrides.resolverCursor != null) {
return sOverrides.resolverCursor;
@@ -205,6 +210,7 @@
public boolean resolverForceException;
public Bitmap previewThumbnail;
public MetricsLogger metricsLogger;
+ public ChooserActivityLogger chooserActivityLogger;
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
@@ -223,6 +229,7 @@
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
metricsLogger = mock(MetricsLogger.class);
+ chooserActivityLogger = new ChooserActivityLoggerFake();
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index afd82ac..43cc4f2 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -50,10 +50,8 @@
}
SkAlphaType ImageDecoder::getOutAlphaType() const {
- // While an SkBitmap may want to use kOpaque_SkAlphaType for a performance
- // optimization, this class just outputs raw pixels. Using either
- // premultiplication choice has no effect on decoding an opaque encoded image.
- return mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
+ return opaque() ? kOpaque_SkAlphaType
+ : mUnpremultipliedRequired ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
}
bool ImageDecoder::setTargetSize(int width, int height) {
@@ -82,8 +80,7 @@
SkISize targetSize = { width, height }, decodeSize = targetSize;
int sampleSize = mCodec->computeSampleSize(&decodeSize);
- if (decodeSize != targetSize && mUnpremultipliedRequired
- && !mCodec->getInfo().isOpaque()) {
+ if (decodeSize != targetSize && mUnpremultipliedRequired && !opaque()) {
return false;
}
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index b6b3785..41d939b 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -305,9 +305,6 @@
}
SkImageInfo bitmapInfo = decoder->getOutputInfo();
- if (decoder->opaque()) {
- bitmapInfo = bitmapInfo.makeAlphaType(kOpaque_SkAlphaType);
- }
if (asAlphaMask && colorType == kGray_8_SkColorType) {
bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
}
diff --git a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
index 2e9381a..5f84587 100644
--- a/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
+++ b/packages/CtsShim/build/shim/AndroidManifestTargetPSdk.xml
@@ -14,9 +14,7 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.apk.cts.shim"
- android:versionCode="2"
- android:versionName="2.0" >
+ package="com.android.cts.ctsshim">
- <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P" />
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="P.123" />
</manifest>
\ No newline at end of file
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 5d363f3..68bd407 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -351,9 +351,6 @@
@Override
public void onProgress(float progress) {
synchronized (mLock) {
- if (progress == 0) {
- trackInfoWithIdLocked();
- }
checkProgressUpdatedLocked(mInfo, (int) progress);
}
}
@@ -365,7 +362,6 @@
@Override
public void onError(@BugreportErrorCode int errorCode) {
synchronized (mLock) {
- trackInfoWithIdLocked();
stopProgressLocked(mInfo.id);
}
Log.e(TAG, "Bugreport API callback onError() errorCode = " + errorCode);
@@ -382,10 +378,10 @@
}
/**
- * Reads bugreport id and links it to the bugreport info to track the bugreport's
- * progress/completion/error. id is incremented in dumpstate code. This function is called
- * when dumpstate calls one of the callback functions (onProgress, onFinished, onError)
- * after the id has been incremented.
+ * Reads bugreport id and links it to the bugreport info to track a bugreport that is in
+ * process. id is incremented in the dumpstate code.
+ * We do not track a bugreport if there is already a bugreport with the same id being
+ * tracked.
*/
@GuardedBy("mLock")
private void trackInfoWithIdLocked() {
@@ -408,7 +404,6 @@
sendRemoteBugreportFinishedBroadcast(mContext, bugreportFilePath,
mInfo.bugreportFile);
} else {
- trackInfoWithIdLocked();
cleanupOldFiles(MIN_KEEP_COUNT, MIN_KEEP_AGE, mBugreportsDir);
final Intent intent = new Intent(INTENT_BUGREPORT_FINISHED);
intent.putExtra(EXTRA_BUGREPORT, bugreportFilePath);
@@ -638,8 +633,11 @@
BugreportCallbackImpl bugreportCallback = new BugreportCallbackImpl(info);
try {
- mBugreportManager.startBugreport(bugreportFd, screenshotFd,
- new BugreportParams(bugreportType), executor, bugreportCallback);
+ synchronized (mLock) {
+ mBugreportManager.startBugreport(bugreportFd, screenshotFd,
+ new BugreportParams(bugreportType), executor, bugreportCallback);
+ bugreportCallback.trackInfoWithIdLocked();
+ }
} catch (RuntimeException e) {
Log.i(TAG, "Error in generating bugreports: ", e);
// The binder call didn't go through successfully, so need to close the fds.
@@ -756,7 +754,7 @@
!= (info.lastProgress.intValue() / LOG_PROGRESS_STEP))) {
Log.d(TAG, "Progress #" + info.id + ": " + percentageText);
}
- info.lastProgress = new AtomicInteger(progress);
+ info.lastProgress.set(progress);
sendForegroundabledNotification(info.id, builder.build());
}
@@ -1025,7 +1023,7 @@
}
Log.d(TAG, "Bugreport finished with title: " + info.getTitle()
+ " and shareDescription: " + info.shareDescription);
- info.finished = new AtomicBoolean(true);
+ info.finished.set(true);
synchronized (mLock) {
// Stop running on foreground, otherwise share notification cannot be dismissed.
@@ -1809,18 +1807,18 @@
* Current value of progress (in percentage) of the bugreport generation as
* displayed by the UI.
*/
- AtomicInteger progress = new AtomicInteger(0);
+ final AtomicInteger progress = new AtomicInteger(0);
/**
* Last value of progress (in percentage) of the bugreport generation for which
* system notification was updated.
*/
- AtomicInteger lastProgress = new AtomicInteger(0);
+ final AtomicInteger lastProgress = new AtomicInteger(0);
/**
* Time of the last progress update.
*/
- AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
+ final AtomicLong lastUpdate = new AtomicLong(System.currentTimeMillis());
/**
* Time of the last progress update when Parcel was created.
@@ -1840,7 +1838,7 @@
/**
* Whether dumpstate sent an intent informing it has finished.
*/
- AtomicBoolean finished = new AtomicBoolean(false);
+ final AtomicBoolean finished = new AtomicBoolean(false);
/**
* Whether the details entries have been added to the bugreport yet.
@@ -2075,8 +2073,8 @@
initialName = in.readString();
title = in.readString();
description = in.readString();
- progress = new AtomicInteger(in.readInt());
- lastUpdate = new AtomicLong(in.readLong());
+ progress.set(in.readInt());
+ lastUpdate.set(in.readLong());
formattedLastUpdate = in.readString();
bugreportFile = readFile(in);
@@ -2085,7 +2083,7 @@
screenshotFiles.add(readFile(in));
}
- finished = new AtomicBoolean(in.readInt() == 1);
+ finished.set(in.readInt() == 1);
screenshotCounter = in.readInt();
shareDescription = in.readString();
shareTitle = in.readString();
@@ -2157,8 +2155,8 @@
+ ") from " + info.progress.intValue() + " to " + progress);
}
}
- info.progress = new AtomicInteger(progress);
- info.lastUpdate = new AtomicLong(System.currentTimeMillis());
+ info.progress.set(progress);
+ info.lastUpdate.set(System.currentTimeMillis());
updateProgress(info);
}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 23be78b..a9e5fa9 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -477,6 +477,7 @@
<dimen name="qs_tile_height">106dp</dimen>
<dimen name="qs_tile_layout_margin_side">6dp</dimen>
<dimen name="qs_tile_margin_horizontal">18dp</dimen>
+ <dimen name="qs_tile_margin_horizontal_two_line">2dp</dimen>
<dimen name="qs_tile_margin_vertical">24dp</dimen>
<dimen name="qs_tile_margin_top_bottom">12dp</dimen>
<dimen name="qs_tile_margin_top_bottom_negative">-12dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7cbc840..57e3f14 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -971,13 +971,16 @@
boolean changed = false;
if (enabled && (oldIntent == null)) {
- ComponentName poComponent = mDevicePolicyManager.getProfileOwnerAsUser(userId);
- if (poComponent == null) {
- Log.e(TAG, "No profile owner found for User " + userId);
+ ComponentName supervisorComponent =
+ mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ UserHandle.of(userId));
+ if (supervisorComponent == null) {
+ Log.e(TAG, "No Profile Owner or Device Owner supervision app found for User "
+ + userId);
} else {
Intent intent =
new Intent(DevicePolicyManager.ACTION_BIND_SECONDARY_LOCKSCREEN_SERVICE)
- .setPackage(poComponent.getPackageName());
+ .setPackage(supervisorComponent.getPackageName());
ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent, 0);
if (resolveInfo != null && resolveInfo.serviceInfo != null) {
Intent launchIntent =
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 5911805..a7c4043 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -37,6 +37,8 @@
import android.hardware.SensorPrivacyManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
import android.os.BatteryStats;
import android.os.Handler;
import android.os.PowerManager;
@@ -193,6 +195,12 @@
return LocalBluetoothManager.create(context, bgHandler, UserHandle.ALL);
}
+ @Provides
+ @Singleton
+ static NetworkScoreManager provideNetworkScoreManager(Context context) {
+ return context.getSystemService(NetworkScoreManager.class);
+ }
+
@Singleton
@Provides
static NotificationManager provideNotificationManager(Context context) {
@@ -273,6 +281,12 @@
return (WallpaperManager) context.getSystemService(Context.WALLPAPER_SERVICE);
}
+ @Provides
+ @Singleton
+ static WifiManager provideWifiManager(Context context) {
+ return context.getSystemService(WifiManager.class);
+ }
+
@Singleton
@Provides
static WindowManager provideWindowManager(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..448531a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -25,13 +25,18 @@
class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
+ companion object {
+ private const val NUM_LINES = 2
+ }
+
protected val mRecords = ArrayList<QSPanel.TileRecord>()
private var _listening = false
private var smallTileSize = 0
private val twoLineHeight
- get() = smallTileSize * 2 + cellMarginVertical
+ get() = smallTileSize * NUM_LINES + cellMarginVertical * (NUM_LINES - 1)
private var cellMarginHorizontal = 0
private var cellMarginVertical = 0
+ private var tilesToShow = 0
init {
isFocusableInTouchMode = true
@@ -68,7 +73,7 @@
override fun updateResources(): Boolean {
with(mContext.resources) {
smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+ cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal_two_line)
cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
}
requestLayout()
@@ -83,11 +88,12 @@
}
}
- override fun getNumVisibleTiles() = mRecords.size
+ override fun getNumVisibleTiles() = tilesToShow
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateResources()
+ postInvalidate()
}
override fun onFinishInflate() {
@@ -95,39 +101,58 @@
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- var previousView: View = this
- var tiles = 0
mRecords.forEach {
- val tileView = it.tileView
- if (tileView.visibility != View.GONE) {
- tileView.updateAccessibilityOrder(previousView)
- previousView = tileView
- tiles++
- tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
- }
+ it.tileView.measure(exactly(smallTileSize), exactly(smallTileSize))
}
val height = twoLineHeight
- val columns = tiles / 2
- val width = paddingStart + paddingEnd +
- columns * smallTileSize +
- (columns - 1) * cellMarginHorizontal
- setMeasuredDimension(width, height)
+ setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height)
}
- override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
- val tiles = mRecords.filter { it.tileView.visibility != View.GONE }
- tiles.forEachIndexed {
- index, tile ->
- val column = index % (tiles.size / 2)
- val left = getLeftForColumn(column)
- val top = if (index < tiles.size / 2) 0 else getTopBottomRow()
- tile.tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+ private fun calculateMaxColumns(availableWidth: Int): Int {
+ if (smallTileSize + cellMarginHorizontal == 0) {
+ return 0
+ } else {
+ return (availableWidth - smallTileSize) / (smallTileSize + cellMarginHorizontal) + 1
}
}
- private fun getLeftForColumn(column: Int) = column * (smallTileSize + cellMarginHorizontal)
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ val availableWidth = r - l - paddingLeft - paddingRight
+ val maxColumns = calculateMaxColumns(availableWidth)
+ val actualColumns = Math.min(maxColumns, mRecords.size / NUM_LINES)
+ if (actualColumns == 0) {
+ // No tileSize or horizontal margin
+ return
+ }
+ tilesToShow = actualColumns * NUM_LINES
+
+ val interTileSpace = if (actualColumns <= 2) {
+ // Extra "column" of padding to be distributed on each end
+ (availableWidth - actualColumns * smallTileSize) / actualColumns
+ } else {
+ (availableWidth - actualColumns * smallTileSize) / (actualColumns - 1)
+ }
+
+ for (index in 0 until mRecords.size) {
+ val tileView = mRecords[index].tileView
+ if (index >= tilesToShow) {
+ tileView.visibility = View.GONE
+ } else {
+ tileView.visibility = View.VISIBLE
+ if (index > 0) tileView.updateAccessibilityOrder(mRecords[index - 1].tileView)
+ val column = index % actualColumns
+ val left = getLeftForColumn(column, interTileSpace, actualColumns <= 2)
+ val top = if (index < actualColumns) 0 else getTopBottomRow()
+ tileView.layout(left, top, left + smallTileSize, top + smallTileSize)
+ }
+ }
+ }
+
+ private fun getLeftForColumn(column: Int, interSpace: Int, sideMargin: Boolean): Int {
+ return (if (sideMargin) interSpace / 2 else 0) + column * (smallTileSize + interSpace)
+ }
private fun getTopBottomRow() = smallTileSize + cellMarginVertical
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index d7322a0..fd44f04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
import com.android.systemui.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
@@ -68,6 +69,7 @@
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
private var shadeExpansion = 0f
+ private var ignoreShadeBlurUntilHidden: Boolean = false
@VisibleForTesting
var shadeSpring = DepthAnimation()
@VisibleForTesting
@@ -83,6 +85,26 @@
}
/**
+ * When launching an app from the shade, the animations progress should affect how blurry the
+ * shade is, overriding the expansion amount.
+ */
+ var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
+ set(value) {
+ field = value
+ if (value != null) {
+ scheduleUpdate()
+ return
+ }
+
+ if (shadeSpring.radius == 0) {
+ return
+ }
+ ignoreShadeBlurUntilHidden = true
+ shadeSpring.animateTo(0)
+ shadeSpring.finishIfRunning()
+ }
+
+ /**
* Blur radius of the wake-up animation on this frame.
*/
private var wakeAndUnlockBlurRadius = 0
@@ -99,9 +121,19 @@
val updateBlurCallback = Choreographer.FrameCallback {
updateScheduled = false
- var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius)
- shadeRadius = (shadeRadius * (1f - brightnessMirrorSpring.ratio)).toInt()
- val blur = max(shadeRadius, globalActionsSpring.radius)
+ var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
+ shadeRadius *= 1f - brightnessMirrorSpring.ratio
+ val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
+ shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
+
+ if (ignoreShadeBlurUntilHidden) {
+ if (shadeRadius == 0f) {
+ ignoreShadeBlurUntilHidden = false
+ } else {
+ shadeRadius = 0f
+ }
+ }
+ val blur = max(shadeRadius.toInt(), globalActionsSpring.radius)
blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
try {
wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -187,7 +219,6 @@
if (statusBarStateController.state == StatusBarState.SHADE) {
newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
}
-
shadeSpring.animateTo(newBlur)
}
@@ -212,6 +243,9 @@
it.println("globalActionsRadius: ${globalActionsSpring.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
+ it.println("notificationLaunchAnimationProgress: " +
+ "${notificationLaunchAnimationParams?.linearProgress}")
+ it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 7c06157..6aef6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,6 +34,7 @@
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -57,6 +58,7 @@
private final NotificationListContainer mNotificationContainer;
private final float mWindowCornerRadius;
private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ private final NotificationShadeDepthController mDepthController;
private Callback mCallback;
private final Runnable mTimeoutRunnable = () -> {
setAnimationPending(false);
@@ -70,9 +72,11 @@
NotificationShadeWindowViewController notificationShadeWindowViewController,
Callback callback,
NotificationPanelViewController notificationPanel,
+ NotificationShadeDepthController depthController,
NotificationListContainer container) {
mNotificationPanel = notificationPanel;
mNotificationContainer = container;
+ mDepthController = depthController;
mNotificationShadeWindowViewController = notificationShadeWindowViewController;
mCallback = callback;
mWindowCornerRadius = ScreenDecorationsUtils
@@ -212,7 +216,7 @@
mWindowCornerRadius, progress);
applyParamsToWindow(primary);
applyParamsToNotification(mParams);
- applyParamsToNotificationList(mParams);
+ applyParamsToNotificationShade(mParams);
}
});
anim.addListener(new AnimatorListenerAdapter() {
@@ -256,14 +260,15 @@
if (!running) {
mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
applyParamsToNotification(null);
- applyParamsToNotificationList(null);
+ applyParamsToNotificationShade(null);
}
}
- private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+ private void applyParamsToNotificationShade(ExpandAnimationParameters params) {
mNotificationContainer.applyExpandAnimationParams(params);
mNotificationPanel.applyExpandAnimationParams(params);
+ mDepthController.setNotificationLaunchAnimationParams(params);
}
private void applyParamsToNotification(ExpandAnimationParameters params) {
@@ -295,7 +300,7 @@
};
public static class ExpandAnimationParameters {
- float linearProgress;
+ public float linearProgress;
int[] startPosition;
float startTranslationZ;
int left;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 19b5f5c..255c2ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -148,6 +149,7 @@
private KeyguardBypassController mBypassController;
private LayoutListener mLayoutListener;
private RowContentBindStage mRowContentBindStage;
+ private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
@@ -1145,7 +1147,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext);
+ mMenuRow = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
if (existed) {
createMenu();
}
@@ -1582,7 +1584,6 @@
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
initDimens();
@@ -1603,9 +1604,13 @@
NotificationMediaManager notificationMediaManager,
OnAppOpsClickListener onAppOpsClickListener,
FalsingManager falsingManager,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mAppName = appName;
- if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+ if (mMenuRow == null) {
+ mMenuRow = new NotificationMenuRow(mContext, peopleNotificationIdentifier);
+ }
+ if (mMenuRow.getMenuView() != null) {
mMenuRow.setAppName(mAppName);
}
mLogger = logger;
@@ -1620,6 +1625,7 @@
setAppOpsOnClickListener(onAppOpsClickListener);
mFalsingManager = falsingManager;
mStatusbarStateController = statusBarStateController;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
private void initDimens() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 39fab43..8b3d06b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -28,6 +28,7 @@
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.dagger.AppName;
import com.android.systemui.statusbar.notification.row.dagger.DismissRunnable;
import com.android.systemui.statusbar.notification.row.dagger.NotificationKey;
@@ -68,6 +69,7 @@
private Runnable mOnDismissRunnable;
private final FalsingManager mFalsingManager;
private final boolean mAllowLongPress;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
public ExpandableNotificationRowController(ExpandableNotificationRow view,
@@ -83,7 +85,8 @@
NotificationRowContentBinder.InflationCallback inflationCallback,
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager) {
+ @DismissRunnable Runnable onDismissRunnable, FalsingManager falsingManager,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
mActivatableNotificationViewController = activatableNotificationViewController;
mMediaManager = mediaManager;
@@ -104,6 +107,7 @@
mOnAppOpsClickListener = mNotificationGutsManager::openGuts;
mAllowLongPress = allowLongPress;
mFalsingManager = falsingManager;
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
/**
@@ -123,7 +127,8 @@
mMediaManager,
mOnAppOpsClickListener,
mFalsingManager,
- mStatusBarStateController
+ mStatusBarStateController,
+ mPeopleNotificationIdentifier
);
mView.setOnDismissRunnable(mOnDismissRunnable);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 212cba6..83a6eb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -45,8 +45,9 @@
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
-import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -114,12 +115,16 @@
private boolean mIsUserTouching;
- public NotificationMenuRow(Context context) {
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+ public NotificationMenuRow(Context context,
+ PeopleNotificationIdentifier peopleNotificationIdentifier) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
}
@Override
@@ -260,7 +265,10 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ NotificationEntry entry = mParent.getEntry();
+ int personNotifType = mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry.getSbn(), entry.getRanking());
+ if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) {
mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index f0cce46..fa55b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1235,6 +1235,7 @@
// Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(
mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
+ mNotificationShadeDepthControllerLazy.get(),
(NotificationListContainer) mStackScroller);
// TODO: inject this.
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 99709402..6e5f8a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -33,6 +33,7 @@
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -173,10 +174,13 @@
@Inject
public NetworkControllerImpl(Context context, @Background Looper bgLooper,
DeviceProvisionedController deviceProvisionedController,
- BroadcastDispatcher broadcastDispatcher) {
- this(context, (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE),
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE),
- (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
+ BroadcastDispatcher broadcastDispatcher, ConnectivityManager connectivityManager,
+ TelephonyManager telephonyManager, WifiManager wifiManager,
+ NetworkScoreManager networkScoreManager) {
+ this(context, connectivityManager,
+ telephonyManager,
+ wifiManager,
+ networkScoreManager,
SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
new CallbackHandler(),
new AccessPointControllerImpl(context),
@@ -190,6 +194,7 @@
@VisibleForTesting
NetworkControllerImpl(Context context, ConnectivityManager connectivityManager,
TelephonyManager telephonyManager, WifiManager wifiManager,
+ NetworkScoreManager networkScoreManager,
SubscriptionManager subManager, Config config, Looper bgLooper,
CallbackHandler callbackHandler,
AccessPointControllerImpl accessPointController,
@@ -229,7 +234,7 @@
}
});
mWifiSignalController = new WifiSignalController(mContext, mHasMobileDataFeature,
- mCallbackHandler, this, mWifiManager);
+ mCallbackHandler, this, mWifiManager, mConnectivityManager, networkScoreManager);
mEthernetSignalController = new EthernetSignalController(mContext, mCallbackHandler, this);
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 c2fc18f..b258fd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -42,13 +42,10 @@
public WifiSignalController(Context context, boolean hasMobileDataFeature,
CallbackHandler callbackHandler, NetworkControllerImpl networkController,
- WifiManager wifiManager) {
+ WifiManager wifiManager, ConnectivityManager connectivityManager,
+ NetworkScoreManager networkScoreManager) {
super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
callbackHandler, networkController);
- NetworkScoreManager networkScoreManager =
- context.getSystemService(NetworkScoreManager.class);
- ConnectivityManager connectivityManager =
- context.getSystemService(ConnectivityManager.class);
mWifiTracker = new WifiStatusTracker(mContext, wifiManager, networkScoreManager,
connectivityManager, this::handleStatusUpdated);
mWifiTracker.setListening(true);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index eecde72..73f9d8a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -590,7 +590,8 @@
when(mPackageManager.resolveService(any(Intent.class), eq(0))).thenReturn(resolveInfo);
when(mDevicePolicyManager.isSecondaryLockscreenEnabled(eq(UserHandle.of(user))))
.thenReturn(true, false);
- when(mDevicePolicyManager.getProfileOwnerAsUser(user))
+ when(mDevicePolicyManager.getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ UserHandle.of(user)))
.thenReturn(new ComponentName(packageName, cls));
// Initially null.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 1e3636b..6b7a3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.NotificationShadeWindowController
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -43,6 +44,7 @@
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -128,6 +130,23 @@
}
@Test
+ fun updateBlurCallback_setsBlur_whenExpanded() {
+ `when`(shadeSpring.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(maxBlur))
+ }
+
+ @Test
+ fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
+ `when`(shadeSpring.radius).thenReturn(maxBlur)
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 1f
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(0))
+ }
+
+ @Test
fun updateBlurCallback_invalidWindow() {
doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
.setWallpaperZoomOut(any(), anyFloat())
@@ -159,6 +178,24 @@
verify(blurUtils).applyBlur(safeEq(viewRootImpl), eq(0))
}
+ @Test
+ fun setNotificationLaunchAnimationParams_schedulesFrame() {
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 0.5f
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ verify(choreographer).postFrameCallback(
+ eq(notificationShadeDepthController.updateBlurCallback))
+ }
+
+ @Test
+ fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
+ val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ animProgress.linearProgress = 0.5f
+ `when`(shadeSpring.radius).thenReturn(0)
+ notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+ verify(shadeSpring, never()).animateTo(anyInt(), any())
+ }
+
private fun <T : Any> safeEq(value: T): T {
return eq(value) ?: value
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index a07cfc3..cdef49d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -19,7 +19,6 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +30,7 @@
import android.view.View;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -39,8 +39,12 @@
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -48,14 +52,22 @@
public class ActivityLaunchAnimatorTest extends SysuiTestCase {
private ActivityLaunchAnimator mLaunchAnimator;
- private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class);
- private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock(
- NotificationShadeWindowViewController.class);
- private NotificationShadeWindowView mNotificationShadeWindowView = mock(
- NotificationShadeWindowView.class);
- private NotificationListContainer mNotificationContainer
- = mock(NotificationListContainer.class);
- private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
+ @Mock
+ private ActivityLaunchAnimator.Callback mCallback;
+ @Mock
+ private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Mock
+ private NotificationListContainer mNotificationContainer;
+ @Mock
+ private ExpandableNotificationRow mRow;
+ @Mock
+ private NotificationShadeDepthController mNotificationShadeDepthController;
+ @Mock
+ private NotificationPanelViewController mNotificationPanelViewController;
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
@Before
public void setUp() throws Exception {
@@ -66,7 +78,8 @@
mLaunchAnimator = new ActivityLaunchAnimator(
mNotificationShadeWindowViewController,
mCallback,
- mock(NotificationPanelViewController.class),
+ mNotificationPanelViewController,
+ mNotificationShadeDepthController,
mNotificationContainer);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index bdd7a2e..a5d8a84 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -131,6 +131,7 @@
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
+ @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
private StatusBarNotification mSbn;
private NotificationListenerService.RankingMap mRankingMap;
@@ -239,7 +240,8 @@
mGutsManager,
true,
null,
- mFalsingManager
+ mFalsingManager,
+ mPeopleNotificationIdentifier
));
when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 5ad88c9..462da93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -72,6 +72,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -118,6 +119,7 @@
@Mock private INotificationManager mINotificationManager;
@Mock private LauncherApps mLauncherApps;
@Mock private ShortcutManager mShortcutManager;
+ @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Before
public void setUp() {
@@ -465,7 +467,8 @@
}
private NotificationMenuRowPlugin.MenuItem createTestMenuItem(ExpandableNotificationRow row) {
- NotificationMenuRowPlugin menuRow = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin menuRow =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
menuRow.createMenu(row, row.getEntry().getSbn());
NotificationMenuRowPlugin.MenuItem menuItem = menuRow.getLongpressMenuItem(mContext);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index b33d26f..99e8c7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -40,6 +40,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.After;
@@ -54,11 +55,13 @@
public class NotificationMenuRowTest extends LeakCheckedTest {
private ExpandableNotificationRow mRow;
+ private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Before
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mRow = mock(ExpandableNotificationRow.class);
+ mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
NotificationEntry entry = new NotificationEntryBuilder().build();
when(mRow.getEntry()).thenReturn(entry);
}
@@ -71,7 +74,8 @@
@Test
public void testAttachDetach() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewUtils.attachView(row.getMenuView());
TestableLooper.get(this).processAllMessages();
@@ -81,7 +85,8 @@
@Test
public void testRecreateMenu() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
assertTrue(row.getMenuView() != null);
row.createMenu(mRow, null);
@@ -90,7 +95,8 @@
@Test
public void testResetUncreatedMenu() {
- NotificationMenuRowPlugin row = new NotificationMenuRow(mContext);
+ NotificationMenuRowPlugin row =
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.resetMenu();
}
@@ -99,7 +105,7 @@
public void testNoAppOpsInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -111,7 +117,7 @@
public void testNoSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 0);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -123,7 +129,7 @@
public void testSnoozeInSlowSwipe() {
Settings.Secure.putInt(mContext.getContentResolver(), SHOW_NOTIFICATION_SNOOZE, 1);
- NotificationMenuRow row = new NotificationMenuRow(mContext);
+ NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
row.createMenu(mRow, null);
ViewGroup container = (ViewGroup) row.getMenuView();
@@ -133,7 +139,8 @@
@Test
public void testIsSnappedAndOnSameSide() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuSnapped()).thenReturn(true);
@@ -165,7 +172,8 @@
@Test
public void testGetMenuSnapTarget() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(30).when(row).getSpaceForMenu();
@@ -179,7 +187,8 @@
@Test
public void testIsSwipedEnoughToShowMenu() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
doReturn(40f).when(row).getMinimumSwipeDistance();
@@ -205,7 +214,8 @@
@Test
public void testIsWithinSnapMenuThreshold() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doReturn(30f).when(row).getSnapBackThreshold();
doReturn(50f).when(row).getDismissThreshold();
@@ -238,7 +248,8 @@
@Test
public void testShouldSnapBack() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doReturn(40f).when(row).getSnapBackThreshold();
when(row.isMenuVisible()).thenReturn(false);
when(row.isMenuOnLeft()).thenReturn(true);
@@ -259,7 +270,8 @@
@Test
public void testCanBeDismissed() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
ExpandableNotificationRow parent = mock(ExpandableNotificationRow.class);
when(row.getParent()).thenReturn(parent);
@@ -274,7 +286,8 @@
@Test
public void testIsTowardsMenu() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuVisible()).thenReturn(true);
when(row.isMenuOnLeft()).thenReturn(true);
@@ -294,7 +307,8 @@
@Test
public void onSnapBack() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
row.setMenuClickListener(listener);
@@ -315,7 +329,8 @@
@Test
public void testOnSnap() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
when(row.isMenuOnLeft()).thenReturn(true);
NotificationMenuRowPlugin.OnMenuEventListener listener = mock(NotificationMenuRowPlugin
.OnMenuEventListener.class);
@@ -335,7 +350,8 @@
@Test
public void testOnDismiss() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doNothing().when(row).cancelDrag();
row.onSnapOpen();
@@ -351,7 +367,8 @@
@Test
public void testOnDown() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doNothing().when(row).beginDrag();
row.onTouchStart();
@@ -361,7 +378,8 @@
@Test
public void testOnUp() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
row.onTouchStart();
assertTrue("before onTouchEnd, isUserTouching is true", row.isUserTouching());
@@ -373,7 +391,8 @@
@Test
public void testIsMenuVisible() {
- NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
+ NotificationMenuRow row = Mockito.spy(
+ new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
row.setMenuAlpha(0);
assertFalse("when alpha is 0, menu is not visible", row.isMenuVisible());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2134a3d..0e67feb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -57,6 +57,7 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.icon.IconBuilder;
import com.android.systemui.statusbar.notification.icon.IconManager;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -100,6 +101,7 @@
private final RowContentBindStage mBindStage;
private final IconManager mIconManager;
private StatusBarStateController mStatusBarStateController;
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
public NotificationTestHelper(Context context, TestableDependency dependency) {
mContext = context;
@@ -138,6 +140,7 @@
ArgumentCaptor.forClass(NotifCollectionListener.class);
verify(collection).addCollectionListener(collectionListenerCaptor.capture());
mBindPipelineEntryListener = collectionListenerCaptor.getValue();
+ mPeopleNotificationIdentifier = mock(PeopleNotificationIdentifier.class);
}
/**
@@ -407,7 +410,8 @@
mock(NotificationMediaManager.class),
mock(ExpandableNotificationRow.OnAppOpsClickListener.class),
mock(FalsingManager.class),
- mStatusBarStateController);
+ mStatusBarStateController,
+ mPeopleNotificationIdentifier);
row.setAboveShelfChangedListener(aboveShelf -> { });
mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
inflateAndWait(entry, mBindStage);
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 b5f57b6..962d773 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
@@ -39,6 +39,7 @@
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.provider.Settings;
@@ -101,6 +102,7 @@
protected NetworkRegistrationInfo mFakeRegInfo;
protected ConnectivityManager mMockCm;
protected WifiManager mMockWm;
+ protected NetworkScoreManager mMockNsm;
protected SubscriptionManager mMockSm;
protected TelephonyManager mMockTm;
protected BroadcastDispatcher mMockBd;
@@ -148,6 +150,7 @@
mMockSm = mock(SubscriptionManager.class);
mMockCm = mock(ConnectivityManager.class);
mMockBd = mock(BroadcastDispatcher.class);
+ mMockNsm = mock(NetworkScoreManager.class);
mMockSubDefaults = mock(SubscriptionDefaults.class);
mNetCapabilities = new NetworkCapabilities();
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(true);
@@ -196,8 +199,8 @@
return null;
}).when(mMockProvisionController).addCallback(any());
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mMockProvisionController, mMockBd);
setupNetworkController();
@@ -244,18 +247,17 @@
}
protected NetworkControllerImpl setUpNoMobileData() {
- when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
- NetworkControllerImpl networkControllerNoMobile
- = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
+ when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
+ NetworkControllerImpl networkControllerNoMobile =
+ new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockNsm, mMockSm,
mConfig, TestableLooper.get(this).getLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
mock(DeviceProvisionedController.class), mMockBd);
- setupNetworkController();
+ setupNetworkController();
- return networkControllerNoMobile;
-
+ return networkControllerNoMobile;
}
// 2 Bars 3G GSM.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 75f2619..6fffcff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -102,8 +102,8 @@
public void test4gDataIcon() {
// Switch to showing 4g icon and re-initialize the NetworkController.
mConfig.show4gForLte = true;
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class),
mock(DataUsageController.class), mMockSubDefaults,
mock(DeviceProvisionedController.class), mMockBd);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index b922f06..399b5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -58,8 +58,8 @@
// Turn off mobile network support.
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
setupNetworkController();
@@ -120,8 +120,8 @@
// Turn off mobile network support.
when(mMockCm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm, mMockSm,
- mConfig, Looper.getMainLooper(), mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm, mMockWm,
+ mMockNsm, mMockSm, mConfig, Looper.getMainLooper(), mCallbackHandler,
mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd);
setupNetworkController();
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 808d322..bfcde97 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -98,8 +98,8 @@
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
-
- private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -118,8 +118,7 @@
// We're disabled if the DeviceConfig disable flag is set to true.
// This is in case that an emergency rollback of the feature is needed.
- if (DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) {
+ if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
Slog.v(TAG, "Disabled because of DeviceConfig flag");
return true;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 9018caa..0671477 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -234,6 +234,7 @@
private static final String FUSE_ENABLED = "fuse_enabled";
private static final boolean DEFAULT_FUSE_ENABLED = true;
+ @GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
public static class Lifecycle extends SystemService {
@@ -810,7 +811,7 @@
}
case H_VOLUME_STATE_CHANGED: {
final SomeArgs args = (SomeArgs) msg.obj;
- onVolumeStateChangedInternal((VolumeInfo) args.arg1, (int) args.arg2,
+ onVolumeStateChangedAsync((VolumeInfo) args.arg1, (int) args.arg2,
(int) args.arg3);
}
}
@@ -1337,6 +1338,7 @@
args.arg2 = oldState;
args.arg3 = newState;
mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
+ onVolumeStateChangedLocked(vol, oldState, newState);
}
}
}
@@ -1509,11 +1511,45 @@
return true;
}
- private void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) {
- synchronized (mLock) {
- if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
+
+ private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
+ if (vol.type == VolumeInfo.TYPE_EMULATED) {
+ if (newState != VolumeInfo.STATE_MOUNTED) {
mFuseMountedUser.remove(vol.getMountUserId());
+ } else {
+ final int userId = vol.getMountUserId();
+ mFuseMountedUser.add(userId);
+ // Async remount app storage so it won't block the main thread.
+ new Thread(() -> {
+ Map<Integer, String> pidPkgMap = null;
+ // getProcessesWithPendingBindMounts() could fail when a new app process is
+ // starting and it's not planning to mount storage dirs in zygote, but it's
+ // rare, so we retry 5 times and hope we can get the result successfully.
+ for (int i = 0; i < 5; i++) {
+ try {
+ pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
+ .getProcessesWithPendingBindMounts(vol.getMountUserId());
+ break;
+ } catch (IllegalStateException e) {
+ Slog.i(TAG, "Some processes are starting, retry");
+ // Wait 100ms and retry so hope the pending process is started.
+ SystemClock.sleep(100);
+ }
+ }
+ if (pidPkgMap != null) {
+ remountAppStorageDirs(pidPkgMap, userId);
+ } else {
+ Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
+ + " 5 retries");
+ }
+ }).start();
}
+ }
+ }
+
+
+ private void onVolumeStateChangedAsync(VolumeInfo vol, int oldState, int newState) {
+ synchronized (mLock) {
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
if (!TextUtils.isEmpty(vol.fsUuid)) {
@@ -2161,35 +2197,6 @@
}
});
Slog.i(TAG, "Mounted volume " + vol);
- if (vol.type == VolumeInfo.TYPE_EMULATED) {
- final int userId = vol.getMountUserId();
- mFuseMountedUser.add(userId);
- // Async remount app storage so it won't block the main thread.
- new Thread(() -> {
- Map<Integer, String> pidPkgMap = null;
- // getProcessesWithPendingBindMounts() could fail when a new app process is
- // starting and it's not planning to mount storage dirs in zygote, but it's
- // rare, so we retry 5 times and hope we can get the result successfully.
- for (int i = 0; i < 5; i++) {
- try {
- pidPkgMap = LocalServices.getService(ActivityManagerInternal.class)
- .getProcessesWithPendingBindMounts(vol.getMountUserId());
- break;
- } catch (IllegalStateException e) {
- Slog.i(TAG, "Some processes are starting, retry");
- // Wait 100ms and retry so hope the pending process is started.
- SystemClock.sleep(100);
- }
- }
- if (pidPkgMap != null) {
- remountAppStorageDirs(pidPkgMap, userId);
- } else {
- Slog.wtf(TAG, "Not able to getStorageNotOptimizedProcesses() after"
- + " 5 retries");
- }
-
- }).start();
- }
} catch (Exception e) {
Slog.wtf(TAG, e);
}
@@ -4445,9 +4452,11 @@
@Override
public boolean prepareStorageDirs(int userId, Set<String> packageList,
String processName) {
- if (!mFuseMountedUser.contains(userId)) {
- Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
- return false;
+ synchronized (mLock) {
+ if (!mFuseMountedUser.contains(userId)) {
+ Slog.w(TAG, "User " + userId + " is not unlocked yet so skip mounting obb");
+ return false;
+ }
}
try {
final IVold vold = IVold.Stub.asInterface(
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 059eb6a..df16058 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -32,7 +32,7 @@
"name": "CtsWindowManagerDeviceTestCases",
"options": [
{
- "include-filter": "android.server.wm.ToastTest"
+ "include-filter": "android.server.wm.ToastWindowTest"
}
],
"file_patterns": ["NotificationManagerService\\.java"]
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 689f64d0..85d28831 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17698,7 +17698,7 @@
proc.setReportedForegroundServiceTypes(fgServiceTypes);
ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid);
- item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
+ item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
item.foregroundServiceTypes = fgServiceTypes;
}
if (oomAdj) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1412112..dbcb3da 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2385,7 +2385,7 @@
"Changes in " + app + ": " + changes);
ActivityManagerService.ProcessChangeItem item =
mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
- item.changes = changes;
+ item.changes |= changes;
item.foregroundActivities = app.repForegroundActivities;
item.capability = app.setCapability;
if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f6cdaeb..3115590 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1331,10 +1331,10 @@
private void updateDefaultVolumes() {
for (int stream = 0; stream < mStreamStates.length; stream++) {
if (stream != mStreamVolumeAlias[stream]) {
- AudioSystem.DEFAULT_STREAM_VOLUME[stream] = rescaleIndex(
- AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]],
+ AudioSystem.DEFAULT_STREAM_VOLUME[stream] = (rescaleIndex(
+ AudioSystem.DEFAULT_STREAM_VOLUME[mStreamVolumeAlias[stream]] * 10,
mStreamVolumeAlias[stream],
- stream);
+ stream) + 5) / 10;
}
}
}
@@ -4779,7 +4779,9 @@
} catch (IllegalArgumentException e) {
// Volume Groups without attributes are not controllable through set/get volume
// using attributes. Do not append them.
- Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ if (DEBUG_VOL) {
+ Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
+ }
continue;
}
sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
@@ -4800,7 +4802,9 @@
}
private void readVolumeGroupsSettings() {
- Log.v(TAG, "readVolumeGroupsSettings");
+ if (DEBUG_VOL) {
+ Log.v(TAG, "readVolumeGroupsSettings");
+ }
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
vgs.readSettings();
@@ -4810,7 +4814,9 @@
// Called upon crash of AudioServer
private void restoreVolumeGroups() {
- Log.v(TAG, "restoreVolumeGroups");
+ if (DEBUG_VOL) {
+ Log.v(TAG, "restoreVolumeGroups");
+ }
for (int i = 0; i < sVolumeGroupStates.size(); i++) {
final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
vgs.applyAllVolumes();
@@ -4846,7 +4852,9 @@
private VolumeGroupState(AudioVolumeGroup avg) {
mAudioVolumeGroup = avg;
- Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ if (DEBUG_VOL) {
+ Log.v(TAG, "VolumeGroupState for " + avg.toString());
+ }
for (final AudioAttributes aa : avg.getAudioAttributes()) {
if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
mAudioAttributes = aa;
@@ -4949,16 +4957,21 @@
final int device = mIndexMap.keyAt(i);
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
index = mIndexMap.valueAt(i);
- Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
- + mAudioVolumeGroup.name() + " and device "
- + AudioSystem.getOutputDeviceName(device));
+ if (DEBUG_VOL) {
+ Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ }
setVolumeIndexInt(index, device, 0 /*flags*/);
}
}
// apply default volume last: by convention , default device volume will be used
+ // by audio policy manager if no explicit volume is present for a given device type
index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
- Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
- + mAudioVolumeGroup.name());
+ if (DEBUG_VOL) {
+ Log.v(TAG, "applyAllVolumes: restore default device index " + index
+ + " for group " + mAudioVolumeGroup.name());
+ }
setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
}
}
@@ -4967,9 +4980,11 @@
if (mUseFixedVolume) {
return;
}
- Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
- + mAudioVolumeGroup.name() + " and device "
- + AudioSystem.getOutputDeviceName(device));
+ if (DEBUG_VOL) {
+ Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
+ + mAudioVolumeGroup.name() + " and device "
+ + AudioSystem.getOutputDeviceName(device));
+ }
boolean success = Settings.System.putIntForUser(mContentResolver,
getSettingNameForDevice(device),
getIndex(device),
@@ -4999,12 +5014,12 @@
index = Settings.System.getIntForUser(
mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
if (index == -1) {
- Log.e(TAG, "readSettings: No index stored for group "
- + mAudioVolumeGroup.name() + ", device " + name);
continue;
}
- Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
- + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ if (DEBUG_VOL) {
+ Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
+ + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
+ }
mIndexMap.put(device, getValidIndex(index));
}
}
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 65f2218..32c6cc3 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -150,14 +150,14 @@
final AudioRecordingConfiguration config = createRecordingConfiguration(
uid, session, source, recordingInfo,
portId, silenced, activeSource, clientEffects, effects);
- if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+ if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
+ && (event == AudioManager.RECORD_CONFIG_EVENT_START
+ || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) {
final AudioDeviceInfo device = config.getAudioDevice();
- if (AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
+ if (device != null
+ && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
mLegacyRemoteSubmixRiid.set(riid);
- if (event == AudioManager.RECORD_CONFIG_EVENT_START
- || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE) {
- mLegacyRemoteSubmixActive.set(true);
- }
+ mLegacyRemoteSubmixActive.set(true);
}
}
if (MediaRecorder.isSystemOnlyAudioSource(source)) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index b28350d..52e9d7c 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -54,11 +54,9 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
@@ -70,10 +68,8 @@
private static final String TAG = "MR2ServiceImpl";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- /**
- * TODO: Change this with the real request ID from MediaRouter2 when
- * MediaRouter2 needs to get notified for the failures.
- */
+ // TODO: (In Android S or later) if we add callback methods for generic failures
+ // in MediaRouter2, remove this constant and replace the usages with the real request IDs.
private static final long DUMMY_REQUEST_ID = -1;
private final Context mContext;
@@ -1150,35 +1146,24 @@
private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
- MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
+ MediaRoute2ProviderInfo currentInfo = provider.getProviderInfo();
MediaRoute2ProviderInfo prevInfo =
(providerInfoIndex < 0) ? null : mLastProviderInfos.get(providerInfoIndex);
+ if (Objects.equals(prevInfo, currentInfo)) return;
- if (Objects.equals(prevInfo, providerInfo)) return;
-
+ List<MediaRoute2Info> addedRoutes = new ArrayList<>();
+ List<MediaRoute2Info> removedRoutes = new ArrayList<>();
+ List<MediaRoute2Info> changedRoutes = new ArrayList<>();
if (prevInfo == null) {
- mLastProviderInfos.add(providerInfo);
- Collection<MediaRoute2Info> addedRoutes = providerInfo.getRoutes();
- if (addedRoutes.size() > 0) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::notifyRoutesAddedToRouters,
- this, getRouters(), new ArrayList<>(addedRoutes)));
- }
- } else if (providerInfo == null) {
+ mLastProviderInfos.add(currentInfo);
+ addedRoutes.addAll(currentInfo.getRoutes());
+ } else if (currentInfo == null) {
mLastProviderInfos.remove(prevInfo);
- Collection<MediaRoute2Info> removedRoutes = prevInfo.getRoutes();
- if (removedRoutes.size() > 0) {
- sendMessage(PooledLambda.obtainMessage(
- UserHandler::notifyRoutesRemovedToRouters,
- this, getRouters(), new ArrayList<>(removedRoutes)));
- }
+ removedRoutes.addAll(prevInfo.getRoutes());
} else {
- mLastProviderInfos.set(providerInfoIndex, providerInfo);
- List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- List<MediaRoute2Info> changedRoutes = new ArrayList<>();
-
- final Collection<MediaRoute2Info> currentRoutes = providerInfo.getRoutes();
- final Set<String> updatedRouteIds = new HashSet<>();
+ mLastProviderInfos.set(providerInfoIndex, currentInfo);
+ final Collection<MediaRoute2Info> prevRoutes = prevInfo.getRoutes();
+ final Collection<MediaRoute2Info> currentRoutes = currentInfo.getRoutes();
for (MediaRoute2Info route : currentRoutes) {
if (!route.isValid()) {
@@ -1186,37 +1171,33 @@
continue;
}
MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
-
- if (prevRoute != null) {
- if (!Objects.equals(prevRoute, route)) {
- changedRoutes.add(route);
- }
- updatedRouteIds.add(route.getId());
- } else {
+ if (prevRoute == null) {
addedRoutes.add(route);
+ } else if (!Objects.equals(prevRoute, route)) {
+ changedRoutes.add(route);
}
}
for (MediaRoute2Info prevRoute : prevInfo.getRoutes()) {
- if (!updatedRouteIds.contains(prevRoute.getId())) {
+ if (currentInfo.getRoute(prevRoute.getOriginalId()) == null) {
removedRoutes.add(prevRoute);
}
}
+ }
- List<IMediaRouter2> routers = getRouters();
- List<IMediaRouter2Manager> managers = getManagers();
- if (addedRoutes.size() > 0) {
- notifyRoutesAddedToRouters(routers, addedRoutes);
- notifyRoutesAddedToManagers(managers, addedRoutes);
- }
- if (removedRoutes.size() > 0) {
- notifyRoutesRemovedToRouters(routers, removedRoutes);
- notifyRoutesRemovedToManagers(managers, removedRoutes);
- }
- if (changedRoutes.size() > 0) {
- notifyRoutesChangedToRouters(routers, changedRoutes);
- notifyRoutesChangedToManagers(managers, changedRoutes);
- }
+ List<IMediaRouter2> routers = getRouters();
+ List<IMediaRouter2Manager> managers = getManagers();
+ if (addedRoutes.size() > 0) {
+ notifyRoutesAddedToRouters(routers, addedRoutes);
+ notifyRoutesAddedToManagers(managers, addedRoutes);
+ }
+ if (removedRoutes.size() > 0) {
+ notifyRoutesRemovedToRouters(routers, removedRoutes);
+ notifyRoutesRemovedToManagers(managers, removedRoutes);
+ }
+ if (changedRoutes.size() > 0) {
+ notifyRoutesChangedToRouters(routers, changedRoutes);
+ notifyRoutesChangedToManagers(managers, changedRoutes);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index 2ed6e16..bfc76df 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -250,26 +250,36 @@
for (int i = mHistoryFiles.size() - 1; i >= 0; i--) {
final AtomicFile currentOldestFile = mHistoryFiles.get(i);
- final long creationTime = Long.parseLong(currentOldestFile.getBaseFile().getName());
- if (DEBUG) {
- Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
- + " created on " + creationTime);
- }
- if (creationTime <= retentionBoundary.getTimeInMillis()) {
+ try {
+ final long creationTime = Long.parseLong(
+ currentOldestFile.getBaseFile().getName());
if (DEBUG) {
- Slog.d(TAG, "Removed " + currentOldestFile.getBaseFile().getName());
+ Slog.d(TAG, "File " + currentOldestFile.getBaseFile().getName()
+ + " created on " + creationTime);
}
- currentOldestFile.delete();
- // TODO: delete all relevant bitmaps, once they exist
- mHistoryFiles.removeLast();
- } else {
- // all remaining files are newer than the cut off; schedule jobs to delete
- scheduleDeletion(currentOldestFile.getBaseFile(), creationTime, retentionDays);
+ if (creationTime <= retentionBoundary.getTimeInMillis()) {
+ deleteFile(currentOldestFile);
+ } else {
+ // all remaining files are newer than the cut off; schedule jobs to delete
+ scheduleDeletion(
+ currentOldestFile.getBaseFile(), creationTime, retentionDays);
+ }
+ } catch (NumberFormatException e) {
+ deleteFile(currentOldestFile);
}
}
}
}
+ private void deleteFile(AtomicFile file) {
+ if (DEBUG) {
+ Slog.d(TAG, "Removed " + file.getBaseFile().getName());
+ }
+ file.delete();
+ // TODO: delete all relevant bitmaps, once they exist
+ mHistoryFiles.removeLast();
+ }
+
private void scheduleDeletion(File file, long creationTime, int retentionDays) {
final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
scheduleDeletion(file, deletionTime);
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 7eb3f01..d89605a 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -542,12 +542,6 @@
void unregisterPointerEventListener(PointerEventListener listener, int displayId);
/**
- * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
- * {@param activityType}.
- */
- void getStackBounds(int windowingMode, int activityType, Rect outBounds);
-
- /**
* @return The currently active input method window.
*/
WindowState getInputMethodWindowLw();
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ce7e797..55b7be779 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -197,6 +197,7 @@
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.window.ITaskOrganizer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -822,8 +823,10 @@
if (w.mHasSurface && isDisplayed) {
final int type = w.mAttrs.type;
- if (type == TYPE_SYSTEM_DIALOG || type == TYPE_SYSTEM_ERROR
- || mWmService.mPolicy.isKeyguardShowing()) {
+ if (type == TYPE_SYSTEM_DIALOG
+ || type == TYPE_SYSTEM_ERROR
+ || (type == TYPE_NOTIFICATION_SHADE
+ && mWmService.mPolicy.isKeyguardShowing())) {
mTmpApplySurfaceChangesTransactionState.syswin = true;
}
if (mTmpApplySurfaceChangesTransactionState.preferredRefreshRate == 0
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 367151c..221258e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -3246,9 +3246,14 @@
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
final int dockedAppearance = updateLightStatusBarAppearanceLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
- mService.getStackBounds(
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
- final boolean inSplitScreen = !mDockedStackBounds.isEmpty();
+ final boolean inSplitScreen =
+ mService.mRoot.getDefaultDisplay().mTaskContainers.isSplitScreenModeActivated();
+ if (inSplitScreen) {
+ mService.getStackBounds(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD,
+ mDockedStackBounds);
+ } else {
+ mDockedStackBounds.setEmpty();
+ }
mService.getStackBounds(inSplitScreen ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
: WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 29a2e18..e43f4b4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -304,7 +304,11 @@
}
}
- boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
+ boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
+ final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo();
+ final int dw = displayInfo.logicalWidth;
+ final int dh = displayInfo.logicalHeight;
+
int xOffset = 0;
int yOffset = 0;
boolean rawChanged = false;
@@ -444,10 +448,6 @@
}
private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
WindowState target = mWallpaperTarget;
if (target != null) {
if (target.mWallpaperX >= 0) {
@@ -484,7 +484,7 @@
}
for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
- mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
+ mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
}
}
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e29580b..203ca25 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -73,11 +73,11 @@
}
}
- void updateWallpaperOffset(int dw, int dh, boolean sync) {
+ void updateWallpaperOffset(boolean sync) {
final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
- if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+ if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
// We only want to be synchronous with one wallpaper.
sync = false;
}
@@ -85,10 +85,6 @@
}
void updateWallpaperVisibility(boolean visible) {
- final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
if (isVisible() != visible) {
// Need to do a layout to ensure the wallpaper now has the correct size.
mDisplayContent.setLayoutNeeded();
@@ -98,7 +94,7 @@
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+ wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
}
wallpaper.dispatchWallpaperVisibility(visible);
@@ -145,19 +141,11 @@
}
}
- DisplayInfo displayInfo = getFixedRotationTransformDisplayInfo();
- if (displayInfo == null) {
- displayInfo = mDisplayContent.getDisplayInfo();
- }
-
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
final WindowState wallpaper = mChildren.get(wallpaperNdx);
if (visible) {
- wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+ wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
}
// First, make sure the client has the current visibility state.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8e45752..dfaa0ec 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2332,9 +2332,7 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (toBeDisplayed && win.mIsWallpaper) {
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- displayContent.mWallpaperController.updateWallpaperOffset(
- win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+ displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
}
if (win.mActivityRecord != null) {
win.mActivityRecord.updateReportedVisibilityLocked();
@@ -2782,7 +2780,6 @@
aspectRatio);
}
- @Override
public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
synchronized (mGlobalLock) {
final ActivityStack stack = mRoot.getStack(windowingMode, activityType);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7dcf375..b87d181 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1101,7 +1101,6 @@
}
}
- final ActivityStack stack = getRootTask();
layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame);
windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
@@ -1205,8 +1204,7 @@
if (mIsWallpaper && (fw != windowFrames.mFrame.width()
|| fh != windowFrames.mFrame.height())) {
- dc.mWallpaperController.updateWallpaperOffset(this,
- displayInfo.logicalWidth, displayInfo.logicalHeight, false /* sync */);
+ dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
}
// Calculate relative frame
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 563710b..b25383b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1381,7 +1381,8 @@
return true;
}
- if (isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+ final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
+ if (isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
mWin.setDisplayLayoutNeeded();
mService.mWindowPlacerLocked.requestTraversal();
@@ -1435,11 +1436,11 @@
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
- } else {
+ } else if (!isImeWindow) {
mWin.cancelAnimation();
}
- if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+ if (!isEntrance && isImeWindow) {
mWin.getDisplayContent().adjustForImeIfNeeded();
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 1544ff1..eed39e1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -9119,6 +9119,31 @@
}
@Override
+ public @Nullable ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent(
+ @NonNull UserHandle userHandle) {
+ if (!mHasFeature) {
+ return null;
+ }
+ synchronized (getLockObject()) {
+ final String supervisor = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+ if (supervisor == null) {
+ return null;
+ }
+ final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ final ComponentName doComponent = mOwners.getDeviceOwnerComponent();
+ final ComponentName poComponent =
+ mOwners.getProfileOwnerComponent(userHandle.getIdentifier());
+ if (supervisorComponent.equals(doComponent) || supervisorComponent.equals(
+ poComponent)) {
+ return supervisorComponent;
+ } else {
+ return null;
+ }
+ }
+ }
+
+ @Override
public String getProfileOwnerName(int userHandle) {
if (!mHasFeature) {
return null;
@@ -11488,6 +11513,18 @@
throw new SecurityException(
"User " + userId + " is not allowed to call setSecondaryLockscreenEnabled");
}
+ // Only the default supervision app can use this API.
+ final String supervisor = mContext.getResources().getString(
+ com.android.internal.R.string.config_defaultSupervisionProfileOwnerComponent);
+ if (supervisor == null) {
+ throw new SecurityException("Unable to set secondary lockscreen setting, no "
+ + "default supervision component defined");
+ }
+ final ComponentName supervisorComponent = ComponentName.unflattenFromString(supervisor);
+ if (!who.equals(supervisorComponent)) {
+ throw new SecurityException(
+ "Admin " + who + " is not the default supervision component");
+ }
}
@Override
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 09e333e..db464e7 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -21,6 +21,7 @@
import android.net.NattKeepalivePacketData;
import android.net.ProxyInfo;
import android.net.TcpKeepalivePacketData;
+import android.net.shared.Layer2Information;
import android.net.shared.ProvisioningConfiguration;
import android.net.util.KeepalivePacketDataUtil;
import android.os.Binder;
@@ -292,4 +293,20 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ /**
+ * Update the bssid, L2 key and group hint layer2 information.
+ */
+ public boolean updateLayer2Information(Layer2Information info) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.updateLayer2Information(info.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error updating layer2 information", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
}
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 136ee91..c87ece2 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -220,7 +220,7 @@
String mimeType = intentFilter != null ? intentFilter.getDataType(0) : null;
@Event.EventType int eventType = mimeTypeToShareEventType(mimeType);
EventHistoryImpl eventHistory;
- if (ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE.equals(event.getLaunchLocation())) {
+ if (ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE.equals(event.getLaunchLocation())) {
// Direct share event
if (appTarget.getShortcutInfo() == null) {
return;
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 5c82200..736a7be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -79,7 +79,8 @@
private static final String CALLING_PACKAGE2 = "com.package.name2";
private static final String NAMESPACE1 = "namespace1";
private static final String NAMESPACE2 = "namespace2";
- private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party";
+ private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+ "persist.device_config.configuration.disable_rescue_party";
private MockitoSession mSession;
private HashMap<String, String> mSystemSettingsMap;
@@ -172,6 +173,7 @@
Integer.toString(RescueParty.LEVEL_NONE));
SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
}
@After
@@ -317,13 +319,6 @@
@Test
public void testExplicitlyEnablingAndDisablingRescue() {
- // mock the DeviceConfig get call to avoid hitting
- // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig.
- doReturn(true)
- .when(() -> DeviceConfig.getBoolean(
- eq(DeviceConfig.NAMESPACE_CONFIGURATION),
- eq(DISABLE_RESCUE_PARTY_FLAG),
- eq(false)));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -336,18 +331,15 @@
@Test
public void testDisablingRescueByDeviceConfigFlag() {
- doReturn(true)
- .when(() -> DeviceConfig.getBoolean(
- eq(DeviceConfig.NAMESPACE_CONFIGURATION),
- eq(DISABLE_RESCUE_PARTY_FLAG),
- eq(false)));
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
// Restore the property value initalized in SetUp()
SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
}
@Test
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 baf551e..fe47cea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4274,6 +4274,9 @@
// Profile owner can set enabled state.
setAsProfileOwner(admin1);
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
dpm.setSecondaryLockscreenEnabled(admin1, true);
assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
DpmMockContext.CALLER_USER_HANDLE)));
@@ -4297,6 +4300,9 @@
// Device owners can set enabled state.
setupDeviceOwner();
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
dpm.setSecondaryLockscreenEnabled(admin1, true);
assertTrue(dpm.isSecondaryLockscreenEnabled(UserHandle.of(UserHandle.USER_SYSTEM)));
}
@@ -4309,12 +4315,39 @@
DpmMockContext.CALLER_USER_HANDLE)));
// Non-DO/PO cannot set enabled state.
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin1.flattenToString());
assertExpectException(SecurityException.class, /* messageRegex= */ null,
() -> dpm.setSecondaryLockscreenEnabled(admin1, true));
assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
DpmMockContext.CALLER_USER_HANDLE)));
}
+ public void testSecondaryLockscreen_nonSupervisionApp() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+
+ // Initial state is disabled.
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+
+ // Caller is Profile Owner, but no supervision app is configured.
+ setAsProfileOwner(admin1);
+ assertExpectException(SecurityException.class, "no default supervision component defined",
+ () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+
+ // Caller is Profile Owner, but is not the default configured supervision app.
+ when(mServiceContext.resources
+ .getString(R.string.config_defaultSupervisionProfileOwnerComponent))
+ .thenReturn(admin2.flattenToString());
+ assertExpectException(SecurityException.class, "is not the default supervision component",
+ () -> dpm.setSecondaryLockscreenEnabled(admin1, true));
+ assertFalse(dpm.isSecondaryLockscreenEnabled(UserHandle.of(
+ DpmMockContext.CALLER_USER_HANDLE)));
+ }
+
public void testIsDeviceManaged() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
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 5199604..728e149 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
@@ -299,7 +299,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
mDataManager.reportShareTargetEvent(appTargetEvent, intentFilter);
@@ -319,7 +319,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
@@ -667,7 +667,7 @@
.build();
AppTargetEvent appTargetEvent =
new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_LAUNCH)
- .setLaunchLocation(ChooserActivity.LAUNCH_LOCATON_DIRECT_SHARE)
+ .setLaunchLocation(ChooserActivity.LAUNCH_LOCATION_DIRECT_SHARE)
.build();
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SEND, "image/jpg");
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 191c038..5412bb5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -18,7 +18,9 @@
import android.content.pm.PackageManager
import android.platform.test.annotations.Presubmit
+import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
import org.junit.Test
/**
@@ -28,6 +30,9 @@
@Presubmit
class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
+ @get:Rule
+ val expect = Expect.create()
+
@Test
fun applicationInfoEquality() {
val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
@@ -41,7 +46,8 @@
} else {
"$firstName | $secondName"
}
- assertWithMessage(packageName).that(it.first?.dumpToString())
+ expect.withMessage("${it.first?.sourceDir} $packageName")
+ .that(it.first?.dumpToString())
.isEqualTo(it.second?.dumpToString())
}
}
@@ -71,7 +77,8 @@
} else {
"$firstName | $secondName"
}
- assertWithMessage(packageName).that(it.first?.dumpToString())
+ expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")
+ .that(it.first?.dumpToString())
.isEqualTo(it.second?.dumpToString())
}
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index 19bf9b6..7b1b2d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -29,14 +29,17 @@
import android.content.pm.ProviderInfo
import android.os.Debug
import android.os.Environment
+import android.os.ServiceManager
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.internal.compat.IPlatformCompat
import com.android.server.pm.PackageManagerService
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.pkg.PackageStateUnserialized
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
+import org.junit.After
import org.junit.BeforeClass
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -59,7 +62,27 @@
setCallback { false /* hasFeature */ }
}
- protected val packageParser2 = TestPackageParser2()
+ private val platformCompat = IPlatformCompat.Stub
+ .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE))
+
+ protected val packageParser2 = PackageParser2(null /* separateProcesses */,
+ false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */,
+ object : PackageParser2.Callback() {
+ override fun isChangeEnabled(
+ changeId: Long,
+ appInfo: ApplicationInfo
+ ): Boolean {
+ // This test queries PlatformCompat because prebuilts in the tree
+ // may not be updated to be compliant with the latest enforcement checks.
+ return platformCompat.isChangeEnabled(changeId, appInfo)
+ }
+
+ // Assume the device doesn't support anything. This will affect permission
+ // parsing and will force <uses-permission/> declarations to include all
+ // requiredNotFeature permissions and exclude all requiredFeature permissions.
+ // This mirrors the old behavior.
+ override fun hasFeature(feature: String) = false
+ })
/**
* It would be difficult to mock all possibilities, so just use the APKs on device.
@@ -91,35 +114,31 @@
lateinit var newPackages: List<AndroidPackage>
- var failureInBeforeClass: Throwable? = null
+ private val thrownInSetUp = mutableListOf<Throwable>()
@Suppress("ConstantConditionIf")
@JvmStatic
@BeforeClass
fun setUpPackages() {
- failureInBeforeClass = null
- try {
- this.oldPackages = apks.map {
+ this.oldPackages = apks.mapNotNull {
+ tryOrNull {
packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
}
+ }
- this.newPackages = apks.map {
+ this.newPackages = apks.mapNotNull {
+ tryOrNull {
packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
}
+ }
- if (DUMP_HPROF_TO_EXTERNAL) {
- System.gc()
- Environment.getExternalStorageDirectory()
- .resolve(
- "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
- .absolutePath
- .run(Debug::dumpHprofData)
- }
- } catch (t: Throwable) {
- // If we crash here we cause a tool failure (because we don't run any of the tests
- // in the subclasses, leading to a difference between expected and actual test
- // result counts).
- failureInBeforeClass = t
+ if (DUMP_HPROF_TO_EXTERNAL) {
+ System.gc()
+ Environment.getExternalStorageDirectory()
+ .resolve(
+ "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
+ .absolutePath
+ .run(Debug::dumpHprofData)
}
}
@@ -146,13 +165,36 @@
this.pkg = aPkg
whenever(pkgState) { PackageStateUnserialized() }
}
+
+ private fun <T> tryOrNull(block: () -> T) = try {
+ block()
+ } catch (t: Throwable) {
+ thrownInSetUp.add(t)
+ null
+ }
}
- @org.junit.Before
+ @After
fun verifySetUpPackages() {
- failureInBeforeClass?.let {
- throw AssertionError("setUpPackages failed:", it)
- }
+ if (thrownInSetUp.isEmpty()) return
+ val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" +
+ thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() })
+
+ /*
+ Testing infrastructure doesn't currently support errors thrown in @AfterClass,
+ so instead it's thrown here. But to avoid throwing a massive repeated stack for every
+ test method, only throw on the first method run in the class, clearing the list so that
+ subsequent methods can run without failing. Doing this in @After lets true method
+ failures propagate, as those should throw before this does.
+
+ This will cause the failure to be attached to a different method depending on run order,
+ which could make comparisons difficult. So if a failure points here, it's worth
+ checking failures for all methods in all subclasses.
+
+ TODO: When infrastructure supports @AfterClass errors, move this
+ */
+ thrownInSetUp.clear()
+ throw exception
}
// The following methods dump an exact set of fields from the object to compare, because
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index 3991d8d..80b474f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -146,7 +146,29 @@
assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
+ }
+ @Test
+ public void testPrune_badFileName() {
+ GregorianCalendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(10);
+ int retainDays = 1;
+
+ List<AtomicFile> expectedFiles = new ArrayList<>();
+
+ // add 5 files with a creation date of "today", but the file names are bad
+ for (long i = cal.getTimeInMillis(); i >= 5; i--) {
+ File file = mock(File.class);
+ when(file.getName()).thenReturn(i + ".txt");
+ AtomicFile af = new AtomicFile(file);
+ mDataBase.mHistoryFiles.addLast(af);
+ }
+
+ // trim everything a day+ old
+ cal.add(Calendar.DATE, 1 * retainDays);
+ mDataBase.prune(retainDays, cal.getTimeInMillis());
+
+ assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9cfee34..38b3d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1039,6 +1039,13 @@
assertEquals(config90.orientation, app.getConfiguration().orientation);
assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
+ // Force the negative offset to verify it can be updated.
+ mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1;
+ assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
+ false /* sync */));
+ assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1);
+ assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1);
+
mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
// The animation in old rotation should be cancelled.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4e5be5c..9ae905d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12344,6 +12344,9 @@
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@IsMultiSimSupportedResult
public int isMultiSimSupported() {
+ if (getSupportedModemCount() < 2) {
+ return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
+ }
try {
ITelephony service = getITelephony();
if (service != null) {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index a616c61..6c9ffe2 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -477,4 +477,12 @@
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
assertThat(sm.isCheckpointSupported()).isTrue();
}
+
+ @Test
+ public void hasMainlineModule() throws Exception {
+ String pkgName = getModuleMetadataPackageName();
+ boolean existed = InstrumentationRegistry.getInstrumentation().getContext()
+ .getPackageManager().getModuleInfo(pkgName, 0) != null;
+ assertThat(existed).isTrue();
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 282f012..78775be 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -243,6 +243,7 @@
*/
@Test
public void testRollbackWhitelistedApp() throws Exception {
+ assumeTrue(hasMainlineModule());
runPhase("testRollbackWhitelistedApp_Phase1");
getDevice().reboot();
runPhase("testRollbackWhitelistedApp_Phase2");
@@ -460,4 +461,16 @@
return false;
}
}
+
+ /**
+ * True if this build has mainline modules installed.
+ */
+ private boolean hasMainlineModule() throws Exception {
+ try {
+ runPhase("hasMainlineModule");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
}
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index b4f0daa..8de27e8 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -445,14 +445,20 @@
// Check comparisons work.
LinkProperties lp2 = new LinkProperties(lp);
assertAllRoutesHaveInterface("wlan0", lp2);
- assertEquals(0, lp.compareAllRoutes(lp2).added.size());
- assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+ // LinkProperties#compareAllRoutes exists both in R and before R, but the return type
+ // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q.
+ if (isAtLeastR()) {
+ assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+ }
lp2.setInterfaceName("p2p0");
assertAllRoutesHaveInterface("p2p0", lp2);
assertAllRoutesNotHaveInterface("wlan0", lp2);
- assertEquals(3, lp.compareAllRoutes(lp2).added.size());
- assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+ if (isAtLeastR()) {
+ assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+ assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+ }
// Remove route with incorrect interface, no route removed.
lp.removeRoute(new RouteInfo(prefix2, null, null));
@@ -946,7 +952,7 @@
}
- @Test
+ @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testCompareResult() {
// Either adding or removing items
compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),