diff options
71 files changed, 1221 insertions, 834 deletions
diff --git a/api/current.txt b/api/current.txt index 15c4fdfed178..af8f53f771a9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -13064,6 +13064,10 @@ package android.database.sqlite { method public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String, String); method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String); method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]); + method public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory(); + method public boolean getDistinct(); + method public java.util.Map<java.lang.String,java.lang.String> getProjectionMap(); + method public boolean getStrict(); method public String getTables(); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String); @@ -23408,7 +23412,7 @@ package android.media { method @NonNull public android.media.AudioPresentation.Builder setProgramId(int); } - public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting { + public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { ctor public AudioRecord(int, int, int, int, int) throws java.lang.IllegalArgumentException; method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler); method @Deprecated public void addOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener, android.os.Handler); @@ -23443,6 +23447,8 @@ package android.media { method public void release(); method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener); method @Deprecated public void removeOnRoutingChangedListener(android.media.AudioRecord.OnRoutingChangedListener); + method public boolean setMicrophoneDirection(int); + method public boolean setMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float); method public int setNotificationMarkerPosition(int); method public int setPositionNotificationPeriod(int); method public boolean setPreferredDevice(android.media.AudioDeviceInfo); @@ -26092,6 +26098,15 @@ package android.media { field public static final android.media.MediaTimestamp TIMESTAMP_UNKNOWN; } + public interface MicrophoneDirection { + method public boolean setMicrophoneDirection(int); + method public boolean setMicrophoneFieldDimension(@FloatRange(from=-1.0, to=1.0) float); + field public static final int MIC_DIRECTION_BACK = 2; // 0x2 + field public static final int MIC_DIRECTION_EXTERNAL = 3; // 0x3 + field public static final int MIC_DIRECTION_FRONT = 1; // 0x1 + field public static final int MIC_DIRECTION_UNSPECIFIED = 0; // 0x0 + } + public final class MicrophoneInfo { method @NonNull public String getAddress(); method public java.util.List<android.util.Pair<java.lang.Integer,java.lang.Integer>> getChannelMapping(); @@ -41588,6 +41603,7 @@ package android.service.notification { field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR; field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions"; field public static final String KEY_IMPORTANCE = "key_importance"; + field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; field public static final String KEY_TEXT_REPLIES = "key_text_replies"; field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; } @@ -41645,12 +41661,14 @@ package android.service.notification { method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int); method public final android.os.IBinder onBind(android.content.Intent); method public void onNotificationDirectReplied(@NonNull String); - method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); + method public abstract android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification); method public android.service.notification.Adjustment onNotificationEnqueued(android.service.notification.StatusBarNotification, android.app.NotificationChannel); method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); + method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, String); method public void onNotificationsSeen(java.util.List<java.lang.String>); method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); + method public final void unsnoozeNotification(String); field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; field public static final int SOURCE_FROM_APP = 0; // 0x0 field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 diff --git a/api/system-current.txt b/api/system-current.txt index fb2b9e1e05fb..33b15863b5e2 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3475,7 +3475,7 @@ package android.media { field public static final int PLAYER_TYPE_UNKNOWN = -1; // 0xffffffff } - public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting { + public class AudioRecord implements android.media.AudioRecordingMonitor android.media.AudioRouting android.media.MicrophoneDirection { ctor public AudioRecord(android.media.AudioAttributes, android.media.AudioFormat, int, int) throws java.lang.IllegalArgumentException; } @@ -6522,6 +6522,15 @@ package android.service.euicc { package android.service.notification { + public final class Adjustment implements android.os.Parcelable { + ctor protected Adjustment(android.os.Parcel); + field public static final String KEY_PEOPLE = "key_people"; + } + + public final class NotificationStats implements android.os.Parcelable { + ctor protected NotificationStats(android.os.Parcel); + } + public final class SnoozeCriterion implements android.os.Parcelable { ctor public SnoozeCriterion(String, CharSequence, CharSequence); ctor protected SnoozeCriterion(android.os.Parcel); diff --git a/api/test-current.txt b/api/test-current.txt index 2cb01ead0788..fecb07a29a7f 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -802,6 +802,7 @@ package android.location { public class LocationManager { method public String[] getBackgroundThrottlingWhitelist(); + method public String[] getIgnoreSettingsWhitelist(); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper); method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(android.location.LocationRequest, android.app.PendingIntent); method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setLocationEnabledForUser(boolean, android.os.UserHandle); diff --git a/cmds/statsd/src/FieldValue.cpp b/cmds/statsd/src/FieldValue.cpp index 80ed80776829..13f5c8ae5fd8 100644 --- a/cmds/statsd/src/FieldValue.cpp +++ b/cmds/statsd/src/FieldValue.cpp @@ -19,6 +19,7 @@ #include "FieldValue.h" #include "HashableDimensionKey.h" #include "math.h" +#include "statslog.h" namespace android { namespace os { @@ -122,6 +123,24 @@ bool isAttributionUidField(const FieldValue& value) { return false; } +int32_t getUidIfExists(const FieldValue& value) { + bool isUid = false; + // the field is uid field if the field is the uid field in attribution node or marked as + // is_uid in atoms.proto + if (isAttributionUidField(value)) { + isUid = true; + } else { + auto it = android::util::AtomsInfo::kAtomsWithUidField.find(value.mField.getTag()); + if (it != android::util::AtomsInfo::kAtomsWithUidField.end()) { + int uidField = it->second; // uidField is the field number in proto + isUid = value.mField.getDepth() == 0 && value.mField.getPosAtDepth(0) == uidField && + value.mValue.getType() == INT; + } + } + + return isUid ? value.mValue.int_value : -1; +} + bool isAttributionUidField(const Field& field, const Value& value) { int f = field.getField() & 0xff007f; if (f == 0x10001 && value.getType() == INT) { diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h index a5d00ac4e72b..6729e052b5ee 100644 --- a/cmds/statsd/src/FieldValue.h +++ b/cmds/statsd/src/FieldValue.h @@ -386,6 +386,9 @@ bool HasPositionALL(const FieldMatcher& matcher); bool isAttributionUidField(const FieldValue& value); +/* returns uid if the field is uid field, or -1 if the field is not a uid field */ +int getUidIfExists(const FieldValue& value); + void translateFieldMatcher(const FieldMatcher& matcher, std::vector<Matcher>* output); bool isAttributionUidField(const Field& field, const Value& value); diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index fb603b9ce163..c542b6215c88 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -159,7 +159,7 @@ StatsService::StatsService(const sp<Looper>& handlerLooper) } })) { - mUidMap = new UidMap(); + mUidMap = UidMap::getInstance(); mPullerManager = new StatsPullerManager(); StatsPuller::SetUidMap(mUidMap); mConfigManager = new ConfigManager(); diff --git a/cmds/statsd/src/anomaly/AlarmTracker.cpp b/cmds/statsd/src/anomaly/AlarmTracker.cpp index 8d7369935ea4..019a9f7e5f9a 100644 --- a/cmds/statsd/src/anomaly/AlarmTracker.cpp +++ b/cmds/statsd/src/anomaly/AlarmTracker.cpp @@ -78,8 +78,8 @@ void AlarmTracker::informAlarmsFired( } if (!mSubscriptions.empty()) { VLOG("AlarmTracker triggers the subscribers."); - triggerSubscribers(mAlarmConfig.id(), DEFAULT_METRIC_DIMENSION_KEY, mConfigKey, - mSubscriptions); + triggerSubscribers(mAlarmConfig.id(), 0 /*metricId N/A*/, DEFAULT_METRIC_DIMENSION_KEY, + 0 /* metricValue N/A */, mConfigKey, mSubscriptions); } firedAlarms.erase(mInternalAlarm); mAlarmSec = findNextAlarmSec((timestampNs-1) / NS_PER_SEC + 1); // round up diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index ee111cddcfd7..d1dcb5df7838 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -207,7 +207,8 @@ bool AnomalyTracker::detectAnomaly(const int64_t& currentBucketNum, getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt(); } -void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, const MetricDimensionKey& key) { +void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, int64_t metricId, + const MetricDimensionKey& key, int64_t metricValue) { // TODO(b/110563466): Why receive timestamp? RefractoryPeriod should always be based on // real time right now. if (isInRefractoryPeriod(timestampNs, key)) { @@ -225,7 +226,7 @@ void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, const MetricDime if (!mSubscriptions.empty()) { ALOGI("An anomaly (%lld) %s has occurred! Informing subscribers.", mAlert.id(), key.toString().c_str()); - informSubscribers(key); + informSubscribers(key, metricId, metricValue); } else { ALOGI("An anomaly has occurred! (But no subscriber for that alert.)"); } @@ -238,11 +239,11 @@ void AnomalyTracker::declareAnomaly(const int64_t& timestampNs, const MetricDime } void AnomalyTracker::detectAndDeclareAnomaly(const int64_t& timestampNs, - const int64_t& currBucketNum, + const int64_t& currBucketNum, int64_t metricId, const MetricDimensionKey& key, const int64_t& currentBucketValue) { if (detectAnomaly(currBucketNum, key, currentBucketValue)) { - declareAnomaly(timestampNs, key); + declareAnomaly(timestampNs, metricId, key, currentBucketValue); } } @@ -255,8 +256,9 @@ bool AnomalyTracker::isInRefractoryPeriod(const int64_t& timestampNs, return false; } -void AnomalyTracker::informSubscribers(const MetricDimensionKey& key) { - triggerSubscribers(mAlert.id(), key, mConfigKey, mSubscriptions); +void AnomalyTracker::informSubscribers(const MetricDimensionKey& key, int64_t metric_id, + int64_t metricValue) { + triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions); } } // namespace statsd diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index 927e2df1b53a..e9414735b82b 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -67,14 +67,16 @@ public: const int64_t& currentBucketValue); // Informs incidentd about the detected alert. - void declareAnomaly(const int64_t& timestampNs, const MetricDimensionKey& key); + void declareAnomaly(const int64_t& timestampNs, int64_t metricId, const MetricDimensionKey& key, + int64_t metricValue); // Detects if, based on past buckets plus the new currentBucketValue (which generally // represents the partially-filled current bucket), an anomaly has happened, and if so, // declares an anomaly and informs relevant subscribers. // Also advances to currBucketNum-1. void detectAndDeclareAnomaly(const int64_t& timestampNs, const int64_t& currBucketNum, - const MetricDimensionKey& key, const int64_t& currentBucketValue); + int64_t metricId, const MetricDimensionKey& key, + const int64_t& currentBucketValue); // Init the AlarmMonitor which is shared across anomaly trackers. virtual void setAlarmMonitor(const sp<AlarmMonitor>& alarmMonitor) { @@ -176,7 +178,7 @@ protected: virtual void resetStorage(); // Informs the subscribers (incidentd, perfetto, broadcasts, etc) that an anomaly has occurred. - void informSubscribers(const MetricDimensionKey& key); + void informSubscribers(const MetricDimensionKey& key, int64_t metricId, int64_t metricValue); FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp index 3acfd171848e..2b56810170e5 100644 --- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp @@ -65,7 +65,9 @@ void DurationAnomalyTracker::stopAlarm(const MetricDimensionKey& dimensionKey, // If the alarm is set in the past but hasn't fired yet (due to lag), catch it now. if (itr->second != nullptr && timestampNs >= (int64_t)NS_PER_SEC * itr->second->timestampSec) { - declareAnomaly(timestampNs, dimensionKey); + declareAnomaly(timestampNs, mAlert.metric_id(), dimensionKey, + mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - + itr->second->timestampSec); } if (mAlarmMonitor != nullptr) { mAlarmMonitor->remove(itr->second); @@ -100,7 +102,9 @@ void DurationAnomalyTracker::informAlarmsFired(const int64_t& timestampNs, // Now declare each of these alarms to have fired. for (const auto& kv : matchedAlarms) { - declareAnomaly(timestampNs, kv.first); + declareAnomaly( + timestampNs, mAlert.metric_id(), kv.first, + mAlert.trigger_if_sum_gt() + (timestampNs / NS_PER_SEC) - kv.second->timestampSec); mAlarms.erase(kv.first); firedAlarms.erase(kv.second); // No one else can also own it, so we're done with it. } diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp index 6b46b8b3b900..548a6869436d 100644 --- a/cmds/statsd/src/anomaly/subscriber_util.cpp +++ b/cmds/statsd/src/anomaly/subscriber_util.cpp @@ -30,9 +30,8 @@ namespace android { namespace os { namespace statsd { -void triggerSubscribers(const int64_t rule_id, - const MetricDimensionKey& dimensionKey, - const ConfigKey& configKey, +void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionKey& dimensionKey, + int64_t metricValue, const ConfigKey& configKey, const std::vector<Subscription>& subscriptions) { VLOG("informSubscribers called."); if (subscriptions.empty()) { @@ -50,13 +49,14 @@ void triggerSubscribers(const int64_t rule_id, } switch (subscription.subscriber_information_case()) { case Subscription::SubscriberInformationCase::kIncidentdDetails: - if (!GenerateIncidentReport(subscription.incidentd_details(), rule_id, configKey)) { + if (!GenerateIncidentReport(subscription.incidentd_details(), ruleId, metricId, + dimensionKey, metricValue, configKey)) { ALOGW("Failed to generate incident report."); } break; case Subscription::SubscriberInformationCase::kPerfettoDetails: if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details(), - subscription.id(), rule_id, configKey)) { + subscription.id(), ruleId, configKey)) { ALOGW("Failed to generate perfetto traces."); } break; @@ -66,7 +66,7 @@ void triggerSubscribers(const int64_t rule_id, break; case Subscription::SubscriberInformationCase::kPerfprofdDetails: if (!CollectPerfprofdTraceAndUploadToDropbox(subscription.perfprofd_details(), - rule_id, configKey)) { + ruleId, configKey)) { ALOGW("Failed to generate perfprofd traces."); } break; @@ -76,7 +76,6 @@ void triggerSubscribers(const int64_t rule_id, } } - } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/anomaly/subscriber_util.h b/cmds/statsd/src/anomaly/subscriber_util.h index dba8981a72aa..1df3c8991f94 100644 --- a/cmds/statsd/src/anomaly/subscriber_util.h +++ b/cmds/statsd/src/anomaly/subscriber_util.h @@ -24,10 +24,9 @@ namespace android { namespace os { namespace statsd { -void triggerSubscribers(const int64_t rule_id, - const MetricDimensionKey& dimensionKey, - const ConfigKey& configKey, - const std::vector<Subscription>& subscriptions); +void triggerSubscribers(const int64_t ruleId, const int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, + const ConfigKey& configKey, const std::vector<Subscription>& subscriptions); } // namespace statsd } // namespace os diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 5707de544648..e84f88d407d3 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -303,7 +303,7 @@ void CountMetricProducer::onMatchedLogEventInternalLocked( if (prev != mCurrentFullCounters->end()) { countWholeBucket += prev->second; } - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, countWholeBucket); } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 837d532f7e09..63017936b1db 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -485,8 +485,8 @@ void GaugeMetricProducer::onMatchedLogEventInternalLocked( gaugeVal = value.long_value; } for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, - gaugeVal); + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, + eventKey, gaugeVal); } } } diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index bc7c8727284f..3cf378d7d7ce 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -697,8 +697,8 @@ void ValueMetricProducer::onMatchedLogEventInternalLocked(const size_t matcherIn wholeBucketVal += prev->second; } for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly( - eventTimeNs, mCurrentBucketNum, eventKey, wholeBucketVal); + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, mMetricId, eventKey, + wholeBucketVal); } } } diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h index ccb1d4359e89..081e61ed21fa 100644 --- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h +++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h @@ -155,8 +155,8 @@ protected: const int64_t& currentBucketValue) { for (auto& anomalyTracker : mAnomalyTrackers) { if (anomalyTracker != nullptr) { - anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mEventKey, - currentBucketValue); + anomalyTracker->detectAndDeclareAnomaly(timestamp, currBucketNum, mTrackerId, + mEventKey, currentBucketValue); } } } diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp index 5cf012638dce..d4b57dd68134 100644 --- a/cmds/statsd/src/packages/UidMap.cpp +++ b/cmds/statsd/src/packages/UidMap.cpp @@ -73,6 +73,11 @@ UidMap::UidMap() : mBytesUsed(0) {} UidMap::~UidMap() {} +sp<UidMap> UidMap::getInstance() { + static sp<UidMap> sInstance = new UidMap(); + return sInstance; +} + bool UidMap::hasApp(int uid, const string& packageName) const { lock_guard<mutex> lock(mMutex); @@ -336,6 +341,61 @@ size_t UidMap::getBytesUsed() const { return mBytesUsed; } +void UidMap::writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, const std::set<int32_t>& interestingUids, + std::set<string>* str_set, ProtoOutputStream* proto) { + lock_guard<mutex> lock(mMutex); + + writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, interestingUids, + str_set, proto); +} + +void UidMap::writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, + const std::set<int32_t>& interestingUids, + std::set<string>* str_set, ProtoOutputStream* proto) { + proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_TIMESTAMP, (long long)timestamp); + for (const auto& kv : mMap) { + if (!interestingUids.empty() && + interestingUids.find(kv.first.first) == interestingUids.end()) { + continue; + } + uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | + FIELD_ID_SNAPSHOT_PACKAGE_INFO); + if (str_set != nullptr) { + str_set->insert(kv.first.second); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH, + (long long)Hash64(kv.first.second)); + if (includeVersionStrings) { + str_set->insert(kv.second.versionString); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH, + (long long)Hash64(kv.second.versionString)); + } + if (includeInstaller) { + str_set->insert(kv.second.installer); + proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH, + (long long)Hash64(kv.second.installer)); + } + } else { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second); + if (includeVersionStrings) { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING, + kv.second.versionString); + } + if (includeInstaller) { + proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER, + kv.second.installer); + } + } + + proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION, + (long long)kv.second.versionCode); + proto->write(FIELD_TYPE_INT32 | FIELD_ID_SNAPSHOT_PACKAGE_UID, kv.first.first); + proto->write(FIELD_TYPE_BOOL | FIELD_ID_SNAPSHOT_PACKAGE_DELETED, kv.second.deleted); + proto->end(token); + } +} + void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::set<string>* str_set, bool includeVersionStrings, bool includeInstaller, ProtoOutputStream* proto) { @@ -381,43 +441,9 @@ void UidMap::appendUidMap(const int64_t& timestamp, const ConfigKey& key, std::s // Write snapshot from current uid map state. uint64_t snapshotsToken = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_SNAPSHOTS); - proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_TIMESTAMP, (long long)timestamp); - for (const auto& kv : mMap) { - uint64_t token = proto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | - FIELD_ID_SNAPSHOT_PACKAGE_INFO); - - if (str_set != nullptr) { - str_set->insert(kv.first.second); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_NAME_HASH, - (long long)Hash64(kv.first.second)); - if (includeVersionStrings) { - str_set->insert(kv.second.versionString); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING_HASH, - (long long)Hash64(kv.second.versionString)); - } - if (includeInstaller) { - str_set->insert(kv.second.installer); - proto->write(FIELD_TYPE_UINT64 | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER_HASH, - (long long)Hash64(kv.second.installer)); - } - } else { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_NAME, kv.first.second); - if (includeVersionStrings) { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_VERSION_STRING, - kv.second.versionString); - } - if (includeInstaller) { - proto->write(FIELD_TYPE_STRING | FIELD_ID_SNAPSHOT_PACKAGE_INSTALLER, - kv.second.installer); - } - } - - proto->write(FIELD_TYPE_INT64 | FIELD_ID_SNAPSHOT_PACKAGE_VERSION, - (long long)kv.second.versionCode); - proto->write(FIELD_TYPE_INT32 | FIELD_ID_SNAPSHOT_PACKAGE_UID, kv.first.first); - proto->write(FIELD_TYPE_BOOL | FIELD_ID_SNAPSHOT_PACKAGE_DELETED, kv.second.deleted); - proto->end(token); - } + writeUidMapSnapshotLocked(timestamp, includeVersionStrings, includeInstaller, + std::set<int32_t>() /*empty uid set means including every uid*/, + str_set, proto); proto->end(snapshotsToken); int64_t prevMin = getMinimumTimestampNs(); diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h index 75ff507ef09a..a7c5fb27375c 100644 --- a/cmds/statsd/src/packages/UidMap.h +++ b/cmds/statsd/src/packages/UidMap.h @@ -91,6 +91,8 @@ public: UidMap(); ~UidMap(); static const std::map<std::string, uint32_t> sAidToUidMapping; + + static sp<UidMap> getInstance(); /* * All three inputs must be the same size, and the jth element in each array refers to the same * tuple, ie. uid[j] corresponds to packageName[j] with versionCode[j]. @@ -152,12 +154,25 @@ public: std::set<int32_t> getAppUid(const string& package) const; + // Write current PackageInfoSnapshot to ProtoOutputStream. + // interestingUids: If not empty, only write the package info for these uids. If empty, write + // package info for all uids. + // str_set: if not null, add new string to the set and write str_hash to proto + // if null, write string to proto. + void writeUidMapSnapshot(int64_t timestamp, bool includeVersionStrings, bool includeInstaller, + const std::set<int32_t>& interestingUids, std::set<string>* str_set, + ProtoOutputStream* proto); + private: std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const; string normalizeAppName(const string& appName) const; void getListenerListCopyLocked(std::vector<wp<PackageInfoListener>>* output); + void writeUidMapSnapshotLocked(int64_t timestamp, bool includeVersionStrings, + bool includeInstaller, const std::set<int32_t>& interestingUids, + std::set<string>* str_set, ProtoOutputStream* proto); + mutable mutex mMutex; mutable mutex mIsolatedMutex; diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 09b8fed60dcc..623d8bc06e38 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -457,3 +457,17 @@ message StatsdStatsReport { } repeated LogLossStats detected_log_loss = 16; } + +message AlertTriggerDetails { + message MetricValue { + optional int64 metric_id = 1; + optional DimensionsValue dimension_in_what = 2; + optional DimensionsValue dimension_in_condition = 3; + optional int64 value = 4; + } + oneof value { + MetricValue trigger_metric = 1; + EventMetricData trigger_event = 2; + } + optional UidMapping.PackageInfoSnapshot package_info = 3; +} diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp index 42cac0cc4122..0ed2d75802da 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp +++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp @@ -16,7 +16,10 @@ #define DEBUG false #include "Log.h" +#include "FieldValue.h" #include "IncidentdReporter.h" +#include "packages/UidMap.h" +#include "stats_log_util.h" #include <android/os/IIncidentManager.h> #include <android/os/IncidentReportArgs.h> @@ -43,8 +46,18 @@ const int FIELD_ID_CONFIG_KEY = 3; const int FIELD_ID_CONFIG_KEY_UID = 1; const int FIELD_ID_CONFIG_KEY_ID = 2; +const int FIELD_ID_TRIGGER_DETAILS = 4; +const int FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC = 1; +const int FIELD_ID_METRIC_VALUE_METRIC_ID = 1; +const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT = 2; +const int FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION = 3; +const int FIELD_ID_METRIC_VALUE_VALUE = 4; + +const int FIELD_ID_PACKAGE_INFO = 3; + namespace { -void getProtoData(const int64_t& rule_id, const ConfigKey& configKey, vector<uint8_t>* protoData) { +void getProtoData(const int64_t& rule_id, int64_t metricId, const MetricDimensionKey& dimensionKey, + int64_t metricValue, const ConfigKey& configKey, vector<uint8_t>* protoData) { ProtoOutputStream headerProto; headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_ALERT_ID, (long long)rule_id); uint64_t token = @@ -53,6 +66,58 @@ void getProtoData(const int64_t& rule_id, const ConfigKey& configKey, vector<uin headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_CONFIG_KEY_ID, (long long)configKey.GetId()); headerProto.end(token); + token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS); + + // MetricValue trigger_metric = 1; + uint64_t metricToken = + headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_TRIGGER_DETAILS_TRIGGER_METRIC); + // message MetricValue { + // optional int64 metric_id = 1; + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_METRIC_ID, (long long)metricId); + // optional DimensionsValue dimension_in_what = 2; + uint64_t dimToken = + headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_WHAT); + writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), nullptr, &headerProto); + headerProto.end(dimToken); + + // optional DimensionsValue dimension_in_condition = 3; + dimToken = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_METRIC_VALUE_DIMENSION_IN_CONDITION); + writeDimensionToProto(dimensionKey.getDimensionKeyInCondition(), nullptr, &headerProto); + headerProto.end(dimToken); + + // optional int64 value = 4; + headerProto.write(FIELD_TYPE_INT64 | FIELD_ID_METRIC_VALUE_VALUE, (long long)metricValue); + + // } + headerProto.end(metricToken); + + // write relevant uid package info + std::set<int32_t> uids; + + for (const auto& dim : dimensionKey.getDimensionKeyInWhat().getValues()) { + int uid = getUidIfExists(dim); + // any uid <= 2000 are predefined AID_* + if (uid > 2000) { + uids.insert(uid); + } + } + + for (const auto& dim : dimensionKey.getDimensionKeyInCondition().getValues()) { + int uid = getUidIfExists(dim); + if (uid > 2000) { + uids.insert(uid); + } + } + + if (!uids.empty()) { + uint64_t token = headerProto.start(FIELD_TYPE_MESSAGE | FIELD_ID_PACKAGE_INFO); + UidMap::getInstance()->writeUidMapSnapshot(getElapsedRealtimeNs(), true, true, uids, + nullptr /*string set*/, &headerProto); + headerProto.end(token); + } + + headerProto.end(token); + protoData->resize(headerProto.size()); size_t pos = 0; auto iter = headerProto.data(); @@ -65,7 +130,8 @@ void getProtoData(const int64_t& rule_id, const ConfigKey& configKey, vector<uin } } // namespace -bool GenerateIncidentReport(const IncidentdDetails& config, const int64_t& rule_id, +bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, const ConfigKey& configKey) { if (config.section_size() == 0) { VLOG("The alert %lld contains zero section in config(%d,%lld)", (unsigned long long)rule_id, @@ -76,7 +142,7 @@ bool GenerateIncidentReport(const IncidentdDetails& config, const int64_t& rule_ IncidentReportArgs incidentReport; vector<uint8_t> protoData; - getProtoData(rule_id, configKey, &protoData); + getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey, &protoData); incidentReport.addHeader(protoData); for (int i = 0; i < config.section_size(); i++) { diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.h b/cmds/statsd/src/subscriber/IncidentdReporter.h index 1b83fe23de8f..e78a4d98dcd8 100644 --- a/cmds/statsd/src/subscriber/IncidentdReporter.h +++ b/cmds/statsd/src/subscriber/IncidentdReporter.h @@ -16,6 +16,7 @@ #pragma once +#include "HashableDimensionKey.h" #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert, IncidentdDetails @@ -26,7 +27,8 @@ namespace statsd { /** * Calls incidentd to trigger an incident report and put in dropbox for uploading. */ -bool GenerateIncidentReport(const IncidentdDetails& config, const int64_t& rule_id, +bool GenerateIncidentReport(const IncidentdDetails& config, int64_t rule_id, int64_t metricId, + const MetricDimensionKey& dimensionKey, int64_t metricValue, const ConfigKey& configKey); } // namespace statsd diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp index 960fbdab1c15..c10703c36b98 100644 --- a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp +++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp @@ -90,7 +90,8 @@ void detectAndDeclareAnomalies(AnomalyTracker& tracker, const std::shared_ptr<DimToValMap>& bucket, const int64_t& eventTimestamp) { for (const auto& kv : *bucket) { - tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, kv.first, kv.second); + tracker.detectAndDeclareAnomaly(eventTimestamp, bucketNum, 0 /*metric_id*/, kv.first, + kv.second); } } diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index a554882123f1..46316e1a254b 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -229,7 +229,11 @@ public class UiModeManager { * <strong>Note:</strong> On API 22 and below, changes to the night mode * are only effective when the {@link Configuration#UI_MODE_TYPE_CAR car} * or {@link Configuration#UI_MODE_TYPE_DESK desk} mode is enabled on a - * device. Starting in API 23, changes to night mode are always effective. + * device. On API 23 through API 28, changes to night mode are always effective. + * <p> + * Starting in API 29, when the device is in car mode and this method is called, night mode + * will change, but the new setting is not persisted and the previously persisted setting + * will be restored when the device exits car mode. * <p> * Changes to night mode take effect globally and will result in a configuration change * (and potentially an Activity lifecycle event) being applied to all running apps. diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 5722e7b25fbf..ea489c456f3d 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -77,6 +77,14 @@ public class SQLiteQueryBuilder { } /** + * Get if the query is marked as DISTINCT, as last configured by + * {@link #setDistinct(boolean)}. + */ + public boolean getDistinct() { + return mDistinct; + } + + /** * Returns the list of tables being queried * * @return the list of tables being queried @@ -167,6 +175,14 @@ public class SQLiteQueryBuilder { } /** + * Gets the projection map for the query, as last configured by + * {@link #setProjectionMap(Map)}. + */ + public Map<String, String> getProjectionMap() { + return mProjectionMap; + } + + /** * Sets a projection greylist of columns that will be allowed through, even * when {@link #setStrict(boolean)} is enabled. This provides a way for * abusive custom columns like {@code COUNT(*)} to continue working. @@ -178,6 +194,16 @@ public class SQLiteQueryBuilder { } /** + * Gets the projection greylist for the query, as last configured by + * {@link #setProjectionGreylist(List)}. + * + * @hide + */ + public List<Pattern> getProjectionGreylist() { + return mProjectionGreylist; + } + + /** * Sets the cursor factory to be used for the query. You can use * one factory for all queries on a database but it is normally * easier to specify the factory when doing this query. @@ -189,6 +215,14 @@ public class SQLiteQueryBuilder { } /** + * Sets the cursor factory to be used for the query, as last configured by + * {@link #setCursorFactory(android.database.sqlite.SQLiteDatabase.CursorFactory)}. + */ + public SQLiteDatabase.CursorFactory getCursorFactory() { + return mFactory; + } + + /** * When set, the selection is verified against malicious arguments. * When using this class to create a statement using * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, @@ -214,6 +248,14 @@ public class SQLiteQueryBuilder { } /** + * Get if the query is marked as strict, as last configured by + * {@link #setStrict(boolean)}. + */ + public boolean getStrict() { + return mStrict; + } + + /** * Build an SQL query string from the given clauses. * * @param distinct true if you want each row to be unique, false otherwise. diff --git a/core/java/android/provider/BaseColumns.java b/core/java/android/provider/BaseColumns.java index f594c19d8eca..00c9e72df880 100644 --- a/core/java/android/provider/BaseColumns.java +++ b/core/java/android/provider/BaseColumns.java @@ -16,17 +16,18 @@ package android.provider; -public interface BaseColumns -{ +import android.database.Cursor; + +public interface BaseColumns { /** * The unique ID for a row. - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String _ID = "_id"; /** * The count of rows in a directory. - * <P>Type: INTEGER</P> */ + // @Column(Cursor.FIELD_TYPE_INTEGER) public static final String _COUNT = "_count"; } diff --git a/core/java/android/provider/Column.java b/core/java/android/provider/Column.java new file mode 100644 index 000000000000..1364fb86cbd6 --- /dev/null +++ b/core/java/android/provider/Column.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Denotes that a field is a {@link ContentProvider} column. It can be used as a + * key for {@link ContentValues} when inserting or updating data, or as a + * projection when querying. + * + * @hide + */ +@Documented +@Retention(RUNTIME) +@Target({FIELD}) +public @interface Column { + /** + * The {@link Cursor#getType(int)} of the data stored in this column. + */ + int value(); + + /** + * This column is read-only and cannot be defined during insert or updates. + */ + boolean readOnly() default false; +} diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 0b3842080a75..1b10bef76067 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -857,8 +857,6 @@ public final class MediaStore { * this path. Instead of trying to open this path directly, apps should * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain * access. - * <p> - * Type: TEXT * * @deprecated Apps may not have filesystem permissions to directly * access this path. Instead of trying to open this path @@ -869,6 +867,7 @@ public final class MediaStore { * {@link android.os.Build.VERSION_CODES#Q} or higher. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String DATA = "_data"; /** @@ -883,44 +882,44 @@ public final class MediaStore { * If you require the hash of a specific item, you can call * {@link ContentResolver#canonicalize(Uri)}, which will block until the * hash is calculated. - * <p> - * Type: BLOB + * * @removed */ @Deprecated + @Column(value = Cursor.FIELD_TYPE_BLOB, readOnly = true) public static final String HASH = "_hash"; /** * The size of the file in bytes - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String SIZE = "_size"; /** * The display name of the file - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String DISPLAY_NAME = "_display_name"; /** * The title of the content - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String TITLE = "title"; /** * The time the file was added to the media provider * Units are seconds since 1970. - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_ADDED = "date_added"; /** * The time the file was last modified * Units are seconds since 1970. * NOTE: This is for internal use by the media scanner. Do not modify this field. - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_MODIFIED = "date_modified"; /** @@ -938,9 +937,8 @@ public final class MediaStore { * {@code format} of {@code audio/ogg} would be ignored. * <p> * This is a read-only column that is automatically computed. - * <p> - * Type: TEXT */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String MIME_TYPE = "mime_type"; /** @@ -948,35 +946,34 @@ public final class MediaStore { * Used to pass the new file's object handle through the media scanner * from MTP to the media provider * For internal use only by MTP, media scanner and media provider. - * <P>Type: INTEGER</P> * @hide */ + @Deprecated + // @Column(Cursor.FIELD_TYPE_INTEGER) public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id"; /** * Non-zero if the media file is drm-protected - * <P>Type: INTEGER (boolean)</P> * @hide */ @UnsupportedAppUsage + @Deprecated + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IS_DRM = "is_drm"; /** * Flag indicating if a media item is pending, and still being inserted * by its owner. - * <p> - * Type: BOOLEAN * * @see MediaColumns#IS_PENDING * @see MediaStore#setIncludePending(Uri) * @see MediaStore#createPending(Context, PendingParams) */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IS_PENDING = "is_pending"; /** * Flag indicating if a media item is trashed. - * <p> - * Type: BOOLEAN * * @see MediaColumns#IS_TRASHED * @see MediaStore#setIncludeTrashed(Uri) @@ -985,57 +982,54 @@ public final class MediaStore { * @removed */ @Deprecated + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IS_TRASHED = "is_trashed"; /** * The time the file should be considered expired. Units are seconds * since 1970. Typically only meaningful in the context of * {@link #IS_PENDING} or {@link #IS_TRASHED}. - * <p> - * Type: INTEGER * @removed */ @Deprecated + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String DATE_EXPIRES = "date_expires"; /** * The width of the image/video in pixels. */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String WIDTH = "width"; /** * The height of the image/video in pixels. */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String HEIGHT = "height"; /** * Package name that contributed this media. The value may be * {@code NULL} if ownership cannot be reliably determined. - * <p> - * This is a read-only column that is automatically computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String OWNER_PACKAGE_NAME = "owner_package_name"; /** * The primary directory name this media exists under. The value may be * {@code NULL} if the media doesn't have a primary directory name. - * <p> - * Type: TEXT * * @see PendingParams#setPrimaryDirectory(String) */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String PRIMARY_DIRECTORY = "primary_directory"; /** * The secondary directory name this media exists under. The value may * be {@code NULL} if the media doesn't have a secondary directory name. - * <p> - * Type: TEXT * * @see PendingParams#setSecondaryDirectory(String) */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String SECONDARY_DIRECTORY = "secondary_directory"; /** @@ -1046,11 +1040,8 @@ public final class MediaStore { * <p> * Each "document ID" is created once for each new resource. Different * renditions of that resource are expected to have different IDs. - * <p> - * This is a read-only column that is automatically computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String DOCUMENT_ID = "document_id"; /** @@ -1061,11 +1052,8 @@ public final class MediaStore { * <p> * This "instance ID" changes with each save operation of a specific * "document ID". - * <p> - * This is a read-only column that is automatically computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String INSTANCE_ID = "instance_id"; /** @@ -1077,11 +1065,8 @@ public final class MediaStore { * For example, when you save a PSD document as a JPEG, then convert the * JPEG to GIF format, the "original document ID" of both the JPEG and * GIF files is the "document ID" of the original PSD file. - * <p> - * This is a read-only column that is automatically computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ORIGINAL_DOCUMENT_ID = "original_document_id"; } @@ -1172,43 +1157,46 @@ public final class MediaStore { public interface FileColumns extends MediaColumns { /** * The MTP storage ID of the file - * <P>Type: INTEGER</P> * @hide */ @UnsupportedAppUsage + @Deprecated + // @Column(Cursor.FIELD_TYPE_INTEGER) public static final String STORAGE_ID = "storage_id"; /** * The MTP format code of the file - * <P>Type: INTEGER</P> * @hide */ @UnsupportedAppUsage + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String FORMAT = "format"; /** * The index of the parent directory of the file - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String PARENT = "parent"; /** * The MIME type of the file * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String MIME_TYPE = "mime_type"; /** * The title of the content * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String TITLE = "title"; /** * The media type (audio, video, image or playlist) * of the file, or 0 for not a media file - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String MEDIA_TYPE = "media_type"; /** @@ -1241,6 +1229,7 @@ public final class MediaStore { * Column indicating if the file is part of Downloads collection. * @hide */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_DOWNLOAD = "is_download"; } } @@ -1260,23 +1249,20 @@ public final class MediaStore { public interface DownloadColumns extends MediaColumns { /** * Uri indicating where the file has been downloaded from. - * <p> - * Type: TEXT */ + @Column(Cursor.FIELD_TYPE_STRING) String DOWNLOAD_URI = "download_uri"; /** * Uri indicating HTTP referer of {@link #DOWNLOAD_URI}. - * <p> - * Type: TEXT */ + @Column(Cursor.FIELD_TYPE_STRING) String REFERER_URI = "referer_uri"; /** * The description of the download. - * <p> - * Type: Text */ + @Column(Cursor.FIELD_TYPE_STRING) String DESCRIPTION = "description"; } @@ -1442,29 +1428,28 @@ public final class MediaStore { public interface ImageColumns extends MediaColumns { /** * The description of the image - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String DESCRIPTION = "description"; /** * The picasa id of the image - * <P>Type: TEXT</P> * * @deprecated this value was only relevant for images hosted on * Picasa, which are no longer supported. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String PICASA_ID = "picasa_id"; /** * Whether the video should be published as public or private - * <P>Type: INTEGER</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IS_PRIVATE = "isprivate"; /** * The latitude where the image was captured. - * <P>Type: DOUBLE</P> * * @deprecated location details are no longer indexed for privacy * reasons, and this value is now always {@code null}. @@ -1472,11 +1457,11 @@ public final class MediaStore { * {@link ExifInterface#getLatLong(float[])}. */ @Deprecated + @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) public static final String LATITUDE = "latitude"; /** * The longitude where the image was captured. - * <P>Type: DOUBLE</P> * * @deprecated location details are no longer indexed for privacy * reasons, and this value is now always {@code null}. @@ -1484,40 +1469,40 @@ public final class MediaStore { * {@link ExifInterface#getLatLong(float[])}. */ @Deprecated + @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) public static final String LONGITUDE = "longitude"; /** * The date & time that the image was taken in units * of milliseconds since jan 1, 1970. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_TAKEN = "datetaken"; /** * The orientation for the image expressed as degrees. * Only degrees 0, 90, 180, 270 will work. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String ORIENTATION = "orientation"; /** * The mini thumb id. - * <P>Type: INTEGER</P> * * @deprecated all thumbnails should be obtained via * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this * value is no longer supported. */ @Deprecated + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; /** * The primary bucket ID of this media item. This can be useful to * present the user a first-level clustering of related media items. * This is a read-only column that is automatically computed. - * <p> - * Type: INTEGER */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String BUCKET_ID = "bucket_id"; /** @@ -1525,9 +1510,8 @@ public final class MediaStore { * useful to present the user a first-level clustering of related * media items. This is a read-only column that is automatically * computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; /** @@ -1541,9 +1525,8 @@ public final class MediaStore { * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG} * will have the same {@link #GROUP_ID} because the first portion of * their filenames is identical. - * <p> - * Type: INTEGER */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String GROUP_ID = "group_id"; } @@ -1848,8 +1831,6 @@ public final class MediaStore { * apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain * access. - * <p> - * Type: TEXT * * @deprecated Apps may not have filesystem permissions to directly * access this path. Instead of trying to open this path @@ -1860,39 +1841,45 @@ public final class MediaStore { * {@link android.os.Build.VERSION_CODES#Q} or higher. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String DATA = "_data"; /** * The original image for the thumbnal - * <P>Type: INTEGER (ID from Images table)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IMAGE_ID = "image_id"; /** * The kind of the thumbnail - * <P>Type: INTEGER (One of the values below)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String KIND = "kind"; public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; + /** * The blob raw data of thumbnail - * <P>Type: DATA STREAM</P> + * + * @deprecated this column never existed internally, and could never + * have returned valid data. */ + @Deprecated + @Column(Cursor.FIELD_TYPE_BLOB) public static final String THUMB_DATA = "thumb_data"; /** * The width of the thumbnal - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String WIDTH = "width"; /** * The height of the thumbnail - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String HEIGHT = "height"; } } @@ -1909,79 +1896,80 @@ public final class MediaStore { /** * A non human readable key calculated from the TITLE, used for * searching, sorting and grouping - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String TITLE_KEY = "title_key"; /** * The duration of the audio file, in ms - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DURATION = "duration"; /** * The position, in ms, playback was at when playback for this file * was last stopped. - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String BOOKMARK = "bookmark"; /** * The id of the artist who created the audio file, if any - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String ARTIST_ID = "artist_id"; /** * The artist who created the audio file, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST = "artist"; /** * The artist credited for the album that contains the audio file - * <P>Type: TEXT</P> * @hide */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM_ARTIST = "album_artist"; /** * Whether the song is part of a compilation - * <P>Type: TEXT</P> * @hide */ + @Deprecated + // @Column(Cursor.FIELD_TYPE_STRING) public static final String COMPILATION = "compilation"; /** * A non human readable key calculated from the ARTIST, used for * searching, sorting and grouping - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST_KEY = "artist_key"; /** * The composer of the audio file, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String COMPOSER = "composer"; /** * The id of the album the audio file is from, if any - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String ALBUM_ID = "album_id"; /** * The album the audio file is from, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM = "album"; /** * A non human readable key calculated from the ALBUM, used for * searching, sorting and grouping - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM_KEY = "album_key"; /** @@ -1990,63 +1978,63 @@ public final class MediaStore { * disc number. For multi-disc sets, this number will * be 1xxx for tracks on the first disc, 2xxx for tracks * on the second disc, etc. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String TRACK = "track"; /** * The year the audio file was recorded, if any - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String YEAR = "year"; /** * Non-zero if the audio file is music - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_MUSIC = "is_music"; /** * Non-zero if the audio file is a podcast - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_PODCAST = "is_podcast"; /** * Non-zero if the audio file may be a ringtone - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_RINGTONE = "is_ringtone"; /** * Non-zero if the audio file may be an alarm - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_ALARM = "is_alarm"; /** * Non-zero if the audio file may be a notification sound - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_NOTIFICATION = "is_notification"; /** * Non-zero if the audio file is an audiobook - * <P>Type: INTEGER (boolean)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String IS_AUDIOBOOK = "is_audiobook"; /** * The genre of the audio file, if any - * <P>Type: TEXT</P> * Does not exist in the database - only used by the media scanner for inserts. * @hide */ + @Deprecated + // @Column(Cursor.FIELD_TYPE_STRING) public static final String GENRE = "genre"; /** * The resource URI of a localized title, if any - * <P>Type: TEXT</P> * Conforms to this pattern: * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE} * Authority: Package Name of ringtone title provider @@ -2054,6 +2042,7 @@ public final class MediaStore { * Second Path Segment: Resource ID of title * @hide */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String TITLE_RESOURCE_URI = "title_resource_uri"; } @@ -2142,6 +2131,7 @@ public final class MediaStore { * @deprecated Apps may not have filesystem permissions to directly * access this path. */ + @Deprecated public static @Nullable Uri getContentUriForPath(@NonNull String path) { return getContentUri(getVolumeName(new File(path))); } @@ -2202,8 +2192,8 @@ public final class MediaStore { public interface GenresColumns { /** * The name of the genre - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String NAME = "name"; } @@ -2287,14 +2277,14 @@ public final class MediaStore { /** * The ID of the audio file - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String AUDIO_ID = "audio_id"; /** * The ID of the genre - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String GENRE_ID = "genre_id"; } } @@ -2305,8 +2295,8 @@ public final class MediaStore { public interface PlaylistsColumns { /** * The name of the playlist - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String NAME = "name"; /** @@ -2317,8 +2307,6 @@ public final class MediaStore { * apps should use * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain * access. - * <p> - * Type: TEXT * * @deprecated Apps may not have filesystem permissions to directly * access this path. Instead of trying to open this path @@ -2329,21 +2317,22 @@ public final class MediaStore { * {@link android.os.Build.VERSION_CODES#Q} or higher. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String DATA = "_data"; /** * The time the file was added to the media provider * Units are seconds since 1970. - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_ADDED = "date_added"; /** * The time the file was last modified * Units are seconds since 1970. * NOTE: This is for internal use by the media scanner. Do not modify this field. - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_MODIFIED = "date_modified"; } @@ -2426,6 +2415,7 @@ public final class MediaStore { /** * The ID within the playlist. */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String _ID = "_id"; /** @@ -2436,20 +2426,20 @@ public final class MediaStore { /** * The ID of the audio file - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String AUDIO_ID = "audio_id"; /** * The ID of the playlist - * <P>Type: INTEGER (long)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String PLAYLIST_ID = "playlist_id"; /** * The order of the songs in the playlist - * <P>Type: INTEGER (long)></P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String PLAY_ORDER = "play_order"; /** @@ -2465,25 +2455,27 @@ public final class MediaStore { public interface ArtistColumns { /** * The artist who created the audio file, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST = "artist"; /** * A non human readable key calculated from the ARTIST, used for * searching, sorting and grouping - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST_KEY = "artist_key"; /** * The number of albums in the database for this artist */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String NUMBER_OF_ALBUMS = "number_of_albums"; /** * The number of albums in the database for this artist */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String NUMBER_OF_TRACKS = "number_of_tracks"; } @@ -2551,34 +2543,34 @@ public final class MediaStore { /** * The id for the album - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String ALBUM_ID = "album_id"; /** * The album on which the audio file appears, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM = "album"; /** * The artist whose songs appear on this album - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST = "artist"; /** * The number of songs on this album - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String NUMBER_OF_SONGS = "numsongs"; /** * This column is available when getting album info via artist, * and indicates the number of songs on the album by the given * artist. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist"; /** @@ -2586,8 +2578,8 @@ public final class MediaStore { * on this album were released. This will often * be the same as {@link #LAST_YEAR}, but for compilation albums * they might differ. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String FIRST_YEAR = "minyear"; /** @@ -2595,20 +2587,19 @@ public final class MediaStore { * on this album were released. This will often * be the same as {@link #FIRST_YEAR}, but for compilation albums * they might differ. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String LAST_YEAR = "maxyear"; /** * A non human readable key calculated from the ALBUM, used for * searching, sorting and grouping - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM_KEY = "album_key"; /** * Cached album art. - * <P>Type: TEXT</P> * * @deprecated Apps may not have filesystem permissions to directly * access this path. Instead of trying to open this path @@ -2619,6 +2610,7 @@ public final class MediaStore { * {@link android.os.Build.VERSION_CODES#Q} or higher. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String ALBUM_ART = "album_art"; } @@ -2676,6 +2668,43 @@ public final class MediaStore { // Not instantiable. private Radio() { } } + + /** + * This class provides utility methods to obtain thumbnails for various + * {@link Audio} items. + * + * @deprecated Callers should migrate to using + * {@link ContentResolver#loadThumbnail}, since it offers + * richer control over requested thumbnail sizes and + * cancellation behavior. + * @hide + */ + @Deprecated + public static class Thumbnails implements BaseColumns { + /** + * Path to the thumbnail file on disk. + * <p> + * Note that apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path directly, + * apps should use + * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain + * access. + * + * @deprecated Apps may not have filesystem permissions to directly + * access this path. Instead of trying to open this path + * directly, apps should use + * {@link ContentResolver#loadThumbnail} + * to gain access. This value will always be + * {@code NULL} for apps targeting + * {@link android.os.Build.VERSION_CODES#Q} or higher. + */ + @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) + public static final String DATA = "_data"; + + @Column(Cursor.FIELD_TYPE_INTEGER) + public static final String ALBUM_ID = "album_id"; + } } public static final class Video { @@ -2693,61 +2722,60 @@ public final class MediaStore { /** * The duration of the video file, in ms - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DURATION = "duration"; /** * The artist who created the video file, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ARTIST = "artist"; /** * The album the video file is from, if any - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String ALBUM = "album"; /** * The resolution of the video file, formatted as "XxY" - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String RESOLUTION = "resolution"; /** * The description of the video recording - * <P>Type: TEXT</P> */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String DESCRIPTION = "description"; /** * Whether the video should be published as public or private - * <P>Type: INTEGER</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String IS_PRIVATE = "isprivate"; /** * The user-added tags associated with a video - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String TAGS = "tags"; /** * The YouTube category of the video - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String CATEGORY = "category"; /** * The language of the video - * <P>Type: TEXT</P> */ + @Column(Cursor.FIELD_TYPE_STRING) public static final String LANGUAGE = "language"; /** * The latitude where the video was captured. - * <P>Type: DOUBLE</P> * * @deprecated location details are no longer indexed for privacy * reasons, and this value is now always {@code null}. @@ -2755,11 +2783,11 @@ public final class MediaStore { * {@link ExifInterface#getLatLong(float[])}. */ @Deprecated + @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) public static final String LATITUDE = "latitude"; /** * The longitude where the video was captured. - * <P>Type: DOUBLE</P> * * @deprecated location details are no longer indexed for privacy * reasons, and this value is now always {@code null}. @@ -2767,33 +2795,33 @@ public final class MediaStore { * {@link ExifInterface#getLatLong(float[])}. */ @Deprecated + @Column(value = Cursor.FIELD_TYPE_FLOAT, readOnly = true) public static final String LONGITUDE = "longitude"; /** * The date & time that the video was taken in units * of milliseconds since jan 1, 1970. - * <P>Type: INTEGER</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String DATE_TAKEN = "datetaken"; /** * The mini thumb id. - * <P>Type: INTEGER</P> * * @deprecated all thumbnails should be obtained via * {@link MediaStore.Images.Thumbnails#getThumbnail}, as this * value is no longer supported. */ @Deprecated + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String MINI_THUMB_MAGIC = "mini_thumb_magic"; /** * The primary bucket ID of this media item. This can be useful to * present the user a first-level clustering of related media items. * This is a read-only column that is automatically computed. - * <p> - * Type: INTEGER */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String BUCKET_ID = "bucket_id"; /** @@ -2801,9 +2829,8 @@ public final class MediaStore { * useful to present the user a first-level clustering of related * media items. This is a read-only column that is automatically * computed. - * <p> - * Type: TEXT */ + @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true) public static final String BUCKET_DISPLAY_NAME = "bucket_display_name"; /** @@ -2817,9 +2844,8 @@ public final class MediaStore { * {@code IMG1024.BURST001.JPG} and {@code IMG1024.BURST002.JPG} * will have the same {@link #GROUP_ID} because the first portion of * their filenames is identical. - * <p> - * Type: INTEGER */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String GROUP_ID = "group_id"; /** @@ -2827,29 +2853,29 @@ public final class MediaStore { * video should start playing at the next time it is opened. If the value is null or * out of the range 0..DURATION-1 then the video should start playing from the * beginning. - * <P>Type: INTEGER</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String BOOKMARK = "bookmark"; /** * The standard of color aspects - * <P>Type: INTEGER</P> * @hide */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String COLOR_STANDARD = "color_standard"; /** * The transfer of color aspects - * <P>Type: INTEGER</P> * @hide */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String COLOR_TRANSFER = "color_transfer"; /** * The range of color aspects - * <P>Type: INTEGER</P> * @hide */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String COLOR_RANGE = "color_range"; } @@ -3016,8 +3042,6 @@ public final class MediaStore { /** * Path to the thumbnail file on disk. - * <p> - * Type: TEXT * * @deprecated Apps may not have filesystem permissions to directly * access this path. Instead of trying to open this path @@ -3028,18 +3052,19 @@ public final class MediaStore { * {@link android.os.Build.VERSION_CODES#Q} or higher. */ @Deprecated + @Column(Cursor.FIELD_TYPE_STRING) public static final String DATA = "_data"; /** * The original image for the thumbnal - * <P>Type: INTEGER (ID from Video table)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String VIDEO_ID = "video_id"; /** * The kind of the thumbnail - * <P>Type: INTEGER (One of the values below)</P> */ + @Column(Cursor.FIELD_TYPE_INTEGER) public static final String KIND = "kind"; public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; @@ -3048,14 +3073,14 @@ public final class MediaStore { /** * The width of the thumbnal - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String WIDTH = "width"; /** * The height of the thumbnail - * <P>Type: INTEGER (long)</P> */ + @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true) public static final String HEIGHT = "height"; } } diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 93189b310485..2961426278dc 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -15,6 +15,7 @@ */ package android.service.notification; +import android.annotation.SystemApi; import android.app.Notification; import android.os.Bundle; import android.os.Parcel; @@ -36,13 +37,13 @@ public final class Adjustment implements Parcelable { * See {@link android.app.Notification.Builder#addPerson(String)}. * @hide */ + @SystemApi public static final String KEY_PEOPLE = "key_people"; /** * Parcelable {@code ArrayList} of {@link SnoozeCriterion}. These criteria may be visible to * users. If a user chooses to snooze a notification until one of these criterion, the * assistant will be notified via * {@link NotificationAssistantService#onNotificationSnoozedUntilContext}. - * @hide */ public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; /** @@ -111,7 +112,11 @@ public final class Adjustment implements Parcelable { mUser = user; } - private Adjustment(Parcel in) { + /** + * @hide + */ + @SystemApi + protected Adjustment(Parcel in) { if (in.readInt() == 1) { mPackage = in.readString(); } else { diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index e93b1580bc66..a69724860634 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -21,6 +21,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.admin.DevicePolicyManager; @@ -103,7 +104,6 @@ public abstract class NotificationAssistantService extends NotificationListenerS * * @param sbn the notification to snooze * @param snoozeCriterionId the {@link SnoozeCriterion#getId()} representing a device context. - * @hide */ abstract public void onNotificationSnoozedUntilContext(StatusBarNotification sbn, String snoozeCriterionId); @@ -111,12 +111,13 @@ public abstract class NotificationAssistantService extends NotificationListenerS /** * A notification was posted by an app. Called before post. * + * <p>Note: this method is only called if you don't override + * {@link #onNotificationEnqueued(StatusBarNotification, NotificationChannel)}.</p> + * * @param sbn the new notification * @return an adjustment or null to take no action, within 100ms. */ - public Adjustment onNotificationEnqueued(StatusBarNotification sbn) { - return null; - } + abstract public Adjustment onNotificationEnqueued(StatusBarNotification sbn); /** * A notification was posted by an app. Called before post. @@ -240,12 +241,11 @@ public abstract class NotificationAssistantService extends NotificationListenerS /** * Inform the notification manager about un-snoozing a specific notification. * <p> - * This should only be used for notifications snoozed by this listener using - * {@link #snoozeNotification(String, String)}. Once un-snoozed, you will get a + * This should only be used for notifications snoozed because of a contextual snooze suggestion + * you provided via {@link Adjustment#KEY_SNOOZE_CRITERIA}. Once un-snoozed, you will get a * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the * notification. * @param key The key of the notification to snooze - * @hide */ public final void unsnoozeNotification(String key) { if (!isBound()) return; diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java index 814b4772a395..ebfabbfda326 100644 --- a/core/java/android/service/notification/NotificationStats.java +++ b/core/java/android/service/notification/NotificationStats.java @@ -16,6 +16,7 @@ package android.service.notification; import android.annotation.IntDef; +import android.annotation.SystemApi; import android.app.RemoteInput; import android.os.Parcel; import android.os.Parcelable; @@ -98,7 +99,11 @@ public final class NotificationStats implements Parcelable { public NotificationStats() { } - private NotificationStats(Parcel in) { + /** + * @hide + */ + @SystemApi + protected NotificationStats(Parcel in) { mSeen = in.readByte() != 0; mExpanded = in.readByte() != 0; mDirectReplied = in.readByte() != 0; diff --git a/core/java/com/android/internal/os/BackgroundThread.java b/core/java/com/android/internal/os/BackgroundThread.java index eada142dd3c6..22c832e0689c 100644 --- a/core/java/com/android/internal/os/BackgroundThread.java +++ b/core/java/com/android/internal/os/BackgroundThread.java @@ -17,10 +17,13 @@ package com.android.internal.os; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.Looper; import android.os.Trace; +import java.util.concurrent.Executor; + /** * Shared singleton background thread for each process. */ @@ -29,6 +32,7 @@ public final class BackgroundThread extends HandlerThread { private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000; private static BackgroundThread sInstance; private static Handler sHandler; + private static HandlerExecutor sHandlerExecutor; private BackgroundThread() { super("android.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND); @@ -43,6 +47,7 @@ public final class BackgroundThread extends HandlerThread { looper.setSlowLogThresholdMs( SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); } } @@ -59,4 +64,11 @@ public final class BackgroundThread extends HandlerThread { return sHandler; } } + + public static Executor getExecutor() { + synchronized (BackgroundThread.class) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } } diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index a2ed7d3391ed..bd998999ce63 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -1918,7 +1918,6 @@ static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobjec for (jint i = 0; i < nb; i++) { deviceTypesVector.push_back((audio_devices_t) typesPtr[i]); } - env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); // check each address is a string and add device type/address to list for device affinity Vector<AudioDeviceTypeAddr> deviceVector; @@ -1932,6 +1931,7 @@ static jint android_media_AudioSystem_setUidDeviceAffinities(JNIEnv *env, jobjec AudioDeviceTypeAddr dev = AudioDeviceTypeAddr(typesPtr[i], address); deviceVector.add(dev); } + env->ReleaseIntArrayElements(deviceTypes, typesPtr, 0); status_t status = AudioSystem::setUidDeviceAffinities((uid_t) uid, deviceVector); return (jint) nativeToJavaStatus(status); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 5cecf66a593c..b36c85de7264 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1384,8 +1384,8 @@ static jlong CalculateCapabilities(JNIEnv* env, jint uid, jint gid, jintArray gi RuntimeAbort(env, __LINE__, "Bad gids array"); } - for (int gid_index = gids_num; --gids_num >= 0;) { - if (native_gid_proxy[gid_index] == AID_WAKELOCK) { + for (int gids_index = 0; gids_index < gids_num; ++gids_index) { + if (native_gid_proxy[gids_index] == AID_WAKELOCK) { gid_wakelock_found = true; break; } diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 742813e58129..875b90b7ac65 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1647,6 +1647,10 @@ struct ResTable_overlayable_policy_header // The overlay must reside of the product partition or must have existed on the product // partition before an upgrade to overlay these resources. POLICY_PRODUCT_PARTITION = 0x00000008, + + // The overlay must be signed with the same signature as the actor of the target resource, + // which can be separate or the same as the target package with the resource. + POLICY_SIGNATURE = 0x00000010, }; uint32_t policy_flags; diff --git a/libs/incident/proto/android/os/header.proto b/libs/incident/proto/android/os/header.proto index a84dc48b8b34..d463f87055b3 100644 --- a/libs/incident/proto/android/os/header.proto +++ b/libs/incident/proto/android/os/header.proto @@ -37,4 +37,9 @@ message IncidentHeaderProto { optional int64 id = 2; // The unique id of the statsd config. } optional StatsdConfigKey config_key = 3; + + // Details about the trigger. com.android.os.AlertTriggerDetails + // Only use bytes type here to avoid indirect dependency on atoms.proto + // And this header passes through incidentd without incidentd parsing it. + optional bytes trigger_details = 4; } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index 1aeefb84129f..57a0a725fb41 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -114,6 +114,7 @@ interface ILocationManager // for reporting callback completion void locationCallbackFinished(ILocationListener listener); - // used by gts tests to verify throttling whitelist + // used by gts tests to verify whitelists String[] getBackgroundThrottlingWhitelist(); + String[] getIgnoreSettingsWhitelist(); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index c027fd499033..586ee2a43683 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -414,6 +414,18 @@ public class LocationManager { } /** + * @hide + */ + @TestApi + public String[] getIgnoreSettingsWhitelist() { + try { + return mService.getIgnoreSettingsWhitelist(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * @hide - hide this constructor because it has a parameter * of type ILocationManager, which is a system private class. The * right way to create an instance of this class is using the diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java index 92afe7ede8f2..24a3a9b32f77 100644 --- a/media/java/android/media/AudioRecord.java +++ b/media/java/android/media/AudioRecord.java @@ -17,6 +17,7 @@ package android.media; import android.annotation.CallbackExecutor; +import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1713,12 +1714,11 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, /** * Specifies the logical microphone (for processing). * - * @param direction Direction constant (MicrophoneDirection.MIC_DIRECTION_*) - * @return retval OK if the call is successful, an error code otherwise. - * @hide + * @param direction Direction constant. + * @return true if sucessful. */ - public int setMicrophoneDirection(int direction) { - return native_set_microphone_direction(direction); + public boolean setMicrophoneDirection(int direction) { + return native_set_microphone_direction(direction) == 0; } /** @@ -1727,11 +1727,10 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection, * * @param zoom the desired field dimension of microphone capture. Range is from -1 (wide angle), * though 0 (no zoom) to 1 (maximum zoom). - * @return retval OK if the call is successful, an error code otherwise. - * @hide + * @return true if sucessful. */ - public int setMicrophoneFieldDimension(float zoom) { - return native_set_microphone_field_dimension(zoom); + public boolean setMicrophoneFieldDimension(@FloatRange(from = -1.0, to = 1.0) float zoom) { + return native_set_microphone_field_dimension(zoom) == 0; } //--------------------------------------------------------- diff --git a/media/java/android/media/MicrophoneDirection.java b/media/java/android/media/MicrophoneDirection.java index 99201c0279bf..489e2683259e 100644 --- a/media/java/android/media/MicrophoneDirection.java +++ b/media/java/android/media/MicrophoneDirection.java @@ -16,38 +16,46 @@ package android.media; -/** - * @hide - */ +import android.annotation.FloatRange; +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + public interface MicrophoneDirection { /** - * @hide + * Don't do any directionality processing of the activated microphone(s). */ int MIC_DIRECTION_UNSPECIFIED = 0; - /** - * @hide + * Optimize capture for audio coming from the screen-side of the device. */ int MIC_DIRECTION_FRONT = 1; - /** - * @hide + * Optimize capture for audio coming from the side of the device opposite the screen. */ int MIC_DIRECTION_BACK = 2; - /** - * @hide + * Optimize capture for audio coming from an off-device microphone. */ int MIC_DIRECTION_EXTERNAL = 3; + /** @hide */ + @IntDef({ + MIC_DIRECTION_UNSPECIFIED, + MIC_DIRECTION_FRONT, + MIC_DIRECTION_BACK, + MIC_DIRECTION_EXTERNAL + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Directionmode{}; /** * Specifies the logical microphone (for processing). * - * @param direction Direction constant (MicrophoneDirection.MIC_DIRECTION_*) - * @return retval OK if the call is successful, an error code otherwise. - * @hide + * @param direction Direction constant. + * @return true if sucessful. */ - int setMicrophoneDirection(int direction); + boolean setMicrophoneDirection(@Directionmode int direction); /** * Specifies the zoom factor (i.e. the field dimension) for the selected microphone @@ -55,8 +63,7 @@ public interface MicrophoneDirection { * * @param zoom the desired field dimension of microphone capture. Range is from -1 (wide angle), * though 0 (no zoom) to 1 (maximum zoom). - * @return retval OK if the call is successful, an error code otherwise. - * @hide + * @return true if sucessful. */ - int setMicrophoneFieldDimension(float zoom); + boolean setMicrophoneFieldDimension(@FloatRange(from = -1.0, to = 1.0) float zoom); } diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index c5e598d8ce46..31a3ff4a9b8f 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -221,6 +221,12 @@ public class Assistant extends NotificationAssistantService { } @Override + public Adjustment onNotificationEnqueued(StatusBarNotification sbn) { + // we use the version with channel, so this is never called. + return null; + } + + @Override public Adjustment onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel) { if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey() + " on " + channel.getId()); diff --git a/services/core/java/com/android/server/ExtconUEventObserver.java b/services/core/java/com/android/server/ExtconUEventObserver.java index eb591528bc90..775e4c8cf4ed 100644 --- a/services/core/java/com/android/server/ExtconUEventObserver.java +++ b/services/core/java/com/android/server/ExtconUEventObserver.java @@ -68,7 +68,7 @@ public abstract class ExtconUEventObserver extends UEventObserver { * Subclasses of ExtconUEventObserver should override this method to handle UEvents. * * @param extconInfo that matches the {@code DEVPATH} of {@code event} - * @param event the event + * @param event the event */ protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event); @@ -91,6 +91,9 @@ public abstract class ExtconUEventObserver extends UEventObserver { /** Returns a new list of all external connections whose name matches {@code regex}. */ public static List<ExtconInfo> getExtconInfos(@Nullable String regex) { + if (!extconExists()) { + return new ArrayList<>(0); // Always return a new list. + } Pattern p = regex == null ? null : Pattern.compile(regex); File file = new File("/sys/class/extcon"); File[] files = file.listFiles(); diff --git a/services/core/java/com/android/server/FgThread.java b/services/core/java/com/android/server/FgThread.java index fe30057fc820..5d0e308f6649 100644 --- a/services/core/java/com/android/server/FgThread.java +++ b/services/core/java/com/android/server/FgThread.java @@ -17,9 +17,12 @@ package com.android.server; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Trace; +import java.util.concurrent.Executor; + /** * Shared singleton foreground thread for the system. This is a thread for regular * foreground service operations, which shouldn't be blocked by anything running in @@ -34,6 +37,7 @@ public final class FgThread extends ServiceThread { private static FgThread sInstance; private static Handler sHandler; + private static HandlerExecutor sHandlerExecutor; private FgThread() { super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/); @@ -48,6 +52,7 @@ public final class FgThread extends ServiceThread { looper.setSlowLogThresholdMs( SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); } } @@ -64,4 +69,11 @@ public final class FgThread extends ServiceThread { return sHandler; } } + + public static Executor getExecutor() { + synchronized (FgThread.class) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } } diff --git a/services/core/java/com/android/server/IoThread.java b/services/core/java/com/android/server/IoThread.java index bfe825a3a89e..21fd29c3bbef 100644 --- a/services/core/java/com/android/server/IoThread.java +++ b/services/core/java/com/android/server/IoThread.java @@ -17,8 +17,11 @@ package com.android.server; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Trace; +import java.util.concurrent.Executor; + /** * Shared singleton I/O thread for the system. This is a thread for non-background * service operations that can potential block briefly on network IO operations @@ -27,6 +30,7 @@ import android.os.Trace; public final class IoThread extends ServiceThread { private static IoThread sInstance; private static Handler sHandler; + private static HandlerExecutor sHandlerExecutor; private IoThread() { super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT, true /*allowIo*/); @@ -38,6 +42,7 @@ public final class IoThread extends ServiceThread { sInstance.start(); sInstance.getLooper().setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER); sHandler = new Handler(sInstance.getLooper()); + sHandlerExecutor = new HandlerExecutor(sHandler); } } @@ -54,4 +59,11 @@ public final class IoThread extends ServiceThread { return sHandler; } } + + public static Executor getExecutor() { + synchronized (IoThread.class) { + ensureThreadLocked(); + return sHandlerExecutor; + } + } } diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java index 9d92ea2b5b45..5989a46c5a0a 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -2136,6 +2136,13 @@ public class LocationManagerService extends ILocationManager.Stub { } } + @Override + public String[] getIgnoreSettingsWhitelist() { + synchronized (mLock) { + return mIgnoreSettingsPackageWhitelist.toArray(new String[0]); + } + } + @GuardedBy("mLock") private boolean isThrottlingExemptLocked(CallerIdentity callerIdentity) { if (callerIdentity.mUid == Process.SYSTEM_UID) { @@ -2794,8 +2801,7 @@ public class LocationManagerService extends ILocationManager.Stub { } catch (RemoteException e) { // if the remote process registering the listener is already dead, just swallow the // exception and return - Log.w(TAG, "Could not link " + linkedListener.mListenerName + " death callback.", - e); + Log.w(TAG, "Could not link " + linkedListener.mListenerName + " death callback.", e); return false; } } @@ -2808,8 +2814,7 @@ public class LocationManagerService extends ILocationManager.Stub { } catch (NoSuchElementException e) { // if the death callback isn't connected (it should be...), log error, // swallow the exception and return - Log.w(TAG, "Could not unlink " + linkedListener.mListenerName + " death callback.", - e); + Log.w(TAG, "Could not unlink " + linkedListener.mListenerName + " death callback.", e); return false; } } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index 6b57fcd31450..710a0ba34d4f 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -30,11 +30,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.os.BatteryManager; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.ServiceType; @@ -354,8 +356,12 @@ final class UiModeManagerService extends SystemService { try { synchronized (mLock) { if (mNightMode != mode) { - Settings.Secure.putIntForUser(getContext().getContentResolver(), - Settings.Secure.UI_NIGHT_MODE, mode, user); + // Only persist setting if not transient night mode or not in car mode + if (!shouldTransientNightWhenInCarMode() || !mCarModeEnabled) { + Settings.Secure.putIntForUser(getContext().getContentResolver(), + Settings.Secure.UI_NIGHT_MODE, mode, user); + } + mNightMode = mode; updateLocked(0, 0); } @@ -438,9 +444,39 @@ final class UiModeManagerService extends SystemService { } } + // Night mode settings in car mode are only persisted below Q. + // When targeting Q, changes are not saved and night mode will be re-read + // from settings when exiting car mode. + private boolean shouldTransientNightWhenInCarMode() { + int uid = Binder.getCallingUid(); + PackageManager packageManager = getContext().getPackageManager(); + String[] packagesForUid = packageManager.getPackagesForUid(uid); + if (packagesForUid == null || packagesForUid.length == 0) { + return false; + } + + try { + ApplicationInfo appInfo = packageManager.getApplicationInfoAsUser( + packagesForUid[0], 0, uid); + + return appInfo.targetSdkVersion >= Build.VERSION_CODES.Q; + } catch (PackageManager.NameNotFoundException ignored) { + } + + return false; + } + void setCarModeLocked(boolean enabled, int flags) { if (mCarModeEnabled != enabled) { mCarModeEnabled = enabled; + + // When transient night mode and exiting car mode, restore night mode from settings + if (shouldTransientNightWhenInCarMode() && !mCarModeEnabled) { + Context context = getContext(); + updateNightModeFromSettings(context, + context.getResources(), + UserHandle.getCallingUserId()); + } } mCarModeEnableFlags = flags; } @@ -498,7 +534,9 @@ final class UiModeManagerService extends SystemService { uiMode |= mNightMode << 4; } - if (mPowerSave) { + // Override night mode in power save mode if not transient night mode or not in car mode + boolean shouldOverrideNight = !mCarModeEnabled || !shouldTransientNightWhenInCarMode(); + if (mPowerSave && shouldOverrideNight) { uiMode &= ~Configuration.UI_MODE_NIGHT_NO; uiMode |= Configuration.UI_MODE_NIGHT_YES; } diff --git a/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java index 753d3b0cc10e..3663518bf7b9 100644 --- a/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java +++ b/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java @@ -99,7 +99,7 @@ public class CarrierMessagingClientServiceFinder @Override public void startMonitoring() { mRoleManager.addOnRoleHoldersChangedListenerAsUser( - mContext.getMainExecutor(), mRoleHolderChangedListener, UserHandle.ALL); + BackgroundThread.getExecutor(), mRoleHolderChangedListener, UserHandle.ALL); } @Override @@ -120,9 +120,7 @@ public class CarrierMessagingClientServiceFinder private final OnRoleHoldersChangedListener mRoleHolderChangedListener = (role, user) -> { if (RoleManager.ROLE_SMS.equals(role)) { - BackgroundThread.getHandler().post(() -> { - mListener.accept(CarrierMessagingClientServiceFinder.this, user.getIdentifier()); - }); + mListener.accept(CarrierMessagingClientServiceFinder.this, user.getIdentifier()); } }; } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 6df60d6bdd3a..9af57daa259b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -280,9 +280,9 @@ import java.util.ArrayList; AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource); sendIILMsgNoDelay(MSG_IIL_SET_FORCE_USE, SENDMSG_QUEUE, AudioSystem.FOR_RECORD, mForcedUseForComm, eventSource); - // Un-mute ringtone stream volume - mAudioService.setUpdateRingerModeServiceInt(); } + // Un-mute ringtone stream volume + mAudioService.postUpdateRingerModeServiceInt(); } /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index fbabc82ed65d..1a62d4f8faf4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -248,6 +248,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_NOTIFY_VOL_EVENT = 22; private static final int MSG_DISPATCH_AUDIO_SERVER_STATE = 23; private static final int MSG_ENABLE_SURROUND_FORMATS = 24; + private static final int MSG_UPDATE_RINGER_MODE = 25; // start of messages handled under wakelock // these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(), // and not with sendMsg(..., ..., SENDMSG_QUEUE, ...) @@ -2720,7 +2721,11 @@ public class AudioService extends IAudioService.Stub } } - /*package*/ void setUpdateRingerModeServiceInt() { + /*package*/ void postUpdateRingerModeServiceInt() { + sendMsg(mAudioHandler, MSG_UPDATE_RINGER_MODE, SENDMSG_QUEUE, 0, 0, null, 0); + } + + private void onUpdateRingerModeServiceInt() { setRingerModeInt(getRingerModeInternal(), false); } @@ -4959,6 +4964,10 @@ public class AudioService extends IAudioService.Stub case MSG_ENABLE_SURROUND_FORMATS: onEnableSurroundFormats((ArrayList<Integer>) msg.obj); break; + + case MSG_UPDATE_RINGER_MODE: + onUpdateRingerModeServiceInt(); + break; } } } diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index e71b156c3e86..243b6fe7a30e 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -373,6 +373,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements private final NtpTimeHelper mNtpTimeHelper; private final GnssBatchingProvider mGnssBatchingProvider; private final GnssGeofenceProvider mGnssGeofenceProvider; + // Available only on GNSS HAL 2.0 implementations and later. private GnssVisibilityControl mGnssVisibilityControl; // Handler for processing events @@ -463,8 +464,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } }; - // TODO(b/119326010): replace OnSubscriptionsChangedListener with - // ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED broadcast reseiver. + // TODO: replace OnSubscriptionsChangedListener with ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED + // broadcast receiver. private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener = new OnSubscriptionsChangedListener() { @Override @@ -676,8 +677,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements mNtpTimeHelper.onNetworkAvailable(); if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) { if (mSupportsXtra) { - // Download only if supported, (prevents an unneccesary on-boot - // download) + // Download only if supported, (prevents an unnecessary on-boot download) xtraDownloadRequest(); } } @@ -764,7 +764,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements /** Returns true if the location request is too frequent. */ private boolean isRequestLocationRateLimited() { - // TODO(b/73198123): implement exponential backoff. + // TODO: implement exponential backoff. return false; } diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java index c3f25bfa2e5e..20f872a213fd 100644 --- a/services/core/java/com/android/server/location/GnssVisibilityControl.java +++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java @@ -24,10 +24,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.location.LocationManager; import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; +import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import android.util.StatsLog; @@ -70,7 +73,7 @@ class GnssVisibilityControl { private final Handler mHandler; private final Context mContext; - private boolean mIsMasterLocationSettingsEnabled = true; + private boolean mIsDeviceLocationSettingsEnabled; // Number of non-framework location access proxy apps is expected to be small (< 5). private static final int HASH_MAP_INITIAL_CAPACITY_PROXY_APP_TO_LOCATION_PERMISSIONS = 7; @@ -88,13 +91,9 @@ class GnssVisibilityControl { mAppOps = mContext.getSystemService(AppOpsManager.class); mPackageManager = mContext.getPackageManager(); - // Set to empty proxy app list initially until the configuration properties are loaded. - updateNfwLocationAccessProxyAppsInGnssHal(); - - // Listen for proxy app package installation, removal events. - listenForProxyAppsPackageUpdates(); - - // TODO(b/122855984): Handle global location settings on/off. + // Complete initialization as the first event to run in mHandler thread. After that, + // all object state read/update events run in the mHandler thread. + runOnHandler(this::handleInitialize); } void updateProxyApps(List<String> nfwLocationAccessProxyApps) { @@ -105,10 +104,6 @@ class GnssVisibilityControl { runOnHandler(() -> handleUpdateProxyApps(nfwLocationAccessProxyApps)); } - void masterLocationSettingsUpdated(boolean enabled) { - runOnHandler(() -> handleMasterLocationSettingsUpdated(enabled)); - } - void reportNfwNotification(String proxyAppPackageName, byte protocolStack, String otherProtocolStackName, byte requestor, String requestorId, byte responseType, boolean inEmergencyMode, boolean isCachedLocation) { @@ -117,7 +112,19 @@ class GnssVisibilityControl { requestor, requestorId, responseType, inEmergencyMode, isCachedLocation))); } + private void handleInitialize() { + disableNfwLocationAccess(); // Disable until config properties are loaded. + listenForProxyAppsPackageUpdates(); + listenForDeviceLocationSettingsUpdate(); + mIsDeviceLocationSettingsEnabled = getDeviceLocationSettings(); + } + + private boolean getDeviceLocationSettings() { + return mContext.getSystemService(LocationManager.class).isLocationEnabled(); + } + private void listenForProxyAppsPackageUpdates() { + // Listen for proxy apps package installation, removal events. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); @@ -143,11 +150,22 @@ class GnssVisibilityControl { }, UserHandle.ALL, intentFilter, null, mHandler); } + private void listenForDeviceLocationSettingsUpdate() { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.LOCATION_MODE), + true, + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + handleDeviceLocationSettingsUpdated(); + } + }, UserHandle.USER_ALL); + } + private void handleProxyAppPackageUpdate(String pkgName, String action) { final Boolean locationPermission = mProxyAppToLocationPermissions.get(pkgName); - // pkgName is not one of the proxy apps in our list. if (locationPermission == null) { - return; + return; // ignore, pkgName is not one of the proxy apps in our list. } Log.i(TAG, "Proxy app " + pkgName + " package changed: " + action); @@ -197,15 +215,33 @@ class GnssVisibilityControl { return true; } } - return false; } - private void handleMasterLocationSettingsUpdated(boolean enabled) { - mIsMasterLocationSettingsEnabled = enabled; - Log.i(TAG, "Master location settings switch changed to " - + (enabled ? "enabled" : "disabled")); - updateNfwLocationAccessProxyAppsInGnssHal(); + private void handleDeviceLocationSettingsUpdated() { + final boolean enabled = getDeviceLocationSettings(); + Log.i(TAG, "Device location settings enabled: " + enabled); + + if (mIsDeviceLocationSettingsEnabled == enabled) { + return; + } + + mIsDeviceLocationSettingsEnabled = enabled; + if (!mIsDeviceLocationSettingsEnabled) { + disableNfwLocationAccess(); + return; + } + + // When device location settings was disabled, we already set the proxy app list + // to empty in GNSS HAL. Update only if the proxy app list is not empty. + String[] locationPermissionEnabledProxyApps = getLocationPermissionEnabledProxyApps(); + if (locationPermissionEnabledProxyApps.length != 0) { + setNfwLocationAccessProxyAppsInGnssHal(locationPermissionEnabledProxyApps); + } + } + + private void disableNfwLocationAccess() { + setNfwLocationAccessProxyAppsInGnssHal(NO_LOCATION_ENABLED_PROXY_APPS); } // Represents NfwNotification structure in IGnssVisibilityControlCallback.hal @@ -316,8 +352,7 @@ class GnssVisibilityControl { return mPackageManager.getApplicationInfo(pkgName, 0).uid; } catch (PackageManager.NameNotFoundException e) { if (DEBUG) { - Log.d(TAG, "Non-framework location access proxy app " - + pkgName + " is not found."); + Log.d(TAG, "Non-framework location access proxy app " + pkgName + " is not found."); } return null; } @@ -329,8 +364,14 @@ class GnssVisibilityControl { } private void updateNfwLocationAccessProxyAppsInGnssHal() { - final String[] locationPermissionEnabledProxyApps = shouldDisableNfwLocationAccess() - ? NO_LOCATION_ENABLED_PROXY_APPS : getLocationPermissionEnabledProxyApps(); + if (!mIsDeviceLocationSettingsEnabled) { + return; // Keep non-framework location access disabled. + } + setNfwLocationAccessProxyAppsInGnssHal(getLocationPermissionEnabledProxyApps()); + } + + private void setNfwLocationAccessProxyAppsInGnssHal( + String[] locationPermissionEnabledProxyApps) { final String proxyAppsStr = Arrays.toString(locationPermissionEnabledProxyApps); Log.i(TAG, "Updating non-framework location access proxy apps in the GNSS HAL to: " + proxyAppsStr); @@ -341,12 +382,8 @@ class GnssVisibilityControl { } } - private boolean shouldDisableNfwLocationAccess() { - return !mIsMasterLocationSettingsEnabled; - } - private String[] getLocationPermissionEnabledProxyApps() { - // Get a count of proxy apps with location permission enabled to array creation size. + // Get a count of proxy apps with location permission enabled for array creation size. int countLocationPermissionEnabledProxyApps = 0; for (Boolean hasLocationPermissionEnabled : mProxyAppToLocationPermissions.values()) { if (hasLocationPermissionEnabled) { diff --git a/services/core/java/com/android/server/pm/OWNERS b/services/core/java/com/android/server/pm/OWNERS index 33b8641c145e..640b155e69d5 100644 --- a/services/core/java/com/android/server/pm/OWNERS +++ b/services/core/java/com/android/server/pm/OWNERS @@ -19,6 +19,9 @@ per-file BackgroundDexOptService.java = ngeoffray@google.com per-file CompilerStats.java = agampe@google.com per-file CompilerStats.java = calin@google.com per-file CompilerStats.java = ngeoffray@google.com +per-file DynamicCodeLoggingService.java = agampe@google.com +per-file DynamicCodeLoggingService.java = calin@google.com +per-file DynamicCodeLoggingService.java = ngeoffray@google.com per-file InstructionSets.java = agampe@google.com per-file InstructionSets.java = calin@google.com per-file InstructionSets.java = ngeoffray@google.com diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 168c9adb61be..899bf7c8fd39 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5090,11 +5090,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void startWindowTrace(){ - try { - mWindowTracing.startTrace(null /* printwriter */); - } catch (IOException e) { - throw new RuntimeException(e); - } + mWindowTracing.startTrace(null /* printwriter */); } @Override diff --git a/services/core/java/com/android/server/wm/WindowTraceBuffer.java b/services/core/java/com/android/server/wm/WindowTraceBuffer.java index e4461ea90c91..2ce6e6c1d049 100644 --- a/services/core/java/com/android/server/wm/WindowTraceBuffer.java +++ b/services/core/java/com/android/server/wm/WindowTraceBuffer.java @@ -20,7 +20,6 @@ import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_H; import static com.android.server.wm.WindowManagerTraceFileProto.MAGIC_NUMBER_L; -import android.os.Trace; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; @@ -36,24 +35,30 @@ import java.util.Queue; /** * Buffer used for window tracing. */ -abstract class WindowTraceBuffer { +class WindowTraceBuffer { private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; - final Object mBufferLock = new Object(); - final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>(); - final File mTraceFile; - int mBufferSize; - private final int mBufferCapacity; + private final Object mBufferLock = new Object(); - WindowTraceBuffer(int size, File traceFile) throws IOException { - mBufferCapacity = size; - mTraceFile = traceFile; + private final Queue<ProtoOutputStream> mBuffer = new ArrayDeque<>(); + private int mBufferUsedSize; + private int mBufferCapacity; - initTraceFile(); + WindowTraceBuffer(int bufferCapacity) { + mBufferCapacity = bufferCapacity; + resetBuffer(); } int getAvailableSpace() { - return mBufferCapacity - mBufferSize; + return mBufferCapacity - mBufferUsedSize; + } + + int size() { + return mBuffer.size(); + } + + void setCapacity(int capacity) { + mBufferCapacity = capacity; } /** @@ -70,42 +75,37 @@ abstract class WindowTraceBuffer { + mBufferCapacity + " Object size: " + protoLength); } synchronized (mBufferLock) { - boolean canAdd = canAdd(protoLength); - if (canAdd) { - mBuffer.add(proto); - mBufferSize += protoLength; - } + discardOldest(protoLength); + mBuffer.add(proto); + mBufferUsedSize += protoLength; mBufferLock.notify(); } } - /** - * Stops the buffer execution and flush all buffer content to the disk. - * - * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} - */ - void dump() throws IOException, InterruptedException { - try { - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFile"); - writeTraceToFile(); - } finally { - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); - } - } - - @VisibleForTesting boolean contains(byte[] other) { return mBuffer.stream() .anyMatch(p -> Arrays.equals(p.getBytes(), other)); } - private void initTraceFile() throws IOException { - mTraceFile.delete(); - try (OutputStream os = new FileOutputStream(mTraceFile)) { - mTraceFile.setReadable(true, false); - ProtoOutputStream proto = new ProtoOutputStream(os); - proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); - proto.flush(); + /** + * Writes the trace buffer to disk. + */ + void writeTraceToFile(File traceFile) throws IOException { + synchronized (mBufferLock) { + traceFile.delete(); + traceFile.setReadable(true, false); + try (OutputStream os = new FileOutputStream(traceFile)) { + ProtoOutputStream proto = new ProtoOutputStream(); + proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); + os.write(proto.getBytes()); + while (!mBuffer.isEmpty()) { + proto = mBuffer.poll(); + mBufferUsedSize -= proto.getRawSize(); + byte[] protoBytes = proto.getBytes(); + os.write(protoBytes); + } + os.flush(); + } } } @@ -114,59 +114,48 @@ abstract class WindowTraceBuffer { * smaller than the overall buffer size. * * @param protoLength byte array representation of the Proto object to add - * @return {@code true} if the element can be added to the buffer or not - */ - abstract boolean canAdd(int protoLength); - - /** - * Flush all buffer content to the disk. - * - * @throws IOException if the buffer cannot write its contents to the {@link #mTraceFile} */ - abstract void writeTraceToFile() throws IOException, InterruptedException; - - /** - * Builder for a {@code WindowTraceBuffer} which creates a {@link WindowTraceRingBuffer} for - * continuous mode or a {@link WindowTraceQueueBuffer} otherwise - */ - static class Builder { - private boolean mContinuous; - private File mTraceFile; - private int mBufferCapacity; - - Builder setContinuousMode(boolean continuous) { - mContinuous = continuous; - return this; - } + private void discardOldest(int protoLength) { + long availableSpace = getAvailableSpace(); - Builder setTraceFile(File traceFile) { - mTraceFile = traceFile; - return this; - } + while (availableSpace < protoLength) { - Builder setBufferCapacity(int size) { - mBufferCapacity = size; - return this; + ProtoOutputStream item = mBuffer.poll(); + if (item == null) { + throw new IllegalStateException("No element to discard from buffer"); + } + mBufferUsedSize -= item.getRawSize(); + availableSpace = getAvailableSpace(); } + } - File getFile() { - return mTraceFile; + /** + * Removes all elements form the buffer + */ + void resetBuffer() { + synchronized (mBufferLock) { + mBuffer.clear(); + mBufferUsedSize = 0; } + } - WindowTraceBuffer build() throws IOException { - if (mBufferCapacity <= 0) { - throw new IllegalStateException("Buffer capacity must be greater than 0."); - } - - if (mTraceFile == null) { - throw new IllegalArgumentException("A valid trace file must be specified."); - } + @VisibleForTesting + int getBufferSize() { + return mBufferUsedSize; + } - if (mContinuous) { - return new WindowTraceRingBuffer(mBufferCapacity, mTraceFile); - } else { - return new WindowTraceQueueBuffer(mBufferCapacity, mTraceFile); - } + String getStatus() { + synchronized (mBufferLock) { + return "Buffer size: " + + mBufferCapacity + + " bytes" + + "\n" + + "Buffer usage: " + + mBufferUsedSize + + " bytes" + + "\n" + + "Elements in the buffer: " + + mBuffer.size(); } } } diff --git a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java b/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java deleted file mode 100644 index 5888b7a799cf..000000000000 --- a/services/core/java/com/android/server/wm/WindowTraceQueueBuffer.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import static android.os.Build.IS_USER; - -import android.util.Log; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * A buffer structure backed by a {@link java.util.concurrent.BlockingQueue} to store the first - * {@code #size size} bytes of window trace elements. - * Once the buffer is full it will no longer accepts new elements. - */ -class WindowTraceQueueBuffer extends WindowTraceBuffer { - private static final String TAG = "WindowTracing"; - - private Thread mConsumerThread; - private boolean mCancel; - - @VisibleForTesting - WindowTraceQueueBuffer(int size, File traceFile, boolean startConsumerThread) - throws IOException { - super(size, traceFile); - if (startConsumerThread) { - initializeConsumerThread(); - } - } - - WindowTraceQueueBuffer(int size, File traceFile) throws IOException { - this(size, traceFile, !IS_USER); - } - - private void initializeConsumerThread() { - mCancel = false; - mConsumerThread = new Thread(() -> { - try { - loop(); - } catch (InterruptedException e) { - Log.i(TAG, "Interrupting trace consumer thread"); - } catch (IOException e) { - Log.e(TAG, "Failed to execute trace consumer thread", e); - } - }, "window_tracing"); - mConsumerThread.start(); - } - - private void loop() throws IOException, InterruptedException { - while (!mCancel) { - ProtoOutputStream proto; - synchronized (mBufferLock) { - mBufferLock.wait(); - proto = mBuffer.poll(); - if (proto != null) { - mBufferSize -= proto.getRawSize(); - } - } - if (proto != null) { - try (OutputStream os = new FileOutputStream(mTraceFile, true)) { - byte[] protoBytes = proto.getBytes(); - os.write(protoBytes); - } - } - } - } - - @Override - boolean canAdd(int protoLength) { - long availableSpace = getAvailableSpace(); - return availableSpace >= protoLength; - } - - @Override - void writeTraceToFile() throws InterruptedException { - synchronized (mBufferLock) { - mCancel = true; - mBufferLock.notify(); - } - if (mConsumerThread != null) { - mConsumerThread.join(); - mConsumerThread = null; - } - } -} diff --git a/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java b/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java deleted file mode 100644 index 77d30be816bc..000000000000 --- a/services/core/java/com/android/server/wm/WindowTraceRingBuffer.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.wm; - -import android.util.proto.ProtoOutputStream; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * A ring buffer to store the {@code #size size} bytes of window trace data. - * The buffer operates on a trace entry level, that is, if the new trace data is larger than the - * available buffer space, the buffer will discard as many full trace entries as necessary to fit - * the new trace. - */ -class WindowTraceRingBuffer extends WindowTraceBuffer { - WindowTraceRingBuffer(int size, File traceFile) throws IOException { - super(size, traceFile); - } - - @Override - boolean canAdd(int protoLength) { - long availableSpace = getAvailableSpace(); - - while (availableSpace < protoLength) { - discardOldest(); - availableSpace = getAvailableSpace(); - } - - return true; - } - - @Override - void writeTraceToFile() throws IOException { - synchronized (mBufferLock) { - try (OutputStream os = new FileOutputStream(mTraceFile, true)) { - while (!mBuffer.isEmpty()) { - ProtoOutputStream proto = mBuffer.poll(); - mBufferSize -= proto.getRawSize(); - byte[] protoBytes = proto.getBytes(); - os.write(protoBytes); - } - } - } - } - - private void discardOldest() { - ProtoOutputStream item = mBuffer.poll(); - if (item == null) { - throw new IllegalStateException("No element to discard from buffer"); - } - mBufferSize -= item.getRawSize(); - } -} diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java index abc474d756b7..0ce215c88dad 100644 --- a/services/core/java/com/android/server/wm/WindowTracing.java +++ b/services/core/java/com/android/server/wm/WindowTracing.java @@ -31,8 +31,6 @@ import android.util.Log; import android.util.proto.ProtoOutputStream; import android.view.Choreographer; -import com.android.internal.annotations.VisibleForTesting; - import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -47,139 +45,191 @@ class WindowTracing { * Maximum buffer size, currently defined as 512 KB * Size was experimentally defined to fit between 100 to 150 elements. */ - private static final int WINDOW_TRACE_BUFFER_SIZE = 512 * 1024; + private static final int BUFFER_CAPACITY_CRITICAL = 512 * 1024; + private static final int BUFFER_CAPACITY_TRIM = 2048 * 1024; + private static final int BUFFER_CAPACITY_ALL = 4096 * 1024; + private static final String TRACE_FILENAME = "/data/misc/wmtrace/wm_trace.pb"; private static final String TAG = "WindowTracing"; private final WindowManagerService mService; private final Choreographer mChoreographer; private final WindowManagerGlobalLock mGlobalLock; - private final Object mLock = new Object(); - private final WindowTraceBuffer.Builder mBufferBuilder; - - private WindowTraceBuffer mTraceBuffer; + private final Object mEnabledLock = new Object(); + private final File mTraceFile; + private final WindowTraceBuffer mBuffer; + private final Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) -> + log("onFrame" /* where */); - private @WindowTraceLogLevel int mWindowTraceLogLevel = WindowTraceLogLevel.TRIM; - private boolean mContinuousMode; + private @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM; + private boolean mLogOnFrame = false; private boolean mEnabled; private volatile boolean mEnabledLockFree; private boolean mScheduled; - private Choreographer.FrameCallback mFrameCallback = (frameTimeNanos) -> - log("onFrame" /* where */); - private WindowTracing(File file, WindowManagerService service, Choreographer choreographer) { - this(file, service, choreographer, service.mGlobalLock); + static WindowTracing createDefaultAndStartLooper(WindowManagerService service, + Choreographer choreographer) { + File file = new File(TRACE_FILENAME); + return new WindowTracing(file, service, choreographer, BUFFER_CAPACITY_TRIM); } - @VisibleForTesting - WindowTracing(File file, WindowManagerService service, Choreographer choreographer, - WindowManagerGlobalLock globalLock) { - mBufferBuilder = new WindowTraceBuffer.Builder() - .setTraceFile(file) - .setBufferCapacity(WINDOW_TRACE_BUFFER_SIZE); + private WindowTracing(File file, WindowManagerService service, Choreographer choreographer, + int bufferCapacity) { + this(file, service, choreographer, service.mGlobalLock, bufferCapacity); + } + WindowTracing(File file, WindowManagerService service, Choreographer choreographer, + WindowManagerGlobalLock globalLock, int bufferCapacity) { mChoreographer = choreographer; mService = service; mGlobalLock = globalLock; + mTraceFile = file; + mBuffer = new WindowTraceBuffer(bufferCapacity); + setLogLevel(WindowTraceLogLevel.TRIM, null /* pw */); } - void startTrace(@Nullable PrintWriter pw) throws IOException { + void startTrace(@Nullable PrintWriter pw) { if (IS_USER) { logAndPrintln(pw, "Error: Tracing is not supported on user builds."); return; } - synchronized (mLock) { - logAndPrintln(pw, "Start tracing to " + mBufferBuilder.getFile() + "."); - if (mTraceBuffer != null) { - writeTraceToFileLocked(); - } - mTraceBuffer = mBufferBuilder - .setContinuousMode(mContinuousMode) - .build(); + synchronized (mEnabledLock) { + logAndPrintln(pw, "Start tracing to " + mTraceFile + "."); + mBuffer.resetBuffer(); mEnabled = mEnabledLockFree = true; } } - private void logAndPrintln(@Nullable PrintWriter pw, String msg) { - Log.i(TAG, msg); - if (pw != null) { - pw.println(msg); - pw.flush(); - } - } - void stopTrace(@Nullable PrintWriter pw) { if (IS_USER) { logAndPrintln(pw, "Error: Tracing is not supported on user builds."); return; } - synchronized (mLock) { - logAndPrintln(pw, "Stop tracing to " + mBufferBuilder.getFile() - + ". Waiting for traces to flush."); + synchronized (mEnabledLock) { + logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush."); mEnabled = mEnabledLockFree = false; - synchronized (mLock) { - if (mEnabled) { - logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); - throw new IllegalStateException("tracing enabled while waiting for flush."); - } - writeTraceToFileLocked(); - mTraceBuffer = null; + if (mEnabled) { + logAndPrintln(pw, "ERROR: tracing was re-enabled while waiting for flush."); + throw new IllegalStateException("tracing enabled while waiting for flush."); } - logAndPrintln(pw, "Trace written to " + mBufferBuilder.getFile() + "."); + writeTraceToFileLocked(); + logAndPrintln(pw, "Trace written to " + mTraceFile + "."); } } - @VisibleForTesting - void setContinuousMode(boolean continuous, PrintWriter pw) { - logAndPrintln(pw, "Setting window tracing continuous mode to " + continuous); + private void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) { + logAndPrintln(pw, "Setting window tracing log level to " + logLevel); + mLogLevel = logLevel; - if (mEnabled) { - logAndPrintln(pw, "Trace is currently active, change will take effect once the " - + "trace is restarted."); + switch (logLevel) { + case WindowTraceLogLevel.ALL: { + setBufferCapacity(BUFFER_CAPACITY_ALL, pw); + break; + } + case WindowTraceLogLevel.TRIM: { + setBufferCapacity(BUFFER_CAPACITY_TRIM, pw); + break; + } + case WindowTraceLogLevel.CRITICAL: { + setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw); + break; + } } - mContinuousMode = continuous; - mWindowTraceLogLevel = (continuous) ? WindowTraceLogLevel.CRITICAL : - WindowTraceLogLevel.TRIM; } - boolean isEnabled() { - return mEnabledLockFree; + private void setLogFrequency(boolean onFrame, PrintWriter pw) { + logAndPrintln(pw, "Setting window tracing log frequency to " + + ((onFrame) ? "frame" : "transaction")); + mLogOnFrame = onFrame; } - static WindowTracing createDefaultAndStartLooper(WindowManagerService service, - Choreographer choreographer) { - File file = new File("/data/misc/wmtrace/wm_trace.pb"); - return new WindowTracing(file, service, choreographer); + private void setBufferCapacity(int capacity, PrintWriter pw) { + logAndPrintln(pw, "Setting window tracing buffer capacity to " + capacity + "bytes"); + mBuffer.setCapacity(capacity); + } + + boolean isEnabled() { + return mEnabledLockFree; } int onShellCommand(ShellCommand shell) { PrintWriter pw = shell.getOutPrintWriter(); - try { - String cmd = shell.getNextArgRequired(); - switch (cmd) { - case "start": - startTrace(pw); - return 0; - case "stop": - stopTrace(pw); - return 0; - case "continuous": - setContinuousMode(Boolean.valueOf(shell.getNextArgRequired()), pw); - return 0; - default: - pw.println("Unknown command: " + cmd); - return -1; - } - } catch (IOException e) { - logAndPrintln(pw, e.toString()); - throw new RuntimeException(e); + String cmd = shell.getNextArgRequired(); + switch (cmd) { + case "start": + startTrace(pw); + return 0; + case "stop": + stopTrace(pw); + return 0; + case "status": + logAndPrintln(pw, getStatus()); + return 0; + case "frame": + setLogFrequency(true /* onFrame */, pw); + mBuffer.resetBuffer(); + return 0; + case "transaction": + setLogFrequency(false /* onFrame */, pw); + mBuffer.resetBuffer(); + return 0; + case "level": + String logLevelStr = shell.getNextArgRequired().toLowerCase(); + switch (logLevelStr) { + case "all": { + setLogLevel(WindowTraceLogLevel.ALL, pw); + break; + } + case "trim": { + setLogLevel(WindowTraceLogLevel.TRIM, pw); + break; + } + case "critical": { + setLogLevel(WindowTraceLogLevel.CRITICAL, pw); + break; + } + default: { + setLogLevel(WindowTraceLogLevel.TRIM, pw); + break; + } + } + mBuffer.resetBuffer(); + return 0; + case "size": + setBufferCapacity(Integer.parseInt(shell.getNextArgRequired()) * 1024, pw); + mBuffer.resetBuffer(); + return 0; + default: + pw.println("Unknown command: " + cmd); + pw.println("Window manager trace options:"); + pw.println(" start: Start logging"); + pw.println(" stop: Stop logging"); + pw.println(" frame: Log trace once per frame"); + pw.println(" transaction: Log each transaction"); + pw.println(" size: Set the maximum log size (in KB)"); + pw.println(" level [lvl]: Set the log level between"); + pw.println(" lvl may be one of:"); + pw.println(" critical: Only visible windows with reduced information"); + pw.println(" trim: All windows with reduced"); + pw.println(" all: All window and information"); + return -1; } } + private String getStatus() { + return "Status: " + + ((isEnabled()) ? "Enabled" : "Disabled") + + "\n" + + "Log level: " + + mLogLevel + + "\n" + + mBuffer.getStatus(); + } + /** * If tracing is enabled, log the current state or schedule the next frame to be logged, - * according to {@link #mContinuousMode}. + * according to {@link #mLogOnFrame}. * * @param where Logging point descriptor */ @@ -188,7 +238,7 @@ class WindowTracing { return; } - if (mContinuousMode) { + if (mLogOnFrame) { schedule(); } else { log(where); @@ -215,25 +265,24 @@ class WindowTracing { private void log(String where) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked"); try { - synchronized (mGlobalLock) { - ProtoOutputStream os = new ProtoOutputStream(); - long tokenOuter = os.start(ENTRY); - os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); - os.write(WHERE, where); + ProtoOutputStream os = new ProtoOutputStream(); + long tokenOuter = os.start(ENTRY); + os.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); + os.write(WHERE, where); + long tokenInner = os.start(WINDOW_MANAGER_SERVICE); + synchronized (mGlobalLock) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeToProtoLocked"); try { - long tokenInner = os.start(WINDOW_MANAGER_SERVICE); - mService.writeToProtoLocked(os, mWindowTraceLogLevel); - os.end(tokenInner); + mService.writeToProtoLocked(os, mLogLevel); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } - os.end(tokenOuter); - mTraceBuffer.add(os); - - mScheduled = false; } + os.end(tokenInner); + os.end(tokenOuter); + mBuffer.add(os); + mScheduled = false; } catch (Exception e) { Log.wtf(TAG, "Exception while tracing state", e); } finally { @@ -242,31 +291,37 @@ class WindowTracing { } /** - * Writes the trace buffer to disk. This method has no internal synchronization and should be - * externally synchronized + * Writes the trace buffer to new file for the bugreport. + * + * This method is synchronized with {@code #startTrace(PrintWriter)} and + * {@link #stopTrace(PrintWriter)}. */ - private void writeTraceToFileLocked() { - if (mTraceBuffer == null) { - return; + void writeTraceToFile() { + synchronized (mEnabledLock) { + writeTraceToFileLocked(); } + } - try { - mTraceBuffer.dump(); - } catch (IOException e) { - Log.e(TAG, "Unable to write buffer to file", e); - } catch (InterruptedException e) { - Log.e(TAG, "Unable to interrupt window tracing file write thread", e); + private void logAndPrintln(@Nullable PrintWriter pw, String msg) { + Log.i(TAG, msg); + if (pw != null) { + pw.println(msg); + pw.flush(); } } /** - * Writes the trace buffer to disk and clones it into a new file for the bugreport. - * This method is synchronized with {@code #startTrace(PrintWriter)} and - * {@link #stopTrace(PrintWriter)}. + * Writes the trace buffer to disk. This method has no internal synchronization and should be + * externally synchronized */ - void writeTraceToFile() { - synchronized (mLock) { - writeTraceToFileLocked(); + private void writeTraceToFileLocked() { + try { + Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "writeTraceToFileLocked"); + mBuffer.writeTraceToFile(mTraceFile); + } catch (IOException e) { + Log.e(TAG, "Unable to write buffer to file", e); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); } } -} +}
\ No newline at end of file diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java index 2b8e307e23b7..b299f0dd7253 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTraceBufferTest.java @@ -36,11 +36,10 @@ import org.junit.Before; import org.junit.Test; import java.io.File; -import java.io.IOException; /** - * Test class for {@link WindowTraceBuffer} and {@link WindowTraceQueueBuffer}. + * Test class for {@link WindowTraceBuffer}. * * Build/Install/Run: * atest WmTests:WindowTraceBufferTest @@ -49,12 +48,15 @@ import java.io.IOException; @Presubmit public class WindowTraceBufferTest { private File mFile; + private WindowTraceBuffer mBuffer; @Before public void setUp() throws Exception { final Context testContext = getInstrumentation().getContext(); mFile = testContext.getFileStreamPath("tracing_test.dat"); mFile.delete(); + + mBuffer = new WindowTraceBuffer(10); } @After @@ -63,145 +65,112 @@ public class WindowTraceBufferTest { } @Test - public void testTraceQueueBuffer_addItem() throws Exception { - ProtoOutputStream toWrite1 = getDummy(1); - ProtoOutputStream toWrite2 = getDummy(2); - ProtoOutputStream toWrite3 = getDummy(3); - final int objectSize = toWrite1.getRawSize(); - final int bufferCapacity = objectSize * 2; - - final WindowTraceBuffer buffer = buildQueueBuffer(bufferCapacity); - - buffer.add(toWrite1); - byte[] toWrite1Bytes = toWrite1.getBytes(); - assertTrue("First element should be in the list", - buffer.contains(toWrite1Bytes)); - - buffer.add(toWrite2); - byte[] toWrite2Bytes = toWrite2.getBytes(); - assertTrue("First element should be in the list", - buffer.contains(toWrite1Bytes)); - assertTrue("Second element should be in the list", - buffer.contains(toWrite2Bytes)); - - buffer.add(toWrite3); - byte[] toWrite3Bytes = toWrite3.getBytes(); - assertTrue("First element should be in the list", - buffer.contains(toWrite1Bytes)); - assertTrue("Second element should be in the list", - buffer.contains(toWrite2Bytes)); - assertTrue("Third element should not be in the list", - !buffer.contains(toWrite3Bytes)); - - assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2); - assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity), - buffer.mBufferSize, bufferCapacity); - assertEquals("Buffer is full, available space should be 0", - buffer.getAvailableSpace(), 0); - } - - @Test - public void testTraceRingBuffer_addItem() throws Exception { + public void test_addItem() { ProtoOutputStream toWrite = getDummy(1); final int objectSize = toWrite.getRawSize(); + mBuffer.setCapacity(objectSize); + mBuffer.resetBuffer(); - final WindowTraceBuffer buffer = buildRingBuffer(objectSize); - - Preconditions.checkArgument(buffer.mBuffer.isEmpty()); + Preconditions.checkArgument(mBuffer.size() == 0); - buffer.add(toWrite); + mBuffer.add(toWrite); - assertEquals("Item was not added to the buffer", buffer.mBuffer.size(), 1); + assertEquals("Item was not added to the buffer", 1, mBuffer.size()); assertEquals("Total buffer getSize differs from inserted object", - buffer.mBufferSize, objectSize); - assertEquals("Available buffer space does not match used one", - buffer.getAvailableSpace(), 0); + mBuffer.getBufferSize(), objectSize); + assertEquals("Available buffer space does not match used one", 0, + mBuffer.getAvailableSpace()); } @Test - public void testTraceRingBuffer_addItemMustOverwriteOne() throws Exception { + public void test_addItemMustOverwriteOne() { ProtoOutputStream toWrite1 = getDummy(1); ProtoOutputStream toWrite2 = getDummy(2); ProtoOutputStream toWrite3 = getDummy(3); final int objectSize = toWrite1.getRawSize(); - final int bufferCapacity = objectSize * 2 + 1; - final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity); + mBuffer.setCapacity(bufferCapacity); + mBuffer.resetBuffer(); - buffer.add(toWrite1); + mBuffer.add(toWrite1); byte[] toWrite1Bytes = toWrite1.getBytes(); assertTrue("First element should be in the list", - buffer.contains(toWrite1Bytes)); + mBuffer.contains(toWrite1Bytes)); - buffer.add(toWrite2); + mBuffer.add(toWrite2); byte[] toWrite2Bytes = toWrite2.getBytes(); assertTrue("First element should be in the list", - buffer.contains(toWrite1Bytes)); + mBuffer.contains(toWrite1Bytes)); assertTrue("Second element should be in the list", - buffer.contains(toWrite2Bytes)); + mBuffer.contains(toWrite2Bytes)); - buffer.add(toWrite3); + mBuffer.add(toWrite3); byte[] toWrite3Bytes = toWrite3.getBytes(); assertTrue("First element should not be in the list", - !buffer.contains(toWrite1Bytes)); + !mBuffer.contains(toWrite1Bytes)); assertTrue("Second element should be in the list", - buffer.contains(toWrite2Bytes)); + mBuffer.contains(toWrite2Bytes)); assertTrue("Third element should be in the list", - buffer.contains(toWrite3Bytes)); - assertEquals("Buffer should have 2 elements", buffer.mBuffer.size(), 2); + mBuffer.contains(toWrite3Bytes)); + assertEquals("Buffer should have 2 elements", 2, mBuffer.size()); assertEquals(String.format("Buffer is full, used space should be %d", bufferCapacity), - buffer.mBufferSize, bufferCapacity - 1); - assertEquals(" Buffer is full, available space should be 0", - buffer.getAvailableSpace(), 1); + mBuffer.getBufferSize(), bufferCapacity - 1); + assertEquals(" Buffer is full, available space should be 0", 1, + mBuffer.getAvailableSpace()); } @Test - public void testTraceRingBuffer_addItemMustOverwriteMultiple() throws Exception { + public void test_addItemMustOverwriteMultiple() { ProtoOutputStream toWriteSmall1 = getDummy(1); ProtoOutputStream toWriteSmall2 = getDummy(2); final int objectSize = toWriteSmall1.getRawSize(); - final int bufferCapacity = objectSize * 2; - final WindowTraceBuffer buffer = buildRingBuffer(bufferCapacity); + mBuffer.setCapacity(bufferCapacity); + mBuffer.resetBuffer(); ProtoOutputStream toWriteBig = new ProtoOutputStream(); toWriteBig.write(MAGIC_NUMBER, 1); toWriteBig.write(MAGIC_NUMBER, 2); - buffer.add(toWriteSmall1); + mBuffer.add(toWriteSmall1); byte[] toWriteSmall1Bytes = toWriteSmall1.getBytes(); assertTrue("First element should be in the list", - buffer.contains(toWriteSmall1Bytes)); + mBuffer.contains(toWriteSmall1Bytes)); - buffer.add(toWriteSmall2); + mBuffer.add(toWriteSmall2); byte[] toWriteSmall2Bytes = toWriteSmall2.getBytes(); assertTrue("First element should be in the list", - buffer.contains(toWriteSmall1Bytes)); + mBuffer.contains(toWriteSmall1Bytes)); assertTrue("Second element should be in the list", - buffer.contains(toWriteSmall2Bytes)); + mBuffer.contains(toWriteSmall2Bytes)); - buffer.add(toWriteBig); + mBuffer.add(toWriteBig); byte[] toWriteBigBytes = toWriteBig.getBytes(); assertTrue("Third element should overwrite all others", - !buffer.contains(toWriteSmall1Bytes)); + !mBuffer.contains(toWriteSmall1Bytes)); assertTrue("Third element should overwrite all others", - !buffer.contains(toWriteSmall2Bytes)); + !mBuffer.contains(toWriteSmall2Bytes)); assertTrue("Third element should overwrite all others", - buffer.contains(toWriteBigBytes)); + mBuffer.contains(toWriteBigBytes)); - assertEquals(" Buffer should have only 1 big element", buffer.mBuffer.size(), 1); + assertEquals(" Buffer should have only 1 big element", 1, mBuffer.size()); assertEquals(String.format(" Buffer is full, used space should be %d", bufferCapacity), - buffer.mBufferSize, bufferCapacity); - assertEquals(" Buffer is full, available space should be 0", - buffer.getAvailableSpace(), 0); + mBuffer.getBufferSize(), bufferCapacity); + assertEquals(" Buffer is full, available space should be 0", 0, + mBuffer.getAvailableSpace()); } - private WindowTraceBuffer buildRingBuffer(int capacity) throws IOException { - return new WindowTraceBuffer.Builder() - .setContinuousMode(true) - .setBufferCapacity(capacity) - .setTraceFile(mFile) - .build(); + @Test + public void test_startResetsBuffer() { + ProtoOutputStream toWrite = getDummy(1); + mBuffer.resetBuffer(); + Preconditions.checkArgument(mBuffer.size() == 0); + + mBuffer.add(toWrite); + assertEquals("Item was not added to the buffer", 1, mBuffer.size()); + mBuffer.resetBuffer(); + assertEquals("Buffer should be empty after reset", 0, mBuffer.size()); + assertEquals("Buffer size should be 0 after reset", 0, mBuffer.getBufferSize()); } private ProtoOutputStream getDummy(int value) { @@ -212,7 +181,4 @@ public class WindowTraceBufferTest { return toWrite; } - private WindowTraceBuffer buildQueueBuffer(int size) throws IOException { - return new WindowTraceQueueBuffer(size, mFile, false); - } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java index 3c6e2405adff..8358fdd18e0e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingTest.java @@ -88,8 +88,7 @@ public class WindowTracingTest { mFile.delete(); mWindowTracing = new WindowTracing(mFile, mWmMock, mChoreographer, - new WindowManagerGlobalLock()); - mWindowTracing.setContinuousMode(false /* continuous */, null /* pw */); + new WindowManagerGlobalLock(), 1024); } @After @@ -103,13 +102,13 @@ public class WindowTracingTest { } @Test - public void isEnabled_returnsTrueAfterStart() throws Exception { + public void isEnabled_returnsTrueAfterStart() { mWindowTracing.startTrace(mock(PrintWriter.class)); assertTrue(mWindowTracing.isEnabled()); } @Test - public void isEnabled_returnsFalseAfterStop() throws Exception { + public void isEnabled_returnsFalseAfterStop() { mWindowTracing.startTrace(mock(PrintWriter.class)); mWindowTracing.stopTrace(mock(PrintWriter.class)); assertFalse(mWindowTracing.isEnabled()); @@ -133,6 +132,8 @@ public class WindowTracingTest { mWindowTracing.startTrace(mock(PrintWriter.class)); mWindowTracing.stopTrace(mock(PrintWriter.class)); + assertTrue("Trace file should exist", mFile.exists()); + byte[] header = new byte[MAGIC_HEADER.length]; try (InputStream is = new FileInputStream(mFile)) { assertEquals(MAGIC_HEADER.length, is.read(header)); diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java index bd0d4ae27800..ae12a17e1e2f 100644 --- a/telecomm/java/android/telecom/Connection.java +++ b/telecomm/java/android/telecom/Connection.java @@ -623,7 +623,7 @@ public abstract class Connection extends Conferenceable { "android.telecom.event.HANDOVER_FAILED"; /** - * Connection extra key used to store SIP invite fields for an incoming call for IMS calls + * String Connection extra key used to store SIP invite fields for an incoming call for IMS call */ public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE"; diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index a1c32b5f80a3..2462beeb28a8 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2123,6 +2123,11 @@ public final class Telephony { * @hide - not meant for public use */ public interface RcsColumns { + // TODO(sahinc): Turn this to true once the schema finalizes, so that people can update + // their messaging databases. NOTE: move the switch/case update in MmsSmsDatabaseHelper to + // the latest version of the database before turning this flag to true. + boolean IS_RCS_TABLE_SCHEMA_CODE_COMPLETE = false; + /** * The authority for the content provider */ diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java index 73f055649b3f..d5c70793d3bc 100644 --- a/telephony/java/android/telephony/ims/ImsCallProfile.java +++ b/telephony/java/android/telephony/ims/ImsCallProfile.java @@ -266,6 +266,11 @@ public final class ImsCallProfile implements Parcelable { public static final String EXTRA_DISPLAY_TEXT = "DisplayText"; public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo"; public static final String EXTRA_IS_CALL_PULL = "CallPull"; + + /** + * String extra property + * Containing fields from the SIP INVITE message for an IMS call + */ public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS"; diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 2f8ca2d62061..b6691702b3ee 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -1113,6 +1113,13 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); if (element_namespace.empty() && element_name == "item") { + if (current_policies == OverlayableItem::Policy::kNone) { + diag_->Error(DiagMessage(element_source) + << "<item> within an <overlayable> must be inside a <policy> block"); + error = true; + continue; + } + // Items specify the name and type of resource that should be overlayable Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name"); if (!item_name) { @@ -1169,6 +1176,8 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource current_policies |= OverlayableItem::Policy::kSystem; } else if (trimmed_part == "vendor") { current_policies |= OverlayableItem::Policy::kVendor; + } else if (trimmed_part == "signature") { + current_policies |= OverlayableItem::Policy::kSignature; } else { diag_->Error(DiagMessage(element_source) << "<policy> has unsupported type '" << trimmed_part << "'"); @@ -1176,6 +1185,11 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource continue; } } + } else { + diag_->Error(DiagMessage(element_source) + << "<policy> must have a 'type' attribute"); + error = true; + continue; } } else if (!ShouldIgnoreElement(element_namespace, element_name)) { diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> " diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 827c7deaf452..25b76b0c1fa0 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -894,8 +894,10 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { TEST_F(ResourceParserTest, ParseOverlayable) { std::string input = R"( <overlayable name="Name" actor="overlay://theme"> - <item type="string" name="foo" /> - <item type="drawable" name="bar" /> + <policy type="signature"> + <item type="string" name="foo" /> + <item type="drawable" name="bar" /> + </policy> </overlayable>)"; ASSERT_TRUE(TestParse(input)); @@ -906,7 +908,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) { OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); ASSERT_TRUE(search_result); @@ -915,7 +917,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) { result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); } TEST_F(ResourceParserTest, ParseOverlayableRequiresName) { @@ -931,7 +933,6 @@ TEST_F(ResourceParserTest, ParseOverlayableBadActorFail) { TEST_F(ResourceParserTest, ParseOverlayablePolicy) { std::string input = R"( <overlayable name="Name"> - <item type="string" name="foo" /> <policy type="product"> <item type="string" name="bar" /> </policy> @@ -944,23 +945,18 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { <policy type="public"> <item type="string" name="faz" /> </policy> + <policy type="signature"> + <item type="string" name="foz" /> + </policy> </overlayable>)"; ASSERT_TRUE(TestParse(input)); - auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo")); + auto search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); - - search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); - ASSERT_TRUE(search_result); - ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable_item); - result_overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct)); search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); @@ -986,6 +982,30 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); + + search_result = table_.FindResource(test::ParseNameOrDie("string/foz")); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); +} + +TEST_F(ResourceParserTest, ParseOverlayableNoPolicyError) { + std::string input = R"( + <overlayable name="Name"> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable name="Name"> + <policy> + <item name="foo" /> + </policy> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 7ca99ea42b50..32dfd260e53c 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -92,6 +92,9 @@ struct OverlayableItem { // The resource can be overlaid by any overlay on the product partition. kProduct = 0x08, + + // The resource can be overlaid by any overlay signed with the same signature as its actor. + kSignature = 0x010, }; std::shared_ptr<Overlayable> overlayable; diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index 9a1d94288363..a2fd7c664b04 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -138,10 +138,10 @@ message AllowNew { // Represents a set of overlayable resources. message Overlayable { - // The name of the <overlyabale>. + // The name of the <overlayable>. string name = 1; - // The location of the <overlyabale> declaration in the source. + // The location of the <overlayable> declaration in the source. Source source = 2; // The component responsible for enabling and disabling overlays targeting this <overlayable>. diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index 40aaa05c2b30..14906adec782 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -473,6 +473,10 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { policies |= OverlayableItem::Policy::kProduct; } + if (policy_header->policy_flags + & ResTable_overlayable_policy_header::POLICY_SIGNATURE) { + policies |= OverlayableItem::Policy::kSignature; + } const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize)); diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 9d341cc1ca4a..5d7e291447d9 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -274,7 +274,9 @@ class PackageFlattener { FlattenLibrarySpec(buffer); } - FlattenOverlayable(buffer); + if (!FlattenOverlayable(buffer)) { + return false; + } pkg_writer.Finish(); return true; @@ -468,23 +470,29 @@ class PackageFlattener { overlayable_chunk = &chunk; } + if (item.policies == 0) { + context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source) + << "overlayable " + << entry->name + << " does not specify policy"); + return false; + } + uint32_t policy_flags = 0; - if (item.policies == OverlayableItem::Policy::kNone) { - // Encode overlayable entries defined without a policy as publicly overlayable + if (item.policies & OverlayableItem::Policy::kPublic) { policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; - } else { - if (item.policies & OverlayableItem::Policy::kPublic) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; - } - if (item.policies & OverlayableItem::Policy::kSystem) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kVendor) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kProduct) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; - } + } + if (item.policies & OverlayableItem::Policy::kSystem) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; + } + if (item.policies & OverlayableItem::Policy::kVendor) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; + } + if (item.policies & OverlayableItem::Policy::kProduct) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; + } + if (item.policies & OverlayableItem::Policy::kSignature) { + policy_flags |= ResTable_overlayable_policy_header::POLICY_SIGNATURE; } auto policy = overlayable_chunk->policy_ids.find(policy_flags); diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index ddc117399390..4c5dbec8ade8 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -671,9 +671,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { overlayable_item_two.policies |= OverlayableItem::Policy::kSystem; overlayable_item_two.policies |= OverlayableItem::Policy::kVendor; - std::string name_three = "com.app.test:integer/overlayable_three_item"; - OverlayableItem overlayable_item_three(overlayable); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -683,8 +680,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { .SetOverlayable(name_one, overlayable_item_one) .AddSimple(name_two, ResourceId(0x7f020002)) .SetOverlayable(name_two, overlayable_item_two) - .AddSimple(name_three, ResourceId(0x7f020003)) - .SetOverlayable(name_three, overlayable_item_three) .Build(); ResourceTable output_table; @@ -713,16 +708,6 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem | OverlayableItem::Policy::kProduct | OverlayableItem::Policy::kVendor); - - search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); - ASSERT_TRUE(search_result); - ASSERT_THAT(search_result.value().entry, NotNull()); - ASSERT_TRUE(search_result.value().entry->overlayable_item); - overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); - EXPECT_EQ(overlayable_item.overlayable->name, "TestName"); - EXPECT_EQ(overlayable_item.overlayable->actor, "overlay://theme"); - EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { @@ -745,6 +730,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { std::string name_three = "com.app.test:integer/overlayable_three"; OverlayableItem overlayable_item_three(group_one); + overlayable_item_three.policies |= OverlayableItem::Policy::kSignature; + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -793,7 +780,22 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { result_overlayable = search_result.value().entry->overlayable_item.value(); EXPECT_EQ(result_overlayable.overlayable->name, "OtherName"); EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization"); - EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kPublic); + EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSignature); +} + +TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) { + auto group = std::make_shared<Overlayable>("TestName", "overlay://theme"); + std::string name_zero = "com.app.test:integer/overlayable_zero"; + OverlayableItem overlayable_item_zero(group); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple(name_zero, ResourceId(0x7f020000)) + .SetOverlayable(name_zero, overlayable_item_zero) + .Build(); + ResourceTable output_table; + ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table)); } } // namespace aapt diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index aff1b391f861..06f1bf74d7e7 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -390,6 +390,9 @@ bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable, case pb::OverlayableItem::PRODUCT: out_overlayable->policies |= OverlayableItem::Policy::kProduct; break; + case pb::OverlayableItem::SIGNATURE: + out_overlayable->policies |= OverlayableItem::Policy::kSignature; + break; default: *out_error = "unknown overlayable policy"; return false; diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index b549e2369f98..eb2b1a2f35d3 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -309,6 +309,9 @@ static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item if (overlayable_item.policies & OverlayableItem::Policy::kVendor) { pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR); } + if (overlayable_item.policies & OverlayableItem::Policy::kSignature) { + pb_overlayable_item->add_policy(pb::OverlayableItem::SIGNATURE); + } SerializeSourceToPb(overlayable_item.source, source_pool, pb_overlayable_item->mutable_source()); diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index cce3939704cf..d369ac4c8816 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -526,6 +526,10 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { "FontPack", "overlay://theme")); overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic; + OverlayableItem overlayable_item_boz(std::make_shared<Overlayable>( + "IconPack", "overlay://theme")); + overlayable_item_boz.policies |= OverlayableItem::Policy::kSignature; + OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>( "Other", "overlay://customization")); overlayable_item_biz.comment ="comment"; @@ -536,6 +540,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { .SetOverlayable("com.app.a:bool/foo", overlayable_item_foo) .SetOverlayable("com.app.a:bool/bar", overlayable_item_bar) .SetOverlayable("com.app.a:bool/baz", overlayable_item_baz) + .SetOverlayable("com.app.a:bool/boz", overlayable_item_boz) .SetOverlayable("com.app.a:bool/biz", overlayable_item_biz) .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true")) .Build(); @@ -576,6 +581,14 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/boz")); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("IconPack")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); |