diff options
175 files changed, 8325 insertions, 1656 deletions
diff --git a/Android.bp b/Android.bp index b075f5c0a435..7bdedc79943d 100644 --- a/Android.bp +++ b/Android.bp @@ -77,7 +77,6 @@ java_defaults { "core/java/android/app/ISearchManager.aidl", "core/java/android/app/ISearchManagerCallback.aidl", "core/java/android/app/IServiceConnection.aidl", - "core/java/android/app/ISmsAppService.aidl", "core/java/android/app/IStopUserCallback.aidl", "core/java/android/app/job/IJobCallback.aidl", "core/java/android/app/job/IJobScheduler.aidl", @@ -288,6 +287,7 @@ java_defaults { "core/java/android/service/carrier/ICarrierService.aidl", "core/java/android/service/carrier/ICarrierMessagingCallback.aidl", "core/java/android/service/carrier/ICarrierMessagingService.aidl", + "core/java/android/service/carrier/ICarrierMessagingClientService.aidl", "core/java/android/service/contentsuggestions/IContentSuggestionsService.aidl", "core/java/android/service/euicc/IDeleteSubscriptionCallback.aidl", "core/java/android/service/euicc/IDownloadSubscriptionCallback.aidl", diff --git a/api/current.txt b/api/current.txt index 16b7b74b08c6..288f59e6e4b1 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26,6 +26,7 @@ package android { field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET"; field public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE"; field public static final String BIND_CALL_REDIRECTION_SERVICE = "android.permission.BIND_CALL_REDIRECTION_SERVICE"; + field public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"; field @Deprecated public static final String BIND_CARRIER_MESSAGING_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_SERVICE"; field public static final String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES"; field public static final String BIND_CHOOSER_TARGET_SERVICE = "android.permission.BIND_CHOOSER_TARGET_SERVICE"; @@ -41,7 +42,6 @@ package android { field public static final String BIND_QUICK_SETTINGS_TILE = "android.permission.BIND_QUICK_SETTINGS_TILE"; field public static final String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS"; field public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE"; - field public static final String BIND_SMS_APP_SERVICE = "android.permission.BIND_SMS_APP_SERVICE"; field public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission.BIND_TELECOM_CONNECTION_SERVICE"; field public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE"; field public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT"; @@ -645,6 +645,7 @@ package android { field public static final int footerDividersEnabled = 16843311; // 0x101022f field public static final int forceDarkAllowed = 16844172; // 0x101058c field public static final int forceHasOverlappingRendering = 16844065; // 0x1010521 + field public static final int forceUriPermissions = 16844197; // 0x10105a5 field public static final int foreground = 16843017; // 0x1010109 field public static final int foregroundGravity = 16843264; // 0x1010200 field public static final int foregroundServiceType = 16844191; // 0x101059f @@ -6210,11 +6211,6 @@ package android.app { method public void onSharedElementsReady(); } - public class SmsAppService extends android.app.Service { - ctor public SmsAppService(); - method public final android.os.IBinder onBind(android.content.Intent); - } - public class StatusBarManager { } @@ -6742,6 +6738,7 @@ package android.app.admin { method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>); method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean); method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean); + method public void setDefaultSmsApplication(@NonNull android.content.ComponentName, @NonNull String); method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>); method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence); method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence); @@ -7252,6 +7249,7 @@ package android.app.backup { ctor public BackupManager(android.content.Context); method public void dataChanged(); method public static void dataChanged(String); + method @Nullable public android.os.UserHandle getUserForAncestralSerialNumber(long); method @Deprecated public int requestRestore(android.app.backup.RestoreObserver); } @@ -11879,6 +11877,7 @@ package android.content.pm { field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public String authority; field public int flags; + field public boolean forceUriPermissions; field public boolean grantUriPermissions; field public int initOrder; field @Deprecated public boolean isSyncable; @@ -23747,6 +23746,7 @@ package android.media { ctor public ExifInterface(@NonNull java.io.InputStream) throws java.io.IOException; method public double getAltitude(double); method @Nullable public String getAttribute(@NonNull String); + method @Nullable public byte[] getAttributeBytes(@NonNull String); method public double getAttributeDouble(@NonNull String, double); method public int getAttributeInt(@NonNull String, int); method @Nullable public long[] getAttributeRange(@NonNull String); @@ -23902,6 +23902,7 @@ package android.media { field public static final String TAG_USER_COMMENT = "UserComment"; field public static final String TAG_WHITE_BALANCE = "WhiteBalance"; field public static final String TAG_WHITE_POINT = "WhitePoint"; + field public static final String TAG_XMP = "Xmp"; field public static final String TAG_X_RESOLUTION = "XResolution"; field public static final String TAG_Y_CB_CR_COEFFICIENTS = "YCbCrCoefficients"; field public static final String TAG_Y_CB_CR_POSITIONING = "YCbCrPositioning"; @@ -34202,7 +34203,6 @@ package android.os { } public static class Build.Partition { - ctor public Build.Partition(); method public long getBuildTimeMillis(); method @NonNull public String getFingerprint(); method @NonNull public String getName(); @@ -41360,6 +41360,11 @@ package android.service.carrier { field public static final android.os.Parcelable.Creator<android.service.carrier.CarrierIdentifier> CREATOR; } + public class CarrierMessagingClientService extends android.app.Service { + ctor public CarrierMessagingClientService(); + method public final android.os.IBinder onBind(android.content.Intent); + } + public abstract class CarrierMessagingService extends android.app.Service { ctor public CarrierMessagingService(); method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent); @@ -45166,13 +45171,13 @@ package android.telephony { method @Deprecated public void setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void switchMultiSimConfig(int); method public boolean updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>); + field public static final String ACTION_CARRIER_MESSAGING_CLIENT_SERVICE = "android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"; field public static final String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL"; field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; - field public static final String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE"; field public static final String ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_CARRIER_IDENTITY_CHANGED"; field public static final String ACTION_SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED = "android.telephony.action.SUBSCRIPTION_PRECISE_CARRIER_IDENTITY_CHANGED"; field public static final int APPTYPE_CSIM = 4; // 0x4 @@ -56368,7 +56373,11 @@ package android.widget { method @Nullable public android.graphics.PorterDuff.Mode getIndeterminateTintMode(); method public android.view.animation.Interpolator getInterpolator(); method @android.view.ViewDebug.ExportedProperty(category="progress") public int getMax(); + method @Px public int getMaxHeight(); + method @Px public int getMaxWidth(); method @android.view.ViewDebug.ExportedProperty(category="progress") public int getMin(); + method @Px public int getMinHeight(); + method @Px public int getMinWidth(); method @android.view.ViewDebug.ExportedProperty(category="progress") public int getProgress(); method @Nullable public android.content.res.ColorStateList getProgressBackgroundTintList(); method @Nullable public android.graphics.PorterDuff.Mode getProgressBackgroundTintMode(); @@ -56392,7 +56401,11 @@ package android.widget { method public void setInterpolator(android.content.Context, @InterpolatorRes int); method public void setInterpolator(android.view.animation.Interpolator); method public void setMax(int); + method public void setMaxHeight(@Px int); + method public void setMaxWidth(@Px int); method public void setMin(int); + method public void setMinHeight(@Px int); + method public void setMinWidth(@Px int); method public void setProgress(int); method public void setProgress(int, boolean); method public void setProgressBackgroundTintList(@Nullable android.content.res.ColorStateList); diff --git a/api/system-current.txt b/api/system-current.txt index a0eef1d64bcc..8d7ec261e52e 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -711,6 +711,7 @@ package android.app.backup { method @Deprecated public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor); method @Deprecated @RequiresPermission(android.Manifest.permission.BACKUP) public String selectBackupTransport(String); method @RequiresPermission(android.Manifest.permission.BACKUP) public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback); + method @RequiresPermission(android.Manifest.permission.BACKUP) public void setAncestralSerialNumber(long); method @RequiresPermission(android.Manifest.permission.BACKUP) public void setAutoRestore(boolean); method @RequiresPermission(android.Manifest.permission.BACKUP) public void setBackupEnabled(boolean); method @RequiresPermission(android.Manifest.permission.BACKUP) public void updateTransportAttributes(android.content.ComponentName, String, @Nullable android.content.Intent, String, @Nullable android.content.Intent, @Nullable String); @@ -3912,6 +3913,7 @@ package android.net { public class ConnectivityManager { method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); + method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback); method public boolean getAvoidBadWifi(); method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl(); method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler); diff --git a/cmds/statsd/src/external/PullDataReceiver.h b/cmds/statsd/src/external/PullDataReceiver.h index 0d505cb49e8f..b071682f8a59 100644 --- a/cmds/statsd/src/external/PullDataReceiver.h +++ b/cmds/statsd/src/external/PullDataReceiver.h @@ -28,9 +28,15 @@ namespace statsd { class PullDataReceiver : virtual public RefBase{ public: virtual ~PullDataReceiver() {} - virtual void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) = 0; + /** + * @param data The pulled data. + * @param pullSuccess Whether the pull succeeded. If the pull does not succeed, the data for the + * bucket should be invalidated. + */ + virtual void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data, + bool pullSuccess) = 0; }; } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index c69384c7077f..9b603d6c7957 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -358,12 +358,13 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { for (const auto& pullInfo : needToPull) { vector<shared_ptr<LogEvent>> data; - if (!Pull(pullInfo.first, &data)) { + bool pullSuccess = Pull(pullInfo.first, &data); + if (pullSuccess) { + StatsdStats::getInstance().notePullDelay( + pullInfo.first, getElapsedRealtimeNs() - elapsedTimeNs); + } else { VLOG("pull failed at %lld, will try again later", (long long)elapsedTimeNs); - continue; } - StatsdStats::getInstance().notePullDelay(pullInfo.first, - getElapsedRealtimeNs() - elapsedTimeNs); // Convention is to mark pull atom timestamp at request time. // If we pull at t0, puller starts at t1, finishes at t2, and send back @@ -380,8 +381,8 @@ void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) { for (const auto& receiverInfo : pullInfo.second) { sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote(); if (receiverPtr != nullptr) { - receiverPtr->onDataPulled(data); - // we may have just come out of a coma, compute next pull time + receiverPtr->onDataPulled(data, pullSuccess); + // We may have just come out of a coma, compute next pull time. int numBucketsAhead = (elapsedTimeNs - receiverInfo->nextPullTimeNs) / receiverInfo->intervalNs; receiverInfo->nextPullTimeNs += (numBucketsAhead + 1) * receiverInfo->intervalNs; diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 37ccad5f4a49..a5bd5c6b6364 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -448,6 +448,11 @@ void StatsdStats::noteConditionChangeInNextBucket(int metricId) { getAtomMetricStats(metricId).conditionChangeInNextBucket++; } +void StatsdStats::noteInvalidatedBucket(int metricId) { + lock_guard<std::mutex> lock(mLock); + getAtomMetricStats(metricId).invalidatedBucket++; +} + StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int metricId) { auto atomMetricStatsIter = mAtomMetricStats.find(metricId); if (atomMetricStatsIter != mAtomMetricStats.end()) { diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 01e9ca17e5fd..cb17061c1ed1 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -365,6 +365,11 @@ public: void noteConditionChangeInNextBucket(int atomId); /** + * A bucket has been tagged as invalid. + */ + void noteInvalidatedBucket(int metricId); + + /** * Reset the historical stats. Including all stats in icebox, and the tracked stats about * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue * to collect stats after reset() has been called. @@ -408,6 +413,7 @@ public: long skippedForwardBuckets = 0; long badValueType = 0; long conditionChangeInNextBucket = 0; + long invalidatedBucket = 0; } AtomMetricStats; private: diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index c2878f02a691..c9b71659aa58 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -406,9 +406,10 @@ std::shared_ptr<vector<FieldValue>> GaugeMetricProducer::getGaugeFields(const Lo return gaugeFields; } -void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { +void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, + bool pullSuccess) { std::lock_guard<std::mutex> lock(mMutex); - if (allData.size() == 0) { + if (!pullSuccess || allData.size() == 0) { return; } for (const auto& data : allData) { diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index df0877954d70..d480941ed311 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -67,7 +67,8 @@ public: virtual ~GaugeMetricProducer(); // Handles when the pulled data arrives. - void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override; + void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data, + bool pullSuccess) override; // GaugeMetric needs to immediately trigger another pull when we create the partial bucket. void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 6aa8e842b021..9fb78e780870 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -104,6 +104,7 @@ ValueMetricProducer::ValueMetricProducer( mSkipZeroDiffOutput(metric.skip_zero_diff_output()), mUseZeroDefaultBase(metric.use_zero_default_base()), mHasGlobalBase(false), + mCurrentBucketIsInvalid(false), mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC : StatsdStats::kPullMaxDelayNs), mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()) { @@ -308,6 +309,15 @@ void ValueMetricProducer::onDumpReportLocked(const int64_t dumpTimeNs, } } +void ValueMetricProducer::invalidateCurrentBucket() { + if (!mCurrentBucketIsInvalid) { + // Only report once per invalid bucket. + StatsdStats::getInstance().noteInvalidatedBucket(mMetricId); + } + mCurrentBucketIsInvalid = true; + resetBase(); +} + void ValueMetricProducer::resetBase() { for (auto& slice : mCurrentSlicedBucket) { for (auto& interval : slice.second) { @@ -323,6 +333,7 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); StatsdStats::getInstance().noteConditionChangeInNextBucket(mMetricId); + invalidateCurrentBucket(); return; } @@ -346,19 +357,20 @@ void ValueMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) { vector<std::shared_ptr<LogEvent>> allData; if (!mPullerManager->Pull(mPullTagId, &allData)) { ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); - resetBase(); + invalidateCurrentBucket(); return; } const int64_t pullDelayNs = getElapsedRealtimeNs() - timestampNs; + StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); if (pullDelayNs > mMaxPullDelayNs) { ALOGE("Pull finish too late for atom %d, longer than %lld", mPullTagId, (long long)mMaxPullDelayNs); StatsdStats::getInstance().notePullExceedMaxDelay(mPullTagId); - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); - resetBase(); + // We are missing one pull from the bucket which means we will not have a complete view of + // what's going on. + invalidateCurrentBucket(); return; } - StatsdStats::getInstance().notePullDelay(mPullTagId, pullDelayNs); if (timestampNs < mCurrentBucketStartTimeNs) { // The data will be skipped in onMatchedLogEventInternalLocked, but we don't want to report @@ -382,9 +394,16 @@ int64_t ValueMetricProducer::calcPreviousBucketEndTime(const int64_t currentTime return mTimeBaseNs + ((currentTimeNs - mTimeBaseNs) / mBucketSizeNs) * mBucketSizeNs; } -void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { +void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData, + bool pullSuccess) { std::lock_guard<std::mutex> lock(mMutex); if (mCondition) { + if (!pullSuccess) { + // If the pull failed, we won't be able to compute a diff. + invalidateCurrentBucket(); + return; + } + if (allData.size() == 0) { VLOG("Data pulled is empty"); StatsdStats::getInstance().noteEmptyData(mPullTagId); @@ -399,12 +418,13 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven // if the diff base will be cleared and this new data will serve as new diff base. int64_t realEventTime = allData.at(0)->GetElapsedTimestampNs(); int64_t bucketEndTime = calcPreviousBucketEndTime(realEventTime) - 1; - if (bucketEndTime < mCurrentBucketStartTimeNs) { + bool isEventLate = bucketEndTime < mCurrentBucketStartTimeNs; + if (isEventLate) { VLOG("Skip bucket end pull due to late arrival: %lld vs %lld", (long long)bucketEndTime, (long long)mCurrentBucketStartTimeNs); StatsdStats::getInstance().noteLateLogEventSkipped(mMetricId); - return; } + for (const auto& data : allData) { LogEvent localCopy = data->makeCopy(); if (mEventMatcherWizard->matchLogEvent(localCopy, mWhatMatcherIndex) == @@ -679,31 +699,13 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { VLOG("finalizing bucket for %ld, dumping %d slices", (long)mCurrentBucketStartTimeNs, (int)mCurrentSlicedBucket.size()); int64_t fullBucketEndTimeNs = getCurrentBucketEndTimeNs(); - int64_t bucketEndTime = eventTimeNs < fullBucketEndTimeNs ? eventTimeNs : fullBucketEndTimeNs; - if (bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs) { + bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs; + if (isBucketLargeEnough && !mCurrentBucketIsInvalid) { // The current bucket is large enough to keep. for (const auto& slice : mCurrentSlicedBucket) { - ValueBucket bucket; - bucket.mBucketStartNs = mCurrentBucketStartTimeNs; - bucket.mBucketEndNs = bucketEndTime; - for (const auto& interval : slice.second) { - if (interval.hasValue) { - // skip the output if the diff is zero - if (mSkipZeroDiffOutput && mUseDiff && interval.value.isZero()) { - continue; - } - bucket.valueIndex.push_back(interval.valueIndex); - if (mAggregationType != ValueMetric::AVG) { - bucket.values.push_back(interval.value); - } else { - double sum = interval.value.type == LONG ? (double)interval.value.long_value - : interval.value.double_value; - bucket.values.push_back(Value((double)sum / interval.sampleSize)); - } - } - } + ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second); // it will auto create new vector of ValuebucketInfo if the key is not found. if (bucket.valueIndex.size() > 0) { auto& bucketList = mPastBuckets[slice.first]; @@ -714,6 +716,58 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { mSkippedBuckets.emplace_back(mCurrentBucketStartTimeNs, bucketEndTime); } + if (!mCurrentBucketIsInvalid) { + appendToFullBucket(eventTimeNs, fullBucketEndTimeNs); + } + initCurrentSlicedBucket(); + mCurrentBucketIsInvalid = false; +} + +ValueBucket ValueMetricProducer::buildPartialBucket(int64_t bucketEndTime, + const std::vector<Interval>& intervals) { + ValueBucket bucket; + bucket.mBucketStartNs = mCurrentBucketStartTimeNs; + bucket.mBucketEndNs = bucketEndTime; + for (const auto& interval : intervals) { + if (interval.hasValue) { + // skip the output if the diff is zero + if (mSkipZeroDiffOutput && mUseDiff && interval.value.isZero()) { + continue; + } + bucket.valueIndex.push_back(interval.valueIndex); + if (mAggregationType != ValueMetric::AVG) { + bucket.values.push_back(interval.value); + } else { + double sum = interval.value.type == LONG ? (double)interval.value.long_value + : interval.value.double_value; + bucket.values.push_back(Value((double)sum / interval.sampleSize)); + } + } + } + return bucket; +} + +void ValueMetricProducer::initCurrentSlicedBucket() { + for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { + bool obsolete = true; + for (auto& interval : it->second) { + interval.hasValue = false; + interval.sampleSize = 0; + if (interval.seenNewData) { + obsolete = false; + } + interval.seenNewData = false; + } + + if (obsolete) { + it = mCurrentSlicedBucket.erase(it); + } else { + it++; + } + } +} + +void ValueMetricProducer::appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs) { if (eventTimeNs > fullBucketEndTimeNs) { // If full bucket, send to anomaly tracker. // Accumulate partial buckets with current value and then send to anomaly tracker. if (mCurrentFullBucket.size() > 0) { @@ -751,24 +805,6 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs) { mCurrentFullBucket[slice.first] += slice.second[0].value.long_value; } } - - for (auto it = mCurrentSlicedBucket.begin(); it != mCurrentSlicedBucket.end();) { - bool obsolete = true; - for (auto& interval : it->second) { - interval.hasValue = false; - interval.sampleSize = 0; - if (interval.seenNewData) { - obsolete = false; - } - interval.seenNewData = false; - } - - if (obsolete) { - it = mCurrentSlicedBucket.erase(it); - } else { - it++; - } - } } size_t ValueMetricProducer::byteSizeLocked() const { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index a8dfc5ba0e5d..d9bec5d588be 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -51,7 +51,8 @@ public: virtual ~ValueMetricProducer(); // Process data pulled on bucket boundary. - void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override; + void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data, + bool pullSuccess) override; // ValueMetric needs special logic if it's a pulled atom. void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid, @@ -102,6 +103,9 @@ private: // Calculate previous bucket end time based on current time. int64_t calcPreviousBucketEndTime(const int64_t currentTimeNs); + // Mark the data as invalid. + void invalidateCurrentBucket(); + const int mWhatMatcherIndex; sp<EventMatcherWizard> mEventMatcherWizard; @@ -155,6 +159,11 @@ private: void pullAndMatchEventsLocked(const int64_t timestampNs); + ValueBucket buildPartialBucket(int64_t bucketEndTime, + const std::vector<Interval>& intervals); + void initCurrentSlicedBucket(); + void appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs); + // Reset diff base and mHasGlobalBase void resetBase(); @@ -186,6 +195,12 @@ private: // diff against. bool mHasGlobalBase; + // Invalid bucket. There was a problem in collecting data in the current bucket so we cannot + // trust any of the data in this bucket. + // + // For instance, one pull failed. + bool mCurrentBucketIsInvalid; + const int64_t mMaxPullDelayNs; const bool mSplitBucketForAppUpgrade; @@ -216,8 +231,13 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBase); FRIEND_TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures); FRIEND_TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey); - FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFail); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange); + FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket); FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate); + FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed); + FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed); + FRIEND_TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed); }; } // namespace statsd diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index cca09ac017a3..5b9148283bdc 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -417,6 +417,7 @@ message StatsdStatsReport { optional int64 skipped_forward_buckets = 4; optional int64 bad_value_type = 5; optional int64 condition_change_in_next_bucket = 6; + optional int64 invalidated_bucket = 7; } repeated AtomMetricStats atom_metric_stats = 17; diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp index 9c9985ed271c..3cb7563b206b 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -78,6 +78,7 @@ const int FIELD_ID_LATE_LOG_EVENT_SKIPPED = 3; const int FIELD_ID_SKIPPED_FORWARD_BUCKETS = 4; const int FIELD_ID_BAD_VALUE_TYPE = 5; const int FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET = 6; +const int FIELD_ID_INVALIDATED_BUCKET = 7; namespace { @@ -494,6 +495,8 @@ void writeAtomMetricStatsToStream(const std::pair<int, StatsdStats::AtomMetricSt (long long)pair.second.badValueType); protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_CONDITION_CHANGE_IN_NEXT_BUCKET, (long long)pair.second.conditionChangeInNextBucket); + protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_INVALIDATED_BUCKET, + (long long)pair.second.invalidatedBucket); protoOutput->end(token); } diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 0ffbb54c8d48..1725160c00c7 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -133,7 +133,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); auto it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); @@ -151,7 +151,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { event2->write(25); event2->init(); allData.push_back(event2); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); it = gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); @@ -305,7 +305,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { event->write(1); event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -328,7 +328,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { event->write(3); event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(3, gaugeProducer.mCurrentSlicedBucket->begin() @@ -371,7 +371,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) { event->write(1); event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -440,7 +440,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { event->write(110); event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(110, gaugeProducer.mCurrentSlicedBucket->begin() @@ -541,7 +541,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { event->write(110); event->init(); allData.push_back(event); - gaugeProducer.onDataPulled(allData); + gaugeProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); @@ -590,7 +590,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { event1->write(13); event1->init(); - gaugeProducer.onDataPulled({event1}); + gaugeProducer.onDataPulled({event1}, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(13L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -604,7 +604,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { event2->write(15); event2->init(); - gaugeProducer.onDataPulled({event2}); + gaugeProducer.onDataPulled({event2}, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(15L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -619,7 +619,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { event3->write(26); event3->init(); - gaugeProducer.onDataPulled({event3}); + gaugeProducer.onDataPulled({event3}, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_EQ(26L, gaugeProducer.mCurrentSlicedBucket->begin() ->second.front() @@ -633,7 +633,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { std::make_shared<LogEvent>(tagId, bucketStartTimeNs + 3 * bucketSizeNs + 10); event4->write("some value"); event4->init(); - gaugeProducer.onDataPulled({event4}); + gaugeProducer.onDataPulled({event4}, /** succeed */ true); EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size()); EXPECT_TRUE(gaugeProducer.mCurrentSlicedBucket->begin()->second.front().mFields->empty()); } diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index c0648ee70032..64045520fb0b 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -160,7 +160,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -177,7 +177,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { event->write(23); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -196,7 +196,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { event->write(36); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -256,7 +256,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = @@ -274,7 +274,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { event->write(23); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -292,7 +292,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering) { event->write(36); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -341,7 +341,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -357,7 +357,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { event->write(10); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -373,7 +373,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { event->write(36); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); @@ -420,7 +420,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -436,7 +436,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { event->write(10); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -451,7 +451,7 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { event->write(36); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); @@ -525,7 +525,7 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { event->write(110); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -635,7 +635,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); @@ -650,7 +650,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { event->write(150); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size()); EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs); EXPECT_EQ(20L, @@ -689,7 +689,7 @@ TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1); @@ -993,7 +993,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -1011,7 +1011,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { event->write(23); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; @@ -1032,7 +1032,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { event->write(36); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; // startUpdated:false sum:12 @@ -1120,7 +1120,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { event->write(110); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(false, curInterval.hasBase); @@ -1224,7 +1224,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { event->write(110); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0]; EXPECT_EQ(true, curInterval.hasBase); @@ -1677,7 +1677,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBase) { allData.push_back(event1); allData.push_back(event2); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval1.hasBase); EXPECT_EQ(11, interval1.base.long_value); @@ -1762,7 +1762,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { allData.push_back(event1); allData.push_back(event2); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval1.hasBase); EXPECT_EQ(11, interval1.base.long_value); @@ -1791,7 +1791,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { event1->write(5); event1->init(); allData.push_back(event1); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval2.hasBase); @@ -1813,7 +1813,7 @@ TEST(ValueMetricProducerTest, TestUseZeroDefaultBaseWithPullFailures) { event2->write(5); event2->init(); allData.push_back(event2); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval2.hasBase); @@ -1888,7 +1888,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { allData.push_back(event1); allData.push_back(event2); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval1.hasBase); EXPECT_EQ(11, interval1.base.long_value); @@ -1918,7 +1918,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { event1->write(5); event1->init(); allData.push_back(event1); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(2UL, valueProducer.mCurrentSlicedBucket.size()); @@ -1941,7 +1941,7 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { event1->write(13); event1->init(); allData.push_back(event1); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); EXPECT_EQ(true, interval2.hasBase); @@ -1951,7 +1951,60 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) { EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); } -TEST(ValueMetricProducerTest, TestResetBaseOnPullFail) { +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange_EndOfBucket) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(INT_MAX); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + // Used by onConditionChanged. + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(100); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + + valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); + // has one slice + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + EXPECT_EQ(true, curInterval.hasBase); + EXPECT_EQ(100, curInterval.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + + vector<shared_ptr<LogEvent>> allData; + valueProducer.onDataPulled(allData, /** succeed */ false); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer.mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange) { ValueMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -2007,6 +2060,56 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullFail) { EXPECT_EQ(false, valueProducer.mHasGlobalBase); } +TEST(ValueMetricProducerTest, TestResetBaseOnPullFailBeforeConditionChange) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(INT_MAX); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(100); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + + valueProducer.mCondition = true; + + vector<shared_ptr<LogEvent>> allData; + valueProducer.onDataPulled(allData, /** succeed */ false); + EXPECT_EQ(0UL, valueProducer.mCurrentSlicedBucket.size()); + + valueProducer.onConditionChanged(false, bucketStartTimeNs + 1); + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer.mHasGlobalBase); +} + TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { ValueMetric metric; metric.set_id(metricId); @@ -2052,7 +2155,7 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { event->write(110); event->init(); allData.push_back(event); - valueProducer.onDataPulled(allData); + valueProducer.onDataPulled(allData, /** succeed */ true); // has one slice EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); @@ -2073,6 +2176,250 @@ TEST(ValueMetricProducerTest, TestResetBaseOnPullTooLate) { EXPECT_EQ(false, valueProducer.mHasGlobalBase); } +TEST(ValueMetricProducerTest, TestInvalidBucketWhenOneConditionFailed) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(INT_MAX); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + // First onConditionChanged + .WillOnce(Return(false)) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(130); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + + valueProducer.mCondition = true; + + // Bucket start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData, /** succeed */ true); + + // This will fail and should invalidate the whole bucket since we do not have all the data + // needed to compute the metric value when the screen was on. + valueProducer.onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(140); + event2->init(); + allData.push_back(event2); + valueProducer.onDataPulled(allData, /** succeed */ true); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs + 1); + + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + // Contains base from last pull which was successful. + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + EXPECT_EQ(true, curInterval.hasBase); + EXPECT_EQ(140, curInterval.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestInvalidBucketWhenInitialPullFailed) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(INT_MAX); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(130); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + + valueProducer.mCondition = true; + + // Bucket start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData, /** succeed */ false); + + valueProducer.onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(140); + event2->init(); + allData.push_back(event2); + valueProducer.onDataPulled(allData, /** succeed */ true); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs + 1); + + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + // Contains base from last pull which was successful. + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + EXPECT_EQ(true, curInterval.hasBase); + EXPECT_EQ(140, curInterval.base.long_value); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(true, valueProducer.mHasGlobalBase); +} + +TEST(ValueMetricProducerTest, TestInvalidBucketWhenLastPullFailed) { + ValueMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_value_field()->set_field(tagId); + metric.mutable_value_field()->add_child()->set_field(2); + metric.set_condition(StringToId("SCREEN_ON")); + metric.set_max_pull_delay_sec(INT_MAX); + + UidMap uidMap; + SimpleAtomMatcher atomMatcher; + atomMatcher.set_atom_id(tagId); + sp<EventMatcherWizard> eventMatcherWizard = + new EventMatcherWizard({new SimpleLogMatchingTracker( + atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)}); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); + + EXPECT_CALL(*pullerManager, Pull(tagId, _)) + // First onConditionChanged + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(120); + event->init(); + data->push_back(event); + return true; + })) + // Second onConditionChanged + .WillOnce(Invoke([](int tagId, vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 8); + event->write(tagId); + event->write(130); + event->init(); + data->push_back(event); + return true; + })); + + ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex, + eventMatcherWizard, tagId, bucketStartTimeNs, + bucketStartTimeNs, pullerManager); + + valueProducer.mCondition = true; + + // Bucket start. + vector<shared_ptr<LogEvent>> allData; + allData.clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1); + event->write(1); + event->write(110); + event->init(); + allData.push_back(event); + valueProducer.onDataPulled(allData, /** succeed */ true); + + // This will fail and should invalidate the whole bucket since we do not have all the data + // needed to compute the metric value when the screen was on. + valueProducer.onConditionChanged(false, bucketStartTimeNs + 2); + valueProducer.onConditionChanged(true, bucketStartTimeNs + 3); + + // Bucket end. + allData.clear(); + shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1); + event2->write(1); + event2->write(140); + event2->init(); + allData.push_back(event2); + valueProducer.onDataPulled(allData, /** succeed */ false); + + valueProducer.flushIfNeededLocked(bucket2StartTimeNs + 1); + + EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + // Last pull failed so based has been reset. + EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size()); + ValueMetricProducer::Interval& curInterval = + valueProducer.mCurrentSlicedBucket.begin()->second[0]; + EXPECT_EQ(false, curInterval.hasBase); + EXPECT_EQ(false, curInterval.hasValue); + EXPECT_EQ(false, valueProducer.mHasGlobalBase); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt index 11bb38beee87..aad412f15fe2 100644 --- a/config/hiddenapi-greylist.txt +++ b/config/hiddenapi-greylist.txt @@ -328,17 +328,12 @@ Landroid/content/om/IOverlayManager;->getAllOverlays(I)Ljava/util/Map; Landroid/content/om/IOverlayManager;->getOverlayInfo(Ljava/lang/String;I)Landroid/content/om/OverlayInfo; Landroid/content/pm/IPackageDataObserver$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IPackageDataObserver$Stub$Proxy;->mRemote:Landroid/os/IBinder; -Landroid/content/pm/IPackageDataObserver$Stub$Proxy;->onRemoveCompleted(Ljava/lang/String;Z)V Landroid/content/pm/IPackageDataObserver$Stub;-><init>()V Landroid/content/pm/IPackageDataObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageDataObserver; -Landroid/content/pm/IPackageDataObserver$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/content/pm/IPackageDataObserver$Stub;->TRANSACTION_onRemoveCompleted:I Landroid/content/pm/IPackageDataObserver;->onRemoveCompleted(Ljava/lang/String;Z)V Landroid/content/pm/IPackageDeleteObserver$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IPackageDeleteObserver$Stub;-><init>()V Landroid/content/pm/IPackageDeleteObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageDeleteObserver; -Landroid/content/pm/IPackageDeleteObserver$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/content/pm/IPackageDeleteObserver$Stub;->TRANSACTION_packageDeleted:I Landroid/content/pm/IPackageDeleteObserver2$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IPackageDeleteObserver2$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/content/pm/IPackageDeleteObserver2$Stub;-><init>()V @@ -437,8 +432,6 @@ Landroid/content/pm/IPackageStatsObserver$Stub$Proxy;-><init>(Landroid/os/IBinde Landroid/content/pm/IPackageStatsObserver$Stub$Proxy;->mRemote:Landroid/os/IBinder; Landroid/content/pm/IPackageStatsObserver$Stub;-><init>()V Landroid/content/pm/IPackageStatsObserver$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IPackageStatsObserver; -Landroid/content/pm/IPackageStatsObserver$Stub;->DESCRIPTOR:Ljava/lang/String; -Landroid/content/pm/IPackageStatsObserver$Stub;->TRANSACTION_onGetStatsCompleted:I Landroid/content/pm/IPackageStatsObserver;->onGetStatsCompleted(Landroid/content/pm/PackageStats;Z)V Landroid/content/pm/IShortcutService$Stub$Proxy;-><init>(Landroid/os/IBinder;)V Landroid/content/pm/IShortcutService$Stub;->asInterface(Landroid/os/IBinder;)Landroid/content/pm/IShortcutService; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index ea145f0b9d21..64b94a946489 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -189,9 +189,19 @@ public class AppOpsManager { /** * Special mode that means "allow only when app is in foreground." This is <b>not</b> - * returned from {@link #checkOp}, {@link #noteOp}, {@link #startOp}; rather, when this - * mode is set, these functions will return {@link #MODE_ALLOWED} when the app being - * checked is currently in the foreground, otherwise {@link #MODE_IGNORED}. + * returned from {@link #unsafeCheckOp}, {@link #noteOp}, {@link #startOp}. Rather, + * {@link #unsafeCheckOp} will always return {@link #MODE_ALLOWED} (because it is always + * possible for it to be ultimately allowed, depending on the app's background state), + * and {@link #noteOp} and {@link #startOp} will return {@link #MODE_ALLOWED} when the app + * being checked is currently in the foreground, otherwise {@link #MODE_IGNORED}. + * + * <p>The only place you will this normally see this value is through + * {@link #unsafeCheckOpRaw}, which returns the actual raw mode of the op. Note that because + * you can't know the current state of the app being checked (and it can change at any + * point), you can only treat the result here as an indication that it will vary between + * {@link #MODE_ALLOWED} and {@link #MODE_IGNORED} depending on changes in the background + * state of the app. You thus must always use {@link #noteOp} or {@link #startOp} to do + * the actual check for access to the op.</p> */ public static final int MODE_FOREGROUND = 4; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 428c9b01d101..3ff6973a09ff 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -6408,11 +6408,9 @@ public class DevicePolicyManager { * @param admin Which {@link DeviceAdminReceiver} this request is associated with. * @param packageName The name of the package to set as the default SMS application. * @throws SecurityException if {@code admin} is not a device owner. - * - * @hide */ - @UnsupportedAppUsage - public void setDefaultSmsApplication(@NonNull ComponentName admin, String packageName) { + public void setDefaultSmsApplication(@NonNull ComponentName admin, + @NonNull String packageName) { throwIfParentInstance("setDefaultSmsApplication"); if (mService != null) { try { diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index a6f6d06ae71a..868fbfeeee39 100644 --- a/core/java/android/app/backup/BackupManager.java +++ b/core/java/android/app/backup/BackupManager.java @@ -751,6 +751,47 @@ public class BackupManager { } /** + * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the + * serial number of the its ancestral work profile or {@code null} if there is none. + * + * <p> The ancestral serial number will have a corresponding {@link UserHandle} if the device + * has a work profile that was restored from another work profile with serial number + * {@code ancestralSerialNumber}. + * + * @see UserManager#getSerialNumberForUser(UserHandle) + */ + @Nullable + public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) { + if (sService != null) { + try { + return sService.getUserForAncestralSerialNumber(ancestralSerialNumber); + } catch (RemoteException e) { + Log.e(TAG, "getUserForAncestralSerialNumber() couldn't connect"); + } + } + return null; + } + + /** + * Sets the ancestral work profile for the calling user. + * + * <p> The ancestral work profile corresponds to the profile that was used to restore to the + * callers profile. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BACKUP) + public void setAncestralSerialNumber(long ancestralSerialNumber) { + if (sService != null) { + try { + sService.setAncestralSerialNumber(ancestralSerialNumber); + } catch (RemoteException e) { + Log.e(TAG, "setAncestralSerialNumber() couldn't connect"); + } + } + } + + /** * Returns an {@link Intent} for the specified transport's configuration UI. * This value is set by {@link #updateTransportAttributes(ComponentName, String, Intent, String, * Intent, String)}. diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index eda8981d7e0e..8386c72e3406 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -22,6 +22,7 @@ import android.app.backup.IFullBackupRestoreObserver; import android.app.backup.IRestoreSession; import android.app.backup.ISelectBackupTransportCallback; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.content.Intent; import android.content.ComponentName; @@ -685,4 +686,24 @@ interface IBackupManager { * {@link android.app.backup.IBackupManager.cancelBackups} for the calling user id. */ void cancelBackups(); + + /** + * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the serial + * number of the it's ancestral work profile. + * + * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)} + * and it corresponds to the profile that was used to restore to the callers profile. + */ + UserHandle getUserForAncestralSerialNumber(in long ancestralSerialNumber); + + /** + * Sets the ancestral work profile for the calling user. + * + * <p> The ancestral work profile corresponds to the profile that was used to restore to the + * callers profile. + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + */ + void setAncestralSerialNumber(in long ancestralSerialNumber); + } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 47a4a2dca73d..f1bfe8671eec 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -2748,6 +2748,19 @@ public abstract class ContentResolver implements ContentInterface { } /** + * @see #setIsSyncable(Account, String, int) + * @hide + */ + public static void setIsSyncableAsUser(Account account, String authority, int syncable, + int userId) { + try { + getContentService().setIsSyncableAsUser(account, authority, syncable, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Gets the master auto-sync setting that applies to all the providers and accounts. * If this is false then the per-provider auto-sync setting is ignored. * <p>This method requires the caller to hold the permission diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl index 1d0237502812..9f6e236306e0 100644 --- a/core/java/android/content/IContentService.aidl +++ b/core/java/android/content/IContentService.aidl @@ -126,6 +126,7 @@ interface IContentService { * @param syncable, >0 denotes syncable, 0 means not syncable, <0 means unknown */ void setIsSyncable(in Account account, String providerName, int syncable); + void setIsSyncableAsUser(in Account account, String providerName, int syncable, int userId); void setMasterSyncAutomatically(boolean flag); void setMasterSyncAutomaticallyAsUser(boolean flag, int userId); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 60449145a740..81ed110d15ec 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -5260,6 +5260,10 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_grantUriPermissions, false); + p.info.forceUriPermissions = sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestProvider_forceUriPermissions, + false); + p.info.multiprocess = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestProvider_multiprocess, false); diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index 379b7833150c..f06a628f1913 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -47,7 +47,13 @@ public final class ProviderInfo extends ComponentInfo * grantUriPermissions} attribute. */ public boolean grantUriPermissions = false; - + + /** If true, always apply URI permission grants, as per the + * {@link android.R.styleable#AndroidManifestProvider_forceUriPermissions + * forceUriPermissions} attribute. + */ + public boolean forceUriPermissions = false; + /** * If non-null, these are the patterns that are allowed for granting URI * permissions. Any URI that does not match one of these patterns will not @@ -112,6 +118,7 @@ public final class ProviderInfo extends ComponentInfo readPermission = orig.readPermission; writePermission = orig.writePermission; grantUriPermissions = orig.grantUriPermissions; + forceUriPermissions = orig.forceUriPermissions; uriPermissionPatterns = orig.uriPermissionPatterns; pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; @@ -142,6 +149,7 @@ public final class ProviderInfo extends ComponentInfo out.writeString(readPermission); out.writeString(writePermission); out.writeInt(grantUriPermissions ? 1 : 0); + out.writeInt(forceUriPermissions ? 1 : 0); out.writeTypedArray(uriPermissionPatterns, parcelableFlags); out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); @@ -171,6 +179,7 @@ public final class ProviderInfo extends ComponentInfo readPermission = in.readString(); writePermission = in.readString(); grantUriPermissions = in.readInt() != 0; + forceUriPermissions = in.readInt() != 0; uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java index 0ec40183c319..1d0ab5ad2679 100644 --- a/core/java/android/content/rollback/PackageRollbackInfo.java +++ b/core/java/android/content/rollback/PackageRollbackInfo.java @@ -22,6 +22,7 @@ import android.content.pm.VersionedPackage; import android.os.Parcel; import android.os.Parcelable; import android.util.IntArray; +import android.util.SparseLongArray; import java.util.ArrayList; @@ -73,6 +74,18 @@ public final class PackageRollbackInfo implements Parcelable { */ private final boolean mIsApex; + /* + * The list of users the package is installed for. + */ + // NOTE: Not a part of the Parcelable representation of this object. + private final IntArray mInstalledUsers; + + /** + * A mapping between user and an inode of theirs CE data snapshot. + */ + // NOTE: Not a part of the Parcelable representation of this object. + private final SparseLongArray mCeSnapshotInodes; + /** * Returns the name of the package to roll back from. */ @@ -126,15 +139,33 @@ public final class PackageRollbackInfo implements Parcelable { } /** @hide */ + public IntArray getInstalledUsers() { + return mInstalledUsers; + } + + /** @hide */ + public SparseLongArray getCeSnapshotInodes() { + return mCeSnapshotInodes; + } + + /** @hide */ + public void putCeSnapshotInode(int userId, long ceSnapshotInode) { + mCeSnapshotInodes.put(userId, ceSnapshotInode); + } + + /** @hide */ public PackageRollbackInfo(VersionedPackage packageRolledBackFrom, VersionedPackage packageRolledBackTo, @NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores, - boolean isApex) { + boolean isApex, @NonNull IntArray installedUsers, + @NonNull SparseLongArray ceSnapshotInodes) { this.mVersionRolledBackFrom = packageRolledBackFrom; this.mVersionRolledBackTo = packageRolledBackTo; this.mPendingBackups = pendingBackups; this.mPendingRestores = pendingRestores; this.mIsApex = isApex; + this.mInstalledUsers = installedUsers; + this.mCeSnapshotInodes = ceSnapshotInodes; } private PackageRollbackInfo(Parcel in) { @@ -143,6 +174,8 @@ public final class PackageRollbackInfo implements Parcelable { this.mIsApex = in.readBoolean(); this.mPendingRestores = null; this.mPendingBackups = null; + this.mInstalledUsers = null; + this.mCeSnapshotInodes = null; } @Override diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 866ea16aa59d..8a141e2bfe6f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -68,6 +68,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -1889,7 +1890,8 @@ public class ConnectivityManager { * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive * changes. Must be extended by applications that use this API. * - * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object. + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. **/ public SocketKeepalive createSocketKeepalive(@NonNull Network network, @NonNull UdpEncapsulationSocket socket, @@ -1918,6 +1920,8 @@ public class ConnectivityManager { * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive * changes. Must be extended by applications that use this API. * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. * @hide */ @SystemApi @@ -1933,6 +1937,34 @@ public class ConnectivityManager { } /** + * Request that keepalives be started on a TCP socket. + * The socket must be established. + * + * @param network The {@link Network} the socket is on. + * @param socket The socket that needs to be kept alive. + * @param executor The executor on which callback will be invoked. This implementation assumes + * the provided {@link Executor} runs the callbacks in sequence with no + * concurrency. Failing this, no guarantee of correctness can be made. It is + * the responsibility of the caller to ensure the executor provides this + * guarantee. A simple way of creating such an executor is with the standard + * tool {@code Executors.newSingleThreadExecutor}. + * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive + * changes. Must be extended by applications that use this API. + * + * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the + * given socket. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) + public SocketKeepalive createSocketKeepalive(@NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + return new TcpSocketKeepalive(mService, network, socket, executor, callback); + } + + /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. An attempt to add a route that * already exists is ignored, but treated as successful. diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 1148ac1bc707..3a405d35e9aa 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -188,6 +188,9 @@ interface IConnectivityManager int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr, String dstAddr); + void startTcpKeepalive(in Network network, in FileDescriptor fd, int intervalSeconds, + in Messenger messenger, in IBinder binder); + void stopKeepalive(in Network network, int slot); String getCaptivePortalServerUrl(); diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java index c37837837a9e..273f8cd4f21d 100644 --- a/core/java/android/net/NetworkAgent.java +++ b/core/java/android/net/NetworkAgent.java @@ -178,6 +178,26 @@ public abstract class NetworkAgent extends Handler { */ public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13; + // TODO: move the above 2 constants down so they are in order once merge conflicts are resolved + /** + * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter. + * + * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the + * remote site will send ACK packets in response to the keepalive packets, the firmware also + * needs to be configured to properly filter the ACKs to prevent the system from waking up. + * This does not happen with UDP, so this message is TCP-specific. + * arg1 = slot number of the keepalive to filter for. + * obj = the keepalive packet to send repeatedly. + */ + public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16; + + /** + * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See + * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}. + * arg1 = slot number of the keepalive packet filter to remove. + */ + public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17; + /** * Sent by ConnectivityService to inform this network transport of signal strength thresholds * that when crossed should trigger a system wakeup and a NetworkCapabilities update. @@ -329,6 +349,14 @@ public abstract class NetworkAgent extends Handler { preventAutomaticReconnect(); break; } + case CMD_ADD_KEEPALIVE_PACKET_FILTER: { + addKeepalivePacketFilter(msg); + break; + } + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: { + removeKeepalivePacketFilter(msg); + break; + } } } @@ -478,6 +506,24 @@ public abstract class NetworkAgent extends Handler { } /** + * Called by ConnectivityService to add specific packet filter to network hardware to block + * ACKs matching the sent keepalive packets. Implementations that support this feature must + * override this method. + */ + protected void addKeepalivePacketFilter(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + } + + /** + * Called by ConnectivityService to remove a packet filter installed with + * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature + * must override this method. + */ + protected void removeKeepalivePacketFilter(Message msg) { + onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + } + + /** * Called by ConnectivityService to inform this network transport of signal strength thresholds * that when crossed should trigger a system wakeup and a NetworkCapabilities update. */ diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java index 7ea1bef83669..07728beb9c64 100644 --- a/core/java/android/net/SocketKeepalive.java +++ b/core/java/android/net/SocketKeepalive.java @@ -19,6 +19,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; @@ -155,7 +156,7 @@ public abstract class SocketKeepalive implements AutoCloseable { @NonNull private final SocketKeepalive.Callback mCallback; @NonNull private final Looper mLooper; @NonNull final Messenger mMessenger; - @NonNull Integer mSlot; + @Nullable Integer mSlot; SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network, @NonNull Executor executor, @NonNull Callback callback) { diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java new file mode 100644 index 000000000000..8f6ee7bf2950 --- /dev/null +++ b/core/java/android/net/TcpSocketKeepalive.java @@ -0,0 +1,78 @@ +/* + * 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.net; + +import android.annotation.NonNull; +import android.os.Binder; +import android.os.RemoteException; +import android.util.Log; + +import java.io.FileDescriptor; +import java.net.Socket; +import java.util.concurrent.Executor; + +/** @hide */ +final class TcpSocketKeepalive extends SocketKeepalive { + + private final Socket mSocket; + + TcpSocketKeepalive(@NonNull IConnectivityManager service, + @NonNull Network network, + @NonNull Socket socket, + @NonNull Executor executor, + @NonNull Callback callback) { + super(service, network, executor, callback); + mSocket = socket; + } + + /** + * Starts keepalives. {@code mSocket} must be a connected TCP socket. + * + * - The application must not write to or read from the socket after calling this method, until + * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail + * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket + * experienced an error (as in poll(2) returned POLLERR); if this happens, the data received + * from the socket may be invalid, and the socket can't be recovered. + * - If the socket has data in the send or receive buffer, then this call will fail with + * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed. + * An app could ensure this by using an application-layer protocol where it can receive + * acknowledgement that it will go into keepalive mode. It could then go into keepalive + * mode after having read the acknowledgement, draining the socket. + */ + @Override + void startImpl(int intervalSec) { + try { + final FileDescriptor fd = mSocket.getFileDescriptor$(); + mService.startTcpKeepalive(mNetwork, fd, intervalSec, mMessenger, new Binder()); + } catch (RemoteException e) { + Log.e(TAG, "Error starting packet keepalive: ", e); + stopLooper(); + } + } + + @Override + void stopImpl() { + try { + if (mSlot != null) { + mService.stopKeepalive(mNetwork, mSlot); + } + } catch (RemoteException e) { + Log.e(TAG, "Error stopping packet keepalive: ", e); + stopLooper(); + } + } +} diff --git a/core/java/android/net/ip/IIpClient.aidl b/core/java/android/net/ip/IIpClient.aidl index 7769ec2b65ac..a4a80e1efe6f 100644 --- a/core/java/android/net/ip/IIpClient.aidl +++ b/core/java/android/net/ip/IIpClient.aidl @@ -17,6 +17,7 @@ package android.net.ip; import android.net.ProxyInfoParcelable; import android.net.ProvisioningConfigurationParcelable; +import android.net.TcpKeepalivePacketDataParcelable; /** @hide */ oneway interface IIpClient { @@ -29,4 +30,6 @@ oneway interface IIpClient { void setTcpBufferSizes(in String tcpBufferSizes); void setHttpProxy(in ProxyInfoParcelable proxyInfo); void setMulticastFilter(boolean enabled); + void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt); + void removeKeepalivePacketFilter(int slot); } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 2d61a4ee95d9..83a7654d494b 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1120,11 +1120,9 @@ public class Build { /** The name identifying the system partition. */ public static final String PARTITION_NAME_SYSTEM = "system"; - private String mName; - private String mFingerprint; - private long mTimeMs; - - public Partition() {} + private final String mName; + private final String mFingerprint; + private final long mTimeMs; private Partition(String name, String fingerprint, long timeMs) { mName = name; diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 0a60764428dc..e2b5730e10f4 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -734,10 +734,13 @@ public class UserManager { public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs"; /** - * Specifies if what is copied in the clipboard of this profile can - * be pasted in related profiles. Does not restrict if the clipboard of related profiles can be - * pasted in this profile. - * The default value is <code>false</code>. + * Specifies if the clipboard contents can be exported by pasting the data into other users or + * profiles. This restriction doesn't prevent import, such as someone pasting clipboard data + * from other profiles or users. The default value is {@code false}. + * + * <p><strong>Note</strong>: Because it's possible to extract data from screenshots using + * optical character recognition (OCR), we strongly recommend combining this user restriction + * with {@link DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}. * * <p>Key for user restrictions. * <p>Type: Boolean diff --git a/core/java/android/app/SmsAppService.java b/core/java/android/service/carrier/CarrierMessagingClientService.java index 3829d7103b07..13f4fc4b0dcb 100644 --- a/core/java/android/app/SmsAppService.java +++ b/core/java/android/service/carrier/CarrierMessagingClientService.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.app; +package android.service.carrier; +import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; @@ -24,10 +25,11 @@ import android.os.IBinder; * it so that the process is always running, which allows the app to have a persistent connection * to the server. * - * <p>The service must have an {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} + * <p>The service must have an + * {@link android.telephony.TelephonyManager#ACTION_CARRIER_MESSAGING_CLIENT_SERVICE} * action in the intent handler, and be protected with - * {@link android.Manifest.permission#BIND_SMS_APP_SERVICE}. However the service does not have to - * be exported. + * {@link android.Manifest.permission#BIND_CARRIER_MESSAGING_CLIENT_SERVICE}. + * However the service does not have to be exported. * * <p>The service must be associated with a non-main process, meaning it must have an * {@code android:process} tag in its manifest entry. @@ -45,27 +47,27 @@ import android.os.IBinder; * * <p>Example: First, define a subclass in the application: * <pre> - * public class MySmsAppService extends SmsAppService { + * public class MyCarrierMessagingClientService extends CarrierMessagingClientService { * } * </pre> * Then, declare it in its {@code AndroidManifest.xml}: * <pre> * <service - * android:name=".MySmsAppService" + * android:name=".MyCarrierMessagingClientService" * android:exported="false" * android:process=":persistent" - * android:permission="android.permission.BIND_SMS_APP_SERVICE"> + * android:permission="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"> * <intent-filter> - * <action android:name="android.telephony.action.SMS_APP_SERVICE" /> + * <action android:name="android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE" /> * </intent-filter> * </service> * </pre> */ -public class SmsAppService extends Service { - private final ISmsAppService mImpl; +public class CarrierMessagingClientService extends Service { + private final ICarrierMessagingClientServiceImpl mImpl; - public SmsAppService() { - mImpl = new ISmsAppServiceImpl(); + public CarrierMessagingClientService() { + mImpl = new ICarrierMessagingClientServiceImpl(); } @Override @@ -73,6 +75,6 @@ public class SmsAppService extends Service { return mImpl.asBinder(); } - private class ISmsAppServiceImpl extends ISmsAppService.Stub { + private class ICarrierMessagingClientServiceImpl extends ICarrierMessagingClientService.Stub { } } diff --git a/core/java/android/app/ISmsAppService.aidl b/core/java/android/service/carrier/ICarrierMessagingClientService.aidl index 1ac2ec6b1c41..dbe7d121d56e 100644 --- a/core/java/android/app/ISmsAppService.aidl +++ b/core/java/android/service/carrier/ICarrierMessagingClientService.aidl @@ -14,10 +14,10 @@ * limitations under the License. */ -package android.app; +package android.service.carrier; /** * @hide */ -interface ISmsAppService { +interface ICarrierMessagingClientService { } diff --git a/core/java/android/view/DisplayListCanvas.java b/core/java/android/view/DisplayListCanvas.java index 03d99553f125..3e749f4ba16f 100644 --- a/core/java/android/view/DisplayListCanvas.java +++ b/core/java/android/view/DisplayListCanvas.java @@ -37,7 +37,7 @@ public abstract class DisplayListCanvas extends BaseRecordingCanvas { } /** @hide */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.O) public abstract void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top, CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx, CanvasProperty<Float> ry, CanvasProperty<Paint> paint); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 90110e60f0fd..04ffa33d0e22 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4258,47 +4258,51 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * The offset, in pixels, by which the content of this view is scrolled * horizontally. + * Please use {@link View#getScrollX()} and {@link View#setScrollX(int)} instead of + * accessing these directly. * {@hide} */ @ViewDebug.ExportedProperty(category = "scrolling") - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected int mScrollX; /** * The offset, in pixels, by which the content of this view is scrolled * vertically. + * Please use {@link View#getScrollY()} and {@link View#setScrollY(int)} instead of + * accessing these directly. * {@hide} */ @ViewDebug.ExportedProperty(category = "scrolling") - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) protected int mScrollY; /** - * The left padding in pixels, that is the distance in pixels between the - * left edge of this view and the left edge of its content. + * The final computed left padding in pixels that is used for drawing. This is the distance in + * pixels between the left edge of this view and the left edge of its content. * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") @UnsupportedAppUsage protected int mPaddingLeft = 0; /** - * The right padding in pixels, that is the distance in pixels between the - * right edge of this view and the right edge of its content. + * The final computed right padding in pixels that is used for drawing. This is the distance in + * pixels between the right edge of this view and the right edge of its content. * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") @UnsupportedAppUsage protected int mPaddingRight = 0; /** - * The top padding in pixels, that is the distance in pixels between the - * top edge of this view and the top edge of its content. + * The final computed top padding in pixels that is used for drawing. This is the distance in + * pixels between the top edge of this view and the top edge of its content. * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") @UnsupportedAppUsage protected int mPaddingTop; /** - * The bottom padding in pixels, that is the distance in pixels between the - * bottom edge of this view and the bottom edge of its content. + * The final computed bottom padding in pixels that is used for drawing. This is the distance in + * pixels between the bottom edge of this view and the bottom edge of its content. * {@hide} */ @ViewDebug.ExportedProperty(category = "padding") @@ -4350,7 +4354,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private MatchIdPredicate mMatchIdPredicate; /** - * Cache the paddingRight set by the user to append to the scrollbar's size. + * The right padding after RTL resolution, but before taking account of scroll bars. * * @hide */ @@ -4358,7 +4362,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected int mUserPaddingRight; /** - * Cache the paddingBottom set by the user to append to the scrollbar's size. + * The resolved bottom padding before taking account of scroll bars. * * @hide */ @@ -4366,7 +4370,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected int mUserPaddingBottom; /** - * Cache the paddingLeft set by the user to append to the scrollbar's size. + * The left padding after RTL resolution, but before taking account of scroll bars. * * @hide */ @@ -4388,14 +4392,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, int mUserPaddingEnd; /** - * Cache initial left padding. + * The left padding as set by a setter method, a background's padding, or via XML property + * resolution. This value is the padding before LTR resolution or taking account of scrollbars. * * @hide */ int mUserPaddingLeftInitial; /** - * Cache initial right padding. + * The right padding as set by a setter method, a background's padding, or via XML property + * resolution. This value is the padding before LTR resolution or taking account of scrollbars. * * @hide */ @@ -4407,12 +4413,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private static final int UNDEFINED_PADDING = Integer.MIN_VALUE; /** - * Cache if a left padding has been defined + * Cache if a left padding has been defined explicitly via padding, horizontal padding, + * or leftPadding in XML, or by setPadding(...) or setRelativePadding(...) */ private boolean mLeftPaddingDefined = false; /** - * Cache if a right padding has been defined + * Cache if a right padding has been defined explicitly via padding, horizontal padding, + * or rightPadding in XML, or by setPadding(...) or setRelativePadding(...) */ private boolean mRightPaddingDefined = false; @@ -5321,7 +5329,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case com.android.internal.R.styleable.View_paddingVertical: paddingVertical = a.getDimensionPixelSize(attr, -1); break; - case com.android.internal.R.styleable.View_paddingLeft: + case com.android.internal.R.styleable.View_paddingLeft: leftPadding = a.getDimensionPixelSize(attr, -1); mUserPaddingLeftInitial = leftPadding; leftPaddingDefined = true; @@ -5787,7 +5795,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setOverScrollMode(overScrollMode); - // Cache start/end user padding as we cannot fully resolve padding here (we dont have yet + // Cache start/end user padding as we cannot fully resolve padding here (we don't have yet // the resolved layout direction). Those cached values will be used later during padding // resolution. mUserPaddingStart = startPadding; @@ -5802,6 +5810,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, mLeftPaddingDefined = leftPaddingDefined; mRightPaddingDefined = rightPaddingDefined; + // Valid paddingHorizontal/paddingVertical beats leftPadding, rightPadding, topPadding, + // bottomPadding, and padding set by background. Valid padding beats everything. if (padding >= 0) { leftPadding = padding; topPadding = padding; @@ -5854,6 +5864,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + // mPaddingTop and mPaddingBottom may have been set by setBackground(Drawable) so must pass + // them on if topPadding or bottomPadding are not valid. internalSetPadding( mUserPaddingLeftInitial, topPadding >= 0 ? topPadding : mPaddingTop, @@ -16183,7 +16195,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return true if the View subclass handles alpha (the return value for onSetAlpha()) and * the new value for the alpha property is different from the old value */ - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768435) boolean setAlphaNoInvalidation(float alpha) { ensureTransformationInfo(); if (mTransformationInfo.mAlpha != alpha) { diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index a0ab362f3985..68c0d9ec465b 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -1139,12 +1139,6 @@ public class ViewPropertyAnimator { boolean hardwareAccelerated = mView.isHardwareAccelerated(); - // alpha requires slightly different treatment than the other (transform) properties. - // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation - // logic is dependent on how the view handles an internal call to onSetAlpha(). - // We track what kinds of properties are set, and how alpha is handled when it is - // set, and perform the invalidation steps appropriately. - boolean alphaHandled = false; if (!hardwareAccelerated) { mView.invalidateParentCaches(); } @@ -1159,11 +1153,7 @@ public class ViewPropertyAnimator { for (int i = 0; i < count; ++i) { NameValuesHolder values = valueList.get(i); float value = values.mFromValue + fraction * values.mDeltaValue; - if (values.mNameConstant == ALPHA) { - alphaHandled = mView.setAlphaNoInvalidation(value); - } else { - setValue(values.mNameConstant, value); - } + setValue(values.mNameConstant, value); } } if ((propertyMask & TRANSFORM_MASK) != 0) { @@ -1171,13 +1161,8 @@ public class ViewPropertyAnimator { mView.mPrivateFlags |= View.PFLAG_DRAWN; // force another invalidation } } - // invalidate(false) in all cases except if alphaHandled gets set to true - // via the call to setAlphaNoInvalidation(), above - if (alphaHandled) { - mView.invalidate(true); - } else { - mView.invalidateViewProperty(false, false); - } + + mView.invalidateViewProperty(false, false); if (mUpdateListener != null) { mUpdateListener.onAnimationUpdate(animation); } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index b60026810efb..6b48c6584ad2 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -20,6 +20,7 @@ import android.animation.ObjectAnimator; import android.annotation.InterpolatorRes; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; import android.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.res.ColorStateList; @@ -170,12 +171,24 @@ public class ProgressBar extends View { /** Duration of smooth progress animations. */ private static final int PROGRESS_ANIM_DURATION = 80; - @UnsupportedAppUsage + /** + * Outside the framework, please use {@link ProgressBar#getMinWidth()} and + * {@link ProgressBar#setMinWidth(int)} instead of accessing these directly. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMinWidth; int mMaxWidth; - @UnsupportedAppUsage + /** + * Outside the framework, please use {@link ProgressBar#getMinHeight()} and + * {@link ProgressBar#setMinHeight(int)} instead of accessing these directly. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMinHeight; - @UnsupportedAppUsage + /** + * Outside the framework, please use {@link ProgressBar#getMaxHeight()} ()} and + * {@link ProgressBar#setMaxHeight(int)} (int)} instead of accessing these directly. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) int mMaxHeight; private int mProgress; @@ -393,6 +406,74 @@ public class ProgressBar extends View { } /** + * Sets the minimum width the progress bar can have. + * @param minWidth the minimum width to be set, in pixels + * @attr ref android.R.styleable#ProgressBar_minWidth + */ + public void setMinWidth(@Px int minWidth) { + mMinWidth = minWidth; + requestLayout(); + } + + /** + * @return the minimum width the progress bar can have, in pixels + */ + @Px public int getMinWidth() { + return mMinWidth; + } + + /** + * Sets the maximum width the progress bar can have. + * @param maxWidth the maximum width to be set, in pixels + * @attr ref android.R.styleable#ProgressBar_maxWidth + */ + public void setMaxWidth(@Px int maxWidth) { + mMaxWidth = maxWidth; + requestLayout(); + } + + /** + * @return the maximum width the progress bar can have, in pixels + */ + @Px public int getMaxWidth() { + return mMaxWidth; + } + + /** + * Sets the minimum height the progress bar can have. + * @param minHeight the minimum height to be set, in pixels + * @attr ref android.R.styleable#ProgressBar_minHeight + */ + public void setMinHeight(@Px int minHeight) { + mMinHeight = minHeight; + requestLayout(); + } + + /** + * @return the minimum height the progress bar can have, in pixels + */ + @Px public int getMinHeight() { + return mMinHeight; + } + + /** + * Sets the maximum height the progress bar can have. + * @param maxHeight the maximum height to be set, in pixels + * @attr ref android.R.styleable#ProgressBar_maxHeight + */ + public void setMaxHeight(@Px int maxHeight) { + mMaxHeight = maxHeight; + requestLayout(); + } + + /** + * @return the maximum height the progress bar can have, in pixels + */ + @Px public int getMaxHeight() { + return mMaxHeight; + } + + /** * Returns {@code true} if the target drawable needs to be tileified. * * @param dr the drawable to check diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8626c6856e7c..659b71f51588 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -435,8 +435,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private ColorStateList mHintTextColor; private ColorStateList mLinkTextColor; @ViewDebug.ExportedProperty(category = "text") - @UnsupportedAppUsage + + /** + * {@link #setTextColor(int)} or {@link #getCurrentTextColor()} should be used instead. + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private int mCurTextColor; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mCurHintTextColor; private boolean mFreezesText; diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index ee96ae985331..119a015cd5ea 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -54,6 +54,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -186,9 +187,12 @@ public class ChooserActivity extends ResolverActivity { private @interface ContentPreviewType { } - private static final int CONTENT_PREVIEW_IMAGE = 0; - private static final int CONTENT_PREVIEW_FILE = 1; - private static final int CONTENT_PREVIEW_TEXT = 2; + // Starting at 1 since 0 is considered "undefined" for some of the database transformations + // of tron logs. + private static final int CONTENT_PREVIEW_IMAGE = 1; + private static final int CONTENT_PREVIEW_FILE = 2; + private static final int CONTENT_PREVIEW_TEXT = 3; + protected MetricsLogger mMetricsLogger; private final Handler mChooserHandler = new Handler() { @Override @@ -413,11 +417,12 @@ public class ChooserActivity extends ResolverActivity { } }); - MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); - mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; - MetricsLogger.histogram(null, "system_cost_for_smart_sharing", (int) systemCost); + + getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN) + .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType()) + .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost)); if (USE_PREDICTION_MANAGER_FOR_DIRECT_TARGETS) { final IntentFilter filter = getTargetIntentFilter(); @@ -470,6 +475,9 @@ public class ChooserActivity extends ResolverActivity { } int previewType = findPreferredContentPreview(targetIntent, getContentResolver()); + + getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_SHARE_WITH_PREVIEW) + .setSubtype(previewType)); displayContentPreview(previewType, targetIntent); } @@ -1180,6 +1188,13 @@ public class ChooserActivity extends ResolverActivity { } } + protected MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } + public class ChooserListController extends ResolverListController { public ChooserListController(Context context, PackageManager pm, @@ -1726,6 +1741,8 @@ public class ChooserActivity extends ResolverActivity { if (show != mShowServiceTargets) { mShowServiceTargets = show; notifyDataSetChanged(); + getMetricsLogger().write( + new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN_DIRECT_TARGET)); } } @@ -1884,8 +1901,6 @@ public class ChooserActivity extends ResolverActivity { } if (startType == ChooserListAdapter.TARGET_SERVICE) { - holder.row.setBackgroundColor( - getColor(R.color.chooser_service_row_background_color)); int nextStartType = mChooserListAdapter.getPositionTargetType( getFirstRowPosition(rowPosition + 1)); int serviceSpacing = holder.row.getContext().getResources() diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index 397df56a809c..b04ebec77a92 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -691,6 +691,15 @@ public class ArrayUtils { return result; } + public static boolean startsWith(byte[] cur, byte[] val) { + if (cur == null || val == null) return false; + if (cur.length < val.length) return false; + for (int i = 0; i < val.length; i++) { + if (cur[i] != val[i]) return false; + } + return true; + } + /** * Returns the first element from the array for which * condition {@code predicate} is true, or null if there is no such element diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java index b7e656bc9278..ee8637d8c773 100644 --- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java +++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java @@ -17,15 +17,12 @@ package com.android.internal.widget; -import com.android.internal.R; - import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; +import android.metrics.LogMaker; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -45,8 +42,13 @@ import android.view.animation.AnimationUtils; import android.widget.AbsListView; import android.widget.OverScroller; +import com.android.internal.R; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + public class ResolverDrawerLayout extends ViewGroup { private static final String TAG = "ResolverDrawerLayout"; + private MetricsLogger mMetricsLogger; /** * Max width of the whole drawer layout @@ -496,6 +498,9 @@ public class ResolverDrawerLayout extends ViewGroup { final boolean isCollapsedNew = newPos != 0; if (isCollapsedOld != isCollapsedNew) { onCollapsedChanged(isCollapsedNew); + getMetricsLogger().write( + new LogMaker(MetricsEvent.ACTION_SHARESHEET_COLLAPSED_CHANGED) + .setSubtype(isCollapsedNew ? 1 : 0)); } postInvalidateOnAnimation(); return dy; @@ -1037,4 +1042,11 @@ public class ResolverDrawerLayout extends ViewGroup { dispatchOnDismissed(); } } + + private MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } } diff --git a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java index 1b4049212255..b4610bda2cd9 100644 --- a/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java +++ b/core/java/com/android/server/backup/AccountSyncSettingsBackupHelper.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.SyncAdapterType; import android.os.Environment; import android.os.ParcelFileDescriptor; +import android.os.UserHandle; import android.util.Log; import org.json.JSONArray; @@ -48,6 +49,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Helper for backing up account sync settings (whether or not a service should be synced). The @@ -76,15 +78,17 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { private static final String KEY_AUTHORITY_NAME = "name"; private static final String KEY_AUTHORITY_SYNC_STATE = "syncState"; private static final String KEY_AUTHORITY_SYNC_ENABLED = "syncEnabled"; - private static final String STASH_FILE = Environment.getDataDirectory() - + "/backup/unadded_account_syncsettings.json"; + private static final String STASH_FILE = "/backup/unadded_account_syncsettings.json"; private Context mContext; private AccountManager mAccountManager; + private final int mUserId; - public AccountSyncSettingsBackupHelper(Context context) { + public AccountSyncSettingsBackupHelper(Context context, int userId) { mContext = context; mAccountManager = AccountManager.get(mContext); + + mUserId = userId; } /** @@ -94,7 +98,7 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput output, ParcelFileDescriptor newState) { try { - JSONObject dataJSON = serializeAccountSyncSettingsToJSON(); + JSONObject dataJSON = serializeAccountSyncSettingsToJSON(mUserId); if (DEBUG) { Log.d(TAG, "Account sync settings JSON: " + dataJSON); @@ -123,10 +127,9 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { /** * Fetch and serialize Account and authority information as a JSON Array. */ - private JSONObject serializeAccountSyncSettingsToJSON() throws JSONException { - Account[] accounts = mAccountManager.getAccounts(); - SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser( - mContext.getUserId()); + private JSONObject serializeAccountSyncSettingsToJSON(int userId) throws JSONException { + Account[] accounts = mAccountManager.getAccountsAsUser(userId); + SyncAdapterType[] syncAdapters = ContentResolver.getSyncAdapterTypesAsUser(userId); // Create a map of Account types to authorities. Later this will make it easier for us to // generate our JSON. @@ -146,7 +149,8 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { // Generate JSON. JSONObject backupJSON = new JSONObject(); backupJSON.put(KEY_VERSION, JSON_FORMAT_VERSION); - backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomatically()); + backupJSON.put(KEY_MASTER_SYNC_ENABLED, ContentResolver.getMasterSyncAutomaticallyAsUser( + userId)); JSONArray accountJSONArray = new JSONArray(); for (Account account : accounts) { @@ -165,8 +169,9 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { // Add authorities for this Account type and check whether or not sync is enabled. JSONArray authoritiesJSONArray = new JSONArray(); for (String authority : authorities) { - int syncState = ContentResolver.getIsSyncable(account, authority); - boolean syncEnabled = ContentResolver.getSyncAutomatically(account, authority); + int syncState = ContentResolver.getIsSyncableAsUser(account, authority, userId); + boolean syncEnabled = ContentResolver.getSyncAutomaticallyAsUser(account, authority, + userId); JSONObject authorityJSON = new JSONObject(); authorityJSON.put(KEY_AUTHORITY_NAME, authority); @@ -254,17 +259,18 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { boolean masterSyncEnabled = dataJSON.getBoolean(KEY_MASTER_SYNC_ENABLED); JSONArray accountJSONArray = dataJSON.getJSONArray(KEY_ACCOUNTS); - boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomatically(); + boolean currentMasterSyncEnabled = ContentResolver.getMasterSyncAutomaticallyAsUser( + mUserId); if (currentMasterSyncEnabled) { // Disable master sync to prevent any syncs from running. - ContentResolver.setMasterSyncAutomatically(false); + ContentResolver.setMasterSyncAutomaticallyAsUser(false, mUserId); } try { - restoreFromJsonArray(accountJSONArray); + restoreFromJsonArray(accountJSONArray, mUserId); } finally { // Set the master sync preference to the value from the backup set. - ContentResolver.setMasterSyncAutomatically(masterSyncEnabled); + ContentResolver.setMasterSyncAutomaticallyAsUser(masterSyncEnabled, mUserId); } Log.i(TAG, "Restore successful."); } catch (IOException | JSONException e) { @@ -272,9 +278,9 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { } } - private void restoreFromJsonArray(JSONArray accountJSONArray) + private void restoreFromJsonArray(JSONArray accountJSONArray, int userId) throws JSONException { - HashSet<Account> currentAccounts = getAccounts(); + Set<Account> currentAccounts = getAccounts(userId); JSONArray unaddedAccountsJSONArray = new JSONArray(); for (int i = 0; i < accountJSONArray.length(); i++) { JSONObject accountJSON = (JSONObject) accountJSONArray.get(i); @@ -292,14 +298,14 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { // yet won't be restored. if (currentAccounts.contains(account)) { if (DEBUG) Log.i(TAG, "Restoring Sync Settings for" + accountName); - restoreExistingAccountSyncSettingsFromJSON(accountJSON); + restoreExistingAccountSyncSettingsFromJSON(accountJSON, userId); } else { unaddedAccountsJSONArray.put(accountJSON); } } if (unaddedAccountsJSONArray.length() > 0) { - try (FileOutputStream fOutput = new FileOutputStream(STASH_FILE)) { + try (FileOutputStream fOutput = new FileOutputStream(getStashFile(userId))) { String jsonString = unaddedAccountsJSONArray.toString(); DataOutputStream out = new DataOutputStream(fOutput); out.writeUTF(jsonString); @@ -308,18 +314,20 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { Log.e(TAG, "unable to write the sync settings to the stash file", ioe); } } else { - File stashFile = new File(STASH_FILE); - if (stashFile.exists()) stashFile.delete(); + File stashFile = getStashFile(userId); + if (stashFile.exists()) { + stashFile.delete(); + } } } /** * Restore SyncSettings for all existing accounts from a stashed backup-set */ - private void accountAddedInternal() { + private void accountAddedInternal(int userId) { String jsonString; - try (FileInputStream fIn = new FileInputStream(new File(STASH_FILE))) { + try (FileInputStream fIn = new FileInputStream(getStashFile(userId))) { DataInputStream in = new DataInputStream(fIn); jsonString = in.readUTF(); } catch (FileNotFoundException fnfe) { @@ -333,7 +341,7 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { try { JSONArray unaddedAccountsJSONArray = new JSONArray(jsonString); - restoreFromJsonArray(unaddedAccountsJSONArray); + restoreFromJsonArray(unaddedAccountsJSONArray, userId); } catch (JSONException jse) { // Malformed jsonString Log.e(TAG, "there was an error with the stashed sync settings", jse); @@ -343,9 +351,10 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { /** * Restore SyncSettings for all existing accounts from a stashed backup-set */ - public static void accountAdded(Context context) { - AccountSyncSettingsBackupHelper helper = new AccountSyncSettingsBackupHelper(context); - helper.accountAddedInternal(); + public static void accountAdded(Context context, int userId) { + AccountSyncSettingsBackupHelper helper = new AccountSyncSettingsBackupHelper(context, + userId); + helper.accountAddedInternal(userId); } /** @@ -353,9 +362,9 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { * * @return Accounts in a HashSet. */ - private HashSet<Account> getAccounts() { - Account[] accounts = mAccountManager.getAccounts(); - HashSet<Account> accountHashSet = new HashSet<Account>(); + private Set<Account> getAccounts(int userId) { + Account[] accounts = mAccountManager.getAccountsAsUser(userId); + Set<Account> accountHashSet = new HashSet<Account>(); for (Account account : accounts) { accountHashSet.add(account); } @@ -391,7 +400,7 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { * initialization sync, while an adapter that the user had off will be off until the user * enables it on this device at which point it will get an initialization sync. */ - private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON) + private void restoreExistingAccountSyncSettingsFromJSON(JSONObject accountJSON, int userId) throws JSONException { // Restore authorities. JSONArray authorities = accountJSON.getJSONArray(KEY_ACCOUNT_AUTHORITIES); @@ -406,14 +415,15 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { int wasSyncable = authority.getInt(KEY_AUTHORITY_SYNC_STATE); ContentResolver.setSyncAutomaticallyAsUser( - account, authorityName, wasSyncEnabled, 0 /* user Id */); + account, authorityName, wasSyncEnabled, userId); if (!wasSyncEnabled) { - ContentResolver.setIsSyncable( + ContentResolver.setIsSyncableAsUser( account, authorityName, wasSyncable == 0 ? - 0 /* not syncable */ : 2 /* syncable but needs initialization */); + 0 /* not syncable */ : 2 /* syncable but needs initialization */, + userId); } } } @@ -422,4 +432,10 @@ public class AccountSyncSettingsBackupHelper implements BackupHelper { public void writeNewStateDescription(ParcelFileDescriptor newState) { } -}
\ No newline at end of file + + private static File getStashFile(int userId) { + File baseDir = userId == UserHandle.USER_SYSTEM ? Environment.getDataDirectory() + : Environment.getDataSystemCeDirectory(userId); + return new File(baseDir, STASH_FILE); + } +} diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java index 70798d03fc94..35e8f56cf36d 100644 --- a/core/java/com/android/server/backup/SystemBackupAgent.java +++ b/core/java/com/android/server/backup/SystemBackupAgent.java @@ -81,7 +81,7 @@ public class SystemBackupAgent extends BackupAgentHelper { private static final String WALLPAPER_IMAGE_KEY = WallpaperBackupHelper.WALLPAPER_IMAGE_KEY; private static final Set<String> sEligibleForMultiUser = Sets.newArraySet( - PERMISSION_HELPER, NOTIFICATION_HELPER); + PERMISSION_HELPER, NOTIFICATION_HELPER, SYNC_SETTINGS_HELPER); private int mUserId = UserHandle.USER_SYSTEM; @@ -91,7 +91,7 @@ public class SystemBackupAgent extends BackupAgentHelper { mUserId = user.getIdentifier(); - addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this)); + addHelper(SYNC_SETTINGS_HELPER, new AccountSyncSettingsBackupHelper(this, mUserId)); addHelper(PREFERRED_HELPER, new PreferredActivityBackupHelper()); addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(mUserId)); addHelper(PERMISSION_HELPER, new PermissionBackupHelper(mUserId)); diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 5a65028469a8..0ef4f874f583 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -647,6 +647,23 @@ static void MountPkgSpecificDir(const std::string& mntSourceRoot, static void PreparePkgSpecificDirs(const std::vector<std::string>& packageNames, const std::vector<std::string>& volumeLabels, bool mountAllObbs, userid_t userId, fail_fn_t fail_fn) { + if (volumeLabels.size() > 0) { + std::string sandboxDataDir = StringPrintf("/storage/%s", volumeLabels[0].c_str()); + if (volumeLabels[0] == "emulated") { + StringAppendF(&sandboxDataDir, "/%d", userId); + } + StringAppendF(&sandboxDataDir, "/Android/data/%s", packageNames[0].c_str()); + struct stat sb; + if (TEMP_FAILURE_RETRY(lstat(sandboxDataDir.c_str(), &sb)) == -1) { + if (errno == ENOENT) { + ALOGD("Sandbox not fully prepared for %s", sandboxDataDir.c_str()); + return; + } else { + fail_fn(CREATE_ERROR("Failed to lstat %s: %s", + sandboxDataDir.c_str(), strerror(errno))); + } + } + } for (auto& label : volumeLabels) { std::string mntSource = StringPrintf("/mnt/runtime/write/%s", label.c_str()); std::string mntTarget = StringPrintf("/storage/%s", label.c_str()); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c7b528c924dc..60b04cf6c77b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4411,8 +4411,8 @@ <permission android:name="android.permission.MONITOR_DEFAULT_SMS_PACKAGE" android:protectionLevel="signature|privileged" /> - <!-- A subclass of {@link android.app.SmsAppService} must be protected with this permission. --> - <permission android:name="android.permission.BIND_SMS_APP_SERVICE" + <!-- A subclass of {@link android.service.carrier.CarrierMessagingClientService} must be protected with this permission. --> + <permission android:name="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE" android:protectionLevel="signature" /> <!-- @hide Permission that allows configuring appops. diff --git a/core/res/res/drawable/bottomsheet_background.xml b/core/res/res/drawable/bottomsheet_background.xml new file mode 100644 index 000000000000..bc32ba6e3896 --- /dev/null +++ b/core/res/res/drawable/bottomsheet_background.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> + <corners + android:topLeftRadius="?attr/dialogCornerRadius" + android:topRightRadius="?attr/dialogCornerRadius" /> + <solid android:color="?attr/colorBackgroundFloating" /> +</shape> diff --git a/core/res/res/drawable/ic_drag_handle.xml b/core/res/res/drawable/ic_drag_handle.xml new file mode 100644 index 000000000000..67ab84d4080d --- /dev/null +++ b/core/res/res/drawable/ic_drag_handle.xml @@ -0,0 +1,23 @@ +<!-- + Copyright (C) 2016 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M20.0,9.0L4.0,9.0l0.0,2.0l16.0,0.0L20.0,9.0zM4.0,15.0l16.0,0.0l0.0,-2.0L4.0,13.0l0.0,2.0z"/> +</vector>
\ No newline at end of file diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml index f78466168e13..14a5310a4ff2 100644 --- a/core/res/res/layout/chooser_grid.xml +++ b/core/res/res/layout/chooser_grid.xml @@ -25,11 +25,24 @@ android:maxCollapsedHeightSmall="56dp" android:id="@id/contentPanel"> + <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" - android:background="?attr/colorBackgroundFloating"> + android:background="@drawable/bottomsheet_background"> + + <ImageView + android:id="@+id/drag" + android:layout_width="48dp" + android:layout_height="wrap_content" + android:src="@drawable/ic_drag_handle" + android:clickable="true" + android:paddingTop="@dimen/chooser_edge_margin_normal" + android:tint="?android:attr/textColorSecondary" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" /> + <TextView android:id="@+id/profile_button" android:layout_width="wrap_content" android:layout_height="48dp" @@ -41,7 +54,7 @@ android:textAppearance="?attr/textAppearanceButton" android:textColor="?attr/colorAccent" android:gravity="center_vertical" - android:layout_alignParentTop="true" + android:layout_below="@id/drag" android:layout_alignParentRight="true" android:singleLine="true"/> @@ -207,7 +220,6 @@ android:clipToPadding="false" android:scrollbarStyle="outsideOverlay" android:background="?attr/colorBackgroundFloating" - android:elevation="8dp" android:listSelector="@color/transparent" android:divider="@null" android:scrollIndicators="top" @@ -219,7 +231,7 @@ android:layout_alwaysShow="true" android:background="?attr/colorBackgroundFloating" android:text="@string/noApplications" - android:padding="32dp" + android:padding="@dimen/chooser_edge_margin_normal" android:gravity="center" android:visibility="gone"/> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 759fc12dadae..881688bd00c0 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -164,6 +164,15 @@ provider.--> <attr name="grantUriPermissions" format="boolean" /> + <!-- If true, the system will always create URI permission grants + in the cases where {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} + or {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} would apply. + This is useful for a content provider that dynamically enforces permissions + on calls in to the provider, instead of through the manifest: the system + needs to know that it should always apply permission grants, even if it + looks like the target of the grant would already have access to the URI. --> + <attr name="forceUriPermissions" format="boolean" /> + <!-- Characterizes the potential risk implied in a permission and indicates the procedure the system should follow when determining whether to grant the permission to an application requesting it. {@link @@ -2199,6 +2208,7 @@ <attr name="readPermission" /> <attr name="writePermission" /> <attr name="grantUriPermissions" /> + <attr name="forceUriPermissions" /> <attr name="permission" /> <attr name="multiprocess" /> <attr name="initOrder" /> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 16c074484e2c..02fae4a2c1b3 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -199,8 +199,6 @@ <color name="Red_700">#ffc53929</color> <color name="Red_800">#ffb93221</color> - <color name="chooser_service_row_background_color">#fff5f5f5</color> - <!-- Status bar color for semi transparent mode. --> <color name="system_bar_background_semi_transparent">#66000000</color> <!-- 40% black --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e7d8102ff83e..f84f1f1bdb4f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2938,6 +2938,7 @@ <public name="inheritShowWhenLocked" /> <public name="zygotePreloadName" /> <public name="useEmbeddedDex" /> + <public name="forceUriPermissions" /> </public-group> <public-group type="drawable" first-id="0x010800b4"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6d4b04c5e66a..401a7fec972a 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2744,7 +2744,6 @@ <java-symbol type="drawable" name="scroll_indicator_material" /> <java-symbol type="layout" name="chooser_row" /> - <java-symbol type="color" name="chooser_service_row_background_color" /> <java-symbol type="id" name="target_badge" /> <java-symbol type="bool" name="config_supportDoubleTapWake" /> <java-symbol type="drawable" name="ic_perm_device_info" /> diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index b6f56ada445e..3d59835a6719 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -27,7 +27,9 @@ import static com.android.internal.app.ChooserWrapperActivity.sOverrides; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,6 +45,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.metrics.LogMaker; import android.net.Uri; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -51,11 +54,14 @@ import androidx.test.rule.ActivityTestRule; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import java.util.ArrayList; @@ -66,6 +72,11 @@ import java.util.List; */ @RunWith(AndroidJUnit4.class) public class ChooserActivityTest { + + private static final int CONTENT_PREVIEW_IMAGE = 1; + private static final int CONTENT_PREVIEW_FILE = 2; + private static final int CONTENT_PREVIEW_TEXT = 3; + @Rule public ActivityTestRule<ChooserWrapperActivity> mActivityRule = new ActivityTestRule<>(ChooserWrapperActivity.class, false, @@ -402,16 +413,15 @@ public class ChooserActivityTest { createResolvedComponentsForTestWithOtherProfile(1); when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent( - Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); final ChooserWrapperActivity activity = mActivityRule .launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.copy_button)).perform(click()); - ClipboardManager clipboard = (ClipboardManager) activity.getSystemService( Context.CLIPBOARD_SERVICE); ClipData clipData = clipboard.getPrimaryClip(); @@ -488,8 +498,8 @@ public class ChooserActivityTest { List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), - Mockito.anyBoolean(), - Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); waitForIdle(); onView(withId(R.id.content_preview_image_1_large)).check(matches(isDisplayed())); @@ -498,6 +508,93 @@ public class ChooserActivityTest { onView(withId(R.id.content_preview_image_3_small)).check(matches(isDisplayed())); } + @Test + public void testOnCreateLogging() { + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); + waitForIdle(); + verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture()); + assertThat(logMakerCaptor.getAllValues().get(0).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getTaggedData(MetricsProto.MetricsEvent.FIELD_TIME_TO_APP_TARGETS), + is(notNullValue())); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getTaggedData(MetricsProto.MetricsEvent.FIELD_SHARESHEET_MIMETYPE), + is("TestType")); + } + + @Test + public void testEmptyPreviewLogging() { + Intent sendIntent = createSendTextIntentWithPreview(null, null); + + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "empty preview logger test")); + waitForIdle(); + verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture()); + // First invocation is from onCreate + assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), + is(CONTENT_PREVIEW_TEXT)); + } + + @Test + public void testTitlePreviewLogging() { + Intent sendIntent = createSendTextIntentWithPreview("TestTitle", null); + + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + verify(mockLogger, Mockito.times(2)).write(logMakerCaptor.capture()); + // First invocation is from onCreate + assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), + is(CONTENT_PREVIEW_TEXT)); + } + + @Test + public void testImagePreviewLogging() { + Uri uri = Uri.parse("android.resource://com.android.frameworks.coretests/" + + com.android.frameworks.coretests.R.drawable.test320x240); + + ArrayList<Uri> uris = new ArrayList<>(); + uris.add(uri); + + Intent sendIntent = createSendImageIntentWithPreview(uris); + sOverrides.previewThumbnail = createBitmap(); + + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor<LogMaker> logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, null)); + waitForIdle(); + verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture()); + // First invocation is from onCreate + assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), + is(CONTENT_PREVIEW_IMAGE)); + assertThat(logMakerCaptor.getAllValues().get(2).getCategory(), + is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + assertThat(logMakerCaptor.getAllValues().get(2).getSubtype(), + is(CONTENT_PREVIEW_IMAGE)); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index ec8122fb2e47..f60467bd3df2 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -25,6 +25,8 @@ import android.graphics.Bitmap; import android.net.Uri; import android.util.Size; +import com.android.internal.logging.MetricsLogger; + import java.util.function.Function; public class ChooserWrapperActivity extends ChooserActivity { @@ -94,6 +96,11 @@ public class ChooserWrapperActivity extends ChooserActivity { return super.isImageType(mimeType); } + @Override + protected MetricsLogger getMetricsLogger() { + return sOverrides.metricsLogger; + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -106,6 +113,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public ResolverListController resolverListController; public Boolean isVoiceInteraction; public Bitmap previewThumbnail; + public MetricsLogger metricsLogger; public void reset() { onSafelyStartCallback = null; @@ -113,6 +121,7 @@ public class ChooserWrapperActivity extends ChooserActivity { createPackageManager = null; previewThumbnail = null; resolverListController = mock(ResolverListController.class); + metricsLogger = mock(MetricsLogger.class); } } } diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index b9088d417dad..31d22327c79f 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -31,6 +31,8 @@ import android.system.OsConstants; import android.util.Log; import android.util.Pair; +import com.android.internal.util.ArrayUtils; + import libcore.io.IoUtils; import libcore.io.Streams; @@ -395,6 +397,12 @@ public class ExifInterface { * http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/PanasonicRaw.html */ public static final String TAG_RW2_JPG_FROM_RAW = "JpgFromRaw"; + /** + * Type is byte[]. See <a href= + * "https://en.wikipedia.org/wiki/Extensible_Metadata_Platform">Extensible + * Metadata Platform (XMP)</a> for details on contents. + */ + public static final String TAG_XMP = "Xmp"; /** * Private tags used for pointing the other IFD offsets. @@ -1012,7 +1020,8 @@ public class ExifInterface { new ExifTag(TAG_RW2_SENSOR_BOTTOM_BORDER, 6, IFD_FORMAT_ULONG), new ExifTag(TAG_RW2_SENSOR_RIGHT_BORDER, 7, IFD_FORMAT_ULONG), new ExifTag(TAG_RW2_ISO, 23, IFD_FORMAT_USHORT), - new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED) + new ExifTag(TAG_RW2_JPG_FROM_RAW, 46, IFD_FORMAT_UNDEFINED), + new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE), }; // Primary image IFD Exif Private tags (See JEITA CP-3451C Section 4.6.8 Tag Support Levels) @@ -1243,6 +1252,8 @@ public class ExifInterface { private static final Charset ASCII = Charset.forName("US-ASCII"); // Identifier for EXIF APP1 segment in JPEG private static final byte[] IDENTIFIER_EXIF_APP1 = "Exif\0\0".getBytes(ASCII); + // Identifier for XMP APP1 segment in JPEG + private static final byte[] IDENTIFIER_XMP_APP1 = "http://ns.adobe.com/xap/1.0/\0".getBytes(ASCII); // JPEG segment markers, that each marker consumes two bytes beginning with 0xff and ending with // the indicator. There is no SOF4, SOF8, SOF16 markers in JPEG and SOFx markers indicates start // of frame(baseline DCT) and the image size info exists in its beginning part. @@ -2046,6 +2057,22 @@ public class ExifInterface { } /** + * Returns the raw bytes for the value of the requested tag inside the image + * file, or {@code null} if the tag is not contained. + * + * @return raw bytes for the value of the requested tag, or {@code null} if + * no tag was found. + */ + public @Nullable byte[] getAttributeBytes(@NonNull String tag) { + final ExifAttribute attribute = getExifAttribute(tag); + if (attribute != null) { + return attribute.bytes; + } else { + return null; + } + } + + /** * Stores the latitude and longitude value in a float array. The first element is * the latitude, and the second element is the longitude. Returns false if the * Exif tags are not available. @@ -2432,40 +2459,32 @@ public class ExifInterface { } switch (marker) { case MARKER_APP1: { - if (DEBUG) { - Log.d(TAG, "MARKER_APP1"); - } - if (length < 6) { - // Skip if it's not an EXIF APP1 segment. - break; - } - byte[] identifier = new byte[6]; - if (in.read(identifier) != 6) { - throw new IOException("Invalid exif"); - } - bytesRead += 6; - length -= 6; - if (!Arrays.equals(identifier, IDENTIFIER_EXIF_APP1)) { - // Skip if it's not an EXIF APP1 segment. - break; - } - if (length <= 0) { - throw new IOException("Invalid exif"); - } - if (DEBUG) { - Log.d(TAG, "readExifSegment with a byte array (length: " + length + ")"); - } - // Save offset values for createJpegThumbnailBitmap() function - mExifOffset = bytesRead; - - byte[] bytes = new byte[length]; - if (in.read(bytes) != length) { - throw new IOException("Invalid exif"); - } + final int start = bytesRead; + final byte[] bytes = new byte[length]; + in.readFully(bytes); bytesRead += length; length = 0; - readExifSegment(bytes, imageType); + if (ArrayUtils.startsWith(bytes, IDENTIFIER_EXIF_APP1)) { + final long offset = start + IDENTIFIER_EXIF_APP1.length; + final byte[] value = Arrays.copyOfRange(bytes, + IDENTIFIER_EXIF_APP1.length, bytes.length); + + readExifSegment(value, imageType); + + // Save offset values for createJpegThumbnailBitmap() function + mExifOffset = (int) offset; + } else if (ArrayUtils.startsWith(bytes, IDENTIFIER_XMP_APP1)) { + // See XMP Specification Part 3: Storage in Files, 1.1.3 JPEG, Table 6 + final long offset = start + IDENTIFIER_XMP_APP1.length; + final byte[] value = Arrays.copyOfRange(bytes, + IDENTIFIER_XMP_APP1.length, bytes.length); + + if (getAttribute(TAG_XMP) == null) { + mAttributes[IFD_TYPE_PRIMARY].put(TAG_XMP, new ExifAttribute( + IFD_FORMAT_BYTE, value.length, offset, value)); + } + } break; } diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 4fa7d6462092..88017413659f 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -38,6 +38,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; import android.net.ip.IpClient.IpClientCallbacksWrapper; @@ -1489,6 +1490,29 @@ public class ApfFilter { installNewProgramLocked(); } + /** + * Add keepalive packet filter. + * + * @param slot The index used to access the filter. + * @param pkt Parameters needed to compose the filter. + */ + public synchronized void addKeepalivePacketFilter(int slot, + TcpKeepalivePacketDataParcelable pkt) { + // TODO: implement this. + Log.e(TAG, "APF function is not implemented: addKeepalivePacketFilter(" + slot + ", " + + pkt + ")"); + } + + /** + * Remove keepalive packet filter. + * + * @param slot The index used to access the filter. + */ + public synchronized void removeKeepalivePacketFilter(int slot) { + // TODO: implement this. + Log.e(TAG, "APF function is not implemented: removeKeepalivePacketFilter(" + slot + ")"); + } + static public long counterValue(byte[] data, Counter counter) throws ArrayIndexOutOfBoundsException { // Follow the same wrap-around addressing scheme of the interpreter. diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 12fe8c507db4..9e5991298834 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -23,6 +23,7 @@ import static android.net.shared.LinkPropertiesParcelableUtil.toStableParcelable import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission; +import android.annotation.NonNull; import android.content.Context; import android.net.ConnectivityManager; import android.net.DhcpResults; @@ -34,6 +35,7 @@ import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; import android.net.ProxyInfoParcelable; import android.net.RouteInfo; +import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfCapabilities; import android.net.apf.ApfFilter; import android.net.dhcp.DhcpClient; @@ -292,6 +294,8 @@ public class IpClient extends StateMachine { private static final int EVENT_PROVISIONING_TIMEOUT = 10; private static final int EVENT_DHCPACTION_TIMEOUT = 11; private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12; + private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13; + private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14; // Internal commands to use instead of trying to call transitionTo() inside // a given State's enter() method. Calling transitionTo() from enter/exit @@ -522,6 +526,16 @@ public class IpClient extends StateMachine { checkNetworkStackCallingPermission(); IpClient.this.setMulticastFilter(enabled); } + @Override + public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) { + checkNetworkStackCallingPermission(); + IpClient.this.addKeepalivePacketFilter(slot, pkt); + } + @Override + public void removeKeepalivePacketFilter(int slot) { + checkNetworkStackCallingPermission(); + IpClient.this.removeKeepalivePacketFilter(slot); + } } public String getInterfaceName() { @@ -644,6 +658,22 @@ public class IpClient extends StateMachine { } /** + * Called by WifiStateMachine to add keepalive packet filter before setting up + * keepalive offload. + */ + public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) { + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt); + } + + /** + * Called by WifiStateMachine to remove keepalive packet filter after stopping keepalive + * offload. + */ + public void removeKeepalivePacketFilter(int slot) { + sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot, 0 /* Unused */); + } + + /** * Dump logs of this IpClient. */ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { @@ -1512,6 +1542,23 @@ public class IpClient extends StateMachine { break; } + case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: { + final int slot = msg.arg1; + if (mApfFilter != null) { + mApfFilter.addKeepalivePacketFilter(slot, + (TcpKeepalivePacketDataParcelable) msg.obj); + } + break; + } + + case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: { + final int slot = msg.arg1; + if (mApfFilter != null) { + mApfFilter.removeKeepalivePacketFilter(slot); + } + break; + } + case EVENT_DHCPACTION_TIMEOUT: stopDhcpAction(); break; diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp index 45fa2dc2f383..4a09b3e205a6 100644 --- a/packages/NetworkStack/tests/Android.bp +++ b/packages/NetworkStack/tests/Android.bp @@ -49,6 +49,7 @@ android_test { "libhidlbase", "libhidltransport", "libhwbinder", + "libjsoncpp", "liblog", "liblzma", "libnativehelper", diff --git a/packages/SystemUI/res/layout/bubble_expanded_view.xml b/packages/SystemUI/res/layout/bubble_expanded_view.xml index 403d92869fbf..f664c0581d7e 100644 --- a/packages/SystemUI/res/layout/bubble_expanded_view.xml +++ b/packages/SystemUI/res/layout/bubble_expanded_view.xml @@ -14,7 +14,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> -<com.android.systemui.bubbles.BubbleExpandedViewContainer +<com.android.systemui.bubbles.BubbleExpandedView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" @@ -93,4 +93,4 @@ </FrameLayout> -</com.android.systemui.bubbles.BubbleExpandedViewContainer> +</com.android.systemui.bubbles.BubbleExpandedView> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index eb95f2be0fd6..d6a46a9e3458 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -70,7 +70,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton -public class BubbleController implements BubbleExpandedViewContainer.OnBubbleBlockedListener { +public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener { private static final int MAX_BUBBLES = 5; // TODO: actually enforce this private static final String TAG = "BubbleController"; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index f08ba1936b40..bf9d7ba61189 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -51,7 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; /** * Container for the expanded bubble view, handles rendering the caret and header of the view. */ -public class BubbleExpandedViewContainer extends LinearLayout implements View.OnClickListener { +public class BubbleExpandedView extends LinearLayout implements View.OnClickListener { private static final String TAG = "BubbleExpandedView"; // The triangle pointing to the expanded view @@ -81,19 +81,19 @@ public class BubbleExpandedViewContainer extends LinearLayout implements View.On private OnBubbleBlockedListener mOnBubbleBlockedListener; - public BubbleExpandedViewContainer(Context context) { + public BubbleExpandedView(Context context) { this(context, null); } - public BubbleExpandedViewContainer(Context context, AttributeSet attrs) { + public BubbleExpandedView(Context context, AttributeSet attrs) { this(context, attrs, 0); } - public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr) { + public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } - public BubbleExpandedViewContainer(Context context, AttributeSet attrs, int defStyleAttr, + public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPm = context.getPackageManager(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index afa9f02c6ee2..305f86655fcd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -94,7 +94,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F private StackAnimationController mStackAnimationController; private ExpandedAnimationController mExpandedAnimationController; - private BubbleExpandedViewContainer mExpandedViewContainer; + private BubbleExpandedView mExpandedViewContainer; private int mBubbleSize; private int mBubblePadding; @@ -173,7 +173,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - mExpandedViewContainer = (BubbleExpandedViewContainer) + mExpandedViewContainer = (BubbleExpandedView) LayoutInflater.from(context).inflate(R.layout.bubble_expanded_view, this /* parent */, false /* attachToRoot */); mExpandedViewContainer.setElevation(elevation); @@ -226,7 +226,7 @@ public class BubbleStackView extends FrameLayout implements BubbleTouchHandler.F /** * Sets the listener to notify when a bubble is blocked. */ - public void setOnBlockedListener(BubbleExpandedViewContainer.OnBubbleBlockedListener listener) { + public void setOnBlockedListener(BubbleExpandedView.OnBubbleBlockedListener listener) { mExpandedViewContainer.setOnBlockedListener(listener); } diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index eb8710d6759d..3c910696ba60 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -6949,6 +6949,32 @@ message MetricsEvent { // OS: Q NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING = 1648; + // CATEGORY: ACTION_ACTIVITY_CHOOSER_SHOWN + // Field to add the mimetype for a ChooserActivity + // OS:Q + FIELD_SHARESHEET_MIMETYPE = 1649; + + // CATEGORY: ACTION_ACTIVITY_CHOOSER_SHOWN + // Sharesheet direct targets are ready to show. + // OS:Q + ACTION_ACTIVITY_CHOOSER_SHOWN_DIRECT_TARGET = 1650; + + // CATEGORY: ACTION_SHARESHEET_SCROLL + // Sharesheet are either expanded, scrolling through them or compacted again. + // OS:Q + // Subtype 1 means collapsed, 0 expanded + ACTION_SHARESHEET_COLLAPSED_CHANGED = 1651; + + // ACTION: Share with screenshot extra + // OS: Q + ACTION_SHARE_WITH_PREVIEW = 1652; + + // CATEGORY: ACTION_ACTIVITY_CHOOSER_SHOWN + // OS:Q + // The time elapsed from triggering the share to displaying the app targets + // formerly: histogram system_cost_for_smart_sharing + FIELD_TIME_TO_APP_TARGETS = 1653; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 14322ecd76b9..0dd1ded40ead 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -41,6 +41,7 @@ import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Trace; import android.os.UserHandle; +import android.os.UserManager; import android.util.Slog; import android.util.SparseArray; @@ -433,8 +434,46 @@ public class BackupManagerService { getServiceForUserIfCallerHasPermission(userId, "getConfigurationIntent()"); return userBackupManagerService == null - ? null - : userBackupManagerService.getConfigurationIntent(transportName); + ? null + : userBackupManagerService.getConfigurationIntent(transportName); + } + + /** + * Sets the ancestral work profile for the calling user. + * + * <p> The ancestral work profile corresponds to the profile that was used to restore to the + * callers profile. + */ + public void setAncestralSerialNumber(long ancestralSerialNumber) { + UserBackupManagerService userBackupManagerService = + getServiceForUserIfCallerHasPermission( + Binder.getCallingUserHandle().getIdentifier(), + "setAncestralSerialNumber()"); + + if (userBackupManagerService != null) { + userBackupManagerService.setAncestralSerialNumber(ancestralSerialNumber); + } + } + + /** + * Returns a {@link UserHandle} for the user that has {@code ancestralSerialNumber} as the + * serial number of the its ancestral work profile. + * + * <p> The ancestral work profile is set by {@link #setAncestralSerialNumber(long)} + * and it corresponds to the profile that was used to restore to the callers profile. + */ + @Nullable + public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) { + for (UserHandle handle : mContext.getSystemService(UserManager.class).getUserProfiles()) { + UserBackupManagerService userBackupManagerService = getServiceUsers().get( + handle.getIdentifier()); + if (userBackupManagerService != null) { + if (userBackupManagerService.getAncestralSerialNumber() == ancestralSerialNumber) { + return handle; + } + } + } + return null; } /** diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 87872e8bcbc2..17e9b350d368 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -760,6 +760,21 @@ public class Trampoline extends IBackupManager.Stub { } @Override + @Nullable public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) { + if (mService != null) { + return mService.getUserForAncestralSerialNumber(ancestralSerialNumber); + } + return null; + } + + @Override + public void setAncestralSerialNumber(long ancestralSerialNumber) { + if (mService != null) { + mService.setAncestralSerialNumber(ancestralSerialNumber); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; int userId = binderGetCallingUserId(); diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 8b2c1b95ecfd..b2afbc3ec5f9 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -244,6 +244,8 @@ public class UserBackupManagerService { private static final long BUSY_BACKOFF_MIN_MILLIS = 1000 * 60 * 60; // one hour private static final int BUSY_BACKOFF_FUZZ = 1000 * 60 * 60 * 2; // two hours + private static final String SERIAL_ID_FILE = "serial_id"; + private final @UserIdInt int mUserId; private final BackupAgentTimeoutParameters mAgentTimeoutParameters; private final TransportManager mTransportManager; @@ -360,6 +362,8 @@ public class UserBackupManagerService { private Set<String> mAncestralPackages = null; private long mAncestralToken = 0; private long mCurrentToken = 0; + @Nullable private File mAncestralSerialNumberFile; + /** * Creates an instance of {@link UserBackupManagerService} and initializes state for it. This @@ -2308,6 +2312,55 @@ public class UserBackupManagerService { } } + /** + * Sets the work profile serial number of the ancestral work profile. + */ + public void setAncestralSerialNumber(long ancestralSerialNumber) { + mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, + "setAncestralSerialNumber"); + Slog.v(TAG, "Setting ancestral work profile id to " + ancestralSerialNumber); + try (RandomAccessFile af = getAncestralSerialNumberFile()) { + af.writeLong(ancestralSerialNumber); + } catch (IOException e) { + Slog.w(TAG, "Unable to write to work profile serial mapping file:", e); + } + } + + /** + * Returns the work profile serial number of the ancestral device. This will be set by + * {@link #setAncestralSerialNumber(long)}. Will return {@code -1} if not set. + */ + public long getAncestralSerialNumber() { + try (RandomAccessFile af = getAncestralSerialNumberFile()) { + return af.readLong(); + } catch (IOException e) { + Slog.w(TAG, "Unable to write to work profile serial number file:", e); + return -1; + } + } + + private RandomAccessFile getAncestralSerialNumberFile() throws FileNotFoundException { + if (mAncestralSerialNumberFile == null) { + mAncestralSerialNumberFile = new File( + UserBackupManagerFiles.getBaseStateDir(getUserId()), + SERIAL_ID_FILE); + if (!mAncestralSerialNumberFile.exists()) { + try { + mAncestralSerialNumberFile.createNewFile(); + } catch (IOException e) { + Slog.w(TAG, "serial number mapping file creation failed", e); + } + } + } + return new RandomAccessFile(mAncestralSerialNumberFile, "rwd"); + } + + @VisibleForTesting + void setAncestralSerialNumberFile(File ancestralSerialNumberFile) { + mAncestralSerialNumberFile = ancestralSerialNumberFile; + } + + /** Clear the given package's backup data from the current transport. */ public void clearBackupData(String transportName, String packageName) { if (DEBUG) Slog.v(TAG, "clearBackupData() of " + packageName + " on " + transportName); diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1cb2c4a2f5ee..80b3d67217fd 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -6377,6 +6377,14 @@ public class ConnectivityService extends IConnectivityManager.Stub } @Override + public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds, + Messenger messenger, IBinder binder) { + enforceKeepalivePermission(); + mKeepaliveTracker.startTcpKeepalive( + getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, messenger, binder); + } + + @Override public void stopKeepalive(Network network, int slot) { mHandler.sendMessage(mHandler.obtainMessage( NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network)); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 94fc552fa1c2..d1994249cdc1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3495,7 +3495,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (!app.killedByAm) { reportUidInfoMessageLocked(TAG, "Process " + app.processName + " (pid " + pid + ") has died: " - + ProcessList.makeOomAdjString(app.setAdj) + + ProcessList.makeOomAdjString(app.setAdj, true) + " " + ProcessList.makeProcStateString(app.setProcState), app.info.uid); mAllowLowerMemLevel = true; } else { @@ -10029,7 +10029,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.print(" #"); pw.print(index); pw.print(": "); - pw.print(ProcessList.makeOomAdjString(proc.setAdj)); + pw.print(ProcessList.makeOomAdjString(proc.setAdj, false)); pw.print(" "); pw.print(ProcessList.makeProcStateString(proc.getCurProcState())); pw.print(" "); @@ -11334,7 +11334,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i = list.size() - 1; i >= 0; i--) { ProcessRecord r = list.get(i).first; long token = proto.start(fieldId); - String oomAdj = ProcessList.makeOomAdjString(r.setAdj); + String oomAdj = ProcessList.makeOomAdjString(r.setAdj, true); proto.write(ProcessOomProto.PERSISTENT, r.isPersistent()); proto.write(ProcessOomProto.NUM, (origList.size()-1)-list.get(i).second); proto.write(ProcessOomProto.OOM_ADJ, oomAdj); @@ -11434,7 +11434,7 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=list.size()-1; i>=0; i--) { ProcessRecord r = list.get(i).first; - String oomAdj = ProcessList.makeOomAdjString(r.setAdj); + String oomAdj = ProcessList.makeOomAdjString(r.setAdj, false); char schedGroup; switch (r.setSchedGroup) { case ProcessList.SCHED_GROUP_BACKGROUND: @@ -12871,7 +12871,7 @@ public class ActivityManagerService extends IActivityManager.Stub private void appendBasicMemEntry(StringBuilder sb, int oomAdj, int procState, long pss, long memtrack, String name) { sb.append(" "); - sb.append(ProcessList.makeOomAdjString(oomAdj)); + sb.append(ProcessList.makeOomAdjString(oomAdj, false)); sb.append(' '); sb.append(ProcessList.makeProcStateString(procState)); sb.append(' '); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index f90c0cab984a..69cf54b5528a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -679,47 +679,65 @@ public final class ProcessList { return totalProcessLimit/2; } - private static String buildOomTag(String prefix, String space, int val, int base) { + private static String buildOomTag(String prefix, String compactPrefix, String space, int val, + int base, boolean compact) { final int diff = val - base; if (diff == 0) { + if (compact) { + return compactPrefix; + } if (space == null) return prefix; return prefix + space; } if (diff < 10) { - return prefix + "+ " + Integer.toString(diff); + return prefix + (compact ? "+" : "+ ") + Integer.toString(diff); } return prefix + "+" + Integer.toString(diff); } - public static String makeOomAdjString(int setAdj) { + public static String makeOomAdjString(int setAdj, boolean compact) { if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) { - return buildOomTag("cch", " ", setAdj, ProcessList.CACHED_APP_MIN_ADJ); + return buildOomTag("cch", "cch", " ", setAdj, + ProcessList.CACHED_APP_MIN_ADJ, compact); } else if (setAdj >= ProcessList.SERVICE_B_ADJ) { - return buildOomTag("svcb ", null, setAdj, ProcessList.SERVICE_B_ADJ); + return buildOomTag("svcb ", "svcb", null, setAdj, + ProcessList.SERVICE_B_ADJ, compact); } else if (setAdj >= ProcessList.PREVIOUS_APP_ADJ) { - return buildOomTag("prev ", null, setAdj, ProcessList.PREVIOUS_APP_ADJ); + return buildOomTag("prev ", "prev", null, setAdj, + ProcessList.PREVIOUS_APP_ADJ, compact); } else if (setAdj >= ProcessList.HOME_APP_ADJ) { - return buildOomTag("home ", null, setAdj, ProcessList.HOME_APP_ADJ); + return buildOomTag("home ", "home", null, setAdj, + ProcessList.HOME_APP_ADJ, compact); } else if (setAdj >= ProcessList.SERVICE_ADJ) { - return buildOomTag("svc ", null, setAdj, ProcessList.SERVICE_ADJ); + return buildOomTag("svc ", "svc", null, setAdj, + ProcessList.SERVICE_ADJ, compact); } else if (setAdj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) { - return buildOomTag("hvy ", null, setAdj, ProcessList.HEAVY_WEIGHT_APP_ADJ); + return buildOomTag("hvy ", "hvy", null, setAdj, + ProcessList.HEAVY_WEIGHT_APP_ADJ, compact); } else if (setAdj >= ProcessList.BACKUP_APP_ADJ) { - return buildOomTag("bkup ", null, setAdj, ProcessList.BACKUP_APP_ADJ); + return buildOomTag("bkup ", "bkup", null, setAdj, + ProcessList.BACKUP_APP_ADJ, compact); } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { - return buildOomTag("prcp ", null, setAdj, ProcessList.PERCEPTIBLE_APP_ADJ); + return buildOomTag("prcp ", "prcp", null, setAdj, + ProcessList.PERCEPTIBLE_APP_ADJ, compact); } else if (setAdj >= ProcessList.VISIBLE_APP_ADJ) { - return buildOomTag("vis", " ", setAdj, ProcessList.VISIBLE_APP_ADJ); + return buildOomTag("vis", "vis", " ", setAdj, + ProcessList.VISIBLE_APP_ADJ, compact); } else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) { - return buildOomTag("fore ", null, setAdj, ProcessList.FOREGROUND_APP_ADJ); + return buildOomTag("fore ", "fore", null, setAdj, + ProcessList.FOREGROUND_APP_ADJ, compact); } else if (setAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) { - return buildOomTag("psvc ", null, setAdj, ProcessList.PERSISTENT_SERVICE_ADJ); + return buildOomTag("psvc ", "psvc", null, setAdj, + ProcessList.PERSISTENT_SERVICE_ADJ, compact); } else if (setAdj >= ProcessList.PERSISTENT_PROC_ADJ) { - return buildOomTag("pers ", null, setAdj, ProcessList.PERSISTENT_PROC_ADJ); + return buildOomTag("pers ", "pers", null, setAdj, + ProcessList.PERSISTENT_PROC_ADJ, compact); } else if (setAdj >= ProcessList.SYSTEM_ADJ) { - return buildOomTag("sys ", null, setAdj, ProcessList.SYSTEM_ADJ); + return buildOomTag("sys ", "sys", null, setAdj, + ProcessList.SYSTEM_ADJ, compact); } else if (setAdj >= ProcessList.NATIVE_ADJ) { - return buildOomTag("ntv ", null, setAdj, ProcessList.NATIVE_ADJ); + return buildOomTag("ntv ", "ntv", null, setAdj, + ProcessList.NATIVE_ADJ, compact); } else { return Integer.toString(setAdj); } diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java index 3131255a61cd..0b6a4329d15b 100644 --- a/services/core/java/com/android/server/appbinding/AppBindingService.java +++ b/services/core/java/com/android/server/appbinding/AppBindingService.java @@ -47,7 +47,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.SystemService; import com.android.server.am.PersistentConnection; import com.android.server.appbinding.finders.AppServiceFinder; -import com.android.server.appbinding.finders.SmsAppServiceFinder; +import com.android.server.appbinding.finders.CarrierMessagingClientServiceFinder; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -147,7 +147,7 @@ public class AppBindingService extends Binder { mIPackageManager = injector.getIPackageManager(); mHandler = BackgroundThread.getHandler(); - mApps.add(new SmsAppServiceFinder(context, this::onAppChanged, mHandler)); + mApps.add(new CarrierMessagingClientServiceFinder(context, this::onAppChanged, mHandler)); // Initialize with the default value to make it non-null. mConstants = AppBindingConstants.initializeFromString(""); diff --git a/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java index fcc28f8e2886..4c5f1a1c7b49 100644 --- a/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java +++ b/services/core/java/com/android/server/appbinding/finders/CarrierMessagingClientServiceFinder.java @@ -19,8 +19,6 @@ package com.android.server.appbinding.finders; import static android.provider.Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL; import android.Manifest.permission; -import android.app.ISmsAppService; -import android.app.SmsAppService; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -30,6 +28,8 @@ import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.IBinder; import android.os.UserHandle; +import android.service.carrier.CarrierMessagingClientService; +import android.service.carrier.ICarrierMessagingClientService; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; @@ -41,10 +41,11 @@ import com.android.server.appbinding.AppBindingConstants; import java.util.function.BiConsumer; /** - * Find the SmsAppService service within the default SMS app. + * Find the CarrierMessagingClientService service within the default SMS app. */ -public class SmsAppServiceFinder extends AppServiceFinder<SmsAppService, ISmsAppService> { - public SmsAppServiceFinder(Context context, +public class CarrierMessagingClientServiceFinder + extends AppServiceFinder<CarrierMessagingClientService, ICarrierMessagingClientService> { + public CarrierMessagingClientServiceFinder(Context context, BiConsumer<AppServiceFinder, Integer> listener, Handler callbackHandler) { super(context, listener, callbackHandler); @@ -62,23 +63,23 @@ public class SmsAppServiceFinder extends AppServiceFinder<SmsAppService, ISmsApp } @Override - protected Class<SmsAppService> getServiceClass() { - return SmsAppService.class; + protected Class<CarrierMessagingClientService> getServiceClass() { + return CarrierMessagingClientService.class; } @Override - public ISmsAppService asInterface(IBinder obj) { - return ISmsAppService.Stub.asInterface(obj); + public ICarrierMessagingClientService asInterface(IBinder obj) { + return ICarrierMessagingClientService.Stub.asInterface(obj); } @Override protected String getServiceAction() { - return TelephonyManager.ACTION_SMS_APP_SERVICE; + return TelephonyManager.ACTION_CARRIER_MESSAGING_CLIENT_SERVICE; } @Override protected String getServicePermission() { - return permission.BIND_SMS_APP_SERVICE; + return permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE; } @Override @@ -121,7 +122,7 @@ public class SmsAppServiceFinder extends AppServiceFinder<SmsAppService, ISmsApp @Override public void onReceive(Context context, Intent intent) { if (ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals(intent.getAction())) { - mListener.accept(SmsAppServiceFinder.this, getSendingUserId()); + mListener.accept(CarrierMessagingClientServiceFinder.this, getSendingUserId()); } } }; diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index d872e4d428ab..6cff57d4bbb1 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -17,6 +17,8 @@ package com.android.server.connectivity; import static android.net.NattSocketKeepalive.NATT_PORT; +import static android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER; +import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER; import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE; import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE; import static android.net.NetworkAgent.EVENT_SOCKET_KEEPALIVE; @@ -37,6 +39,9 @@ import android.net.NattKeepalivePacketData; import android.net.NetworkAgent; import android.net.NetworkUtils; import android.net.SocketKeepalive.InvalidPacketException; +import android.net.SocketKeepalive.InvalidSocketException; +import android.net.TcpKeepalivePacketData; +import android.net.TcpKeepalivePacketData.TcpSocketInfo; import android.net.util.IpUtils; import android.os.Binder; import android.os.Handler; @@ -65,7 +70,7 @@ import java.util.HashMap; * * Provides methods to stop and start keepalive requests, and keeps track of keepalives across all * networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its - * methods must be called only from the ConnectivityService handler thread. + * handle* methods must be called only from the ConnectivityService handler thread. */ public class KeepaliveTracker { @@ -78,9 +83,12 @@ public class KeepaliveTracker { private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = new HashMap<> (); private final Handler mConnectivityServiceHandler; + @NonNull + private final TcpKeepaliveController mTcpController; public KeepaliveTracker(Handler handler) { mConnectivityServiceHandler = handler; + mTcpController = new TcpKeepaliveController(handler); } /** @@ -96,20 +104,33 @@ public class KeepaliveTracker { private final int mUid; private final int mPid; private final NetworkAgentInfo mNai; + private final int mType; + private final FileDescriptor mFd; - /** Keepalive slot. A small integer that identifies this keepalive among the ones handled - * by this network. */ + public static final int TYPE_NATT = 1; + public static final int TYPE_TCP = 2; + + // Keepalive slot. A small integer that identifies this keepalive among the ones handled + // by this network. private int mSlot = NO_KEEPALIVE; // Packet data. private final KeepalivePacketData mPacket; private final int mInterval; - // Whether the keepalive is started or not. - public boolean isStarted; - - public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai, - KeepalivePacketData packet, int interval) { + // Whether the keepalive is started or not. The initial state is NOT_STARTED. + private static final int NOT_STARTED = 1; + private static final int STARTING = 2; + private static final int STARTED = 3; + private int mStartedState = NOT_STARTED; + + KeepaliveInfo(@NonNull Messenger messenger, + @NonNull IBinder binder, + @NonNull NetworkAgentInfo nai, + @NonNull KeepalivePacketData packet, + int interval, + int type, + @NonNull FileDescriptor fd) { mMessenger = messenger; mBinder = binder; mPid = Binder.getCallingPid(); @@ -118,6 +139,8 @@ public class KeepaliveTracker { mNai = nai; mPacket = packet; mInterval = interval; + mType = type; + mFd = fd; try { mBinder.linkToDeath(this, 0); @@ -130,32 +153,40 @@ public class KeepaliveTracker { return mNai; } + private String startedStateString(final int state) { + switch (state) { + case NOT_STARTED : return "NOT_STARTED"; + case STARTING : return "STARTING"; + case STARTED : return "STARTED"; + } + throw new IllegalArgumentException("Unknown state"); + } + public String toString() { - return new StringBuffer("KeepaliveInfo [") - .append(" network=").append(mNai.network) - .append(" isStarted=").append(isStarted) - .append(" ") - .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)) - .append("->") - .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)) - .append(" interval=" + mInterval) - .append(" packetData=" + HexDump.toHexString(mPacket.getPacket())) - .append(" uid=").append(mUid).append(" pid=").append(mPid) - .append(" ]") - .toString(); + return "KeepaliveInfo [" + + " network=" + mNai.network + + " startedState=" + startedStateString(mStartedState) + + " " + + IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort) + + "->" + + IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort) + + " interval=" + mInterval + + " uid=" + mUid + " pid=" + mPid + + " packetData=" + HexDump.toHexString(mPacket.getPacket()) + + " ]"; } /** Sends a message back to the application via its SocketKeepalive.Callback. */ void notifyMessenger(int slot, int err) { + if (DBG) { + Log.d(TAG, "notify keepalive " + mSlot + " on " + mNai.network + " for " + err); + } KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err); } /** Called when the application process is killed. */ public void binderDied() { - // Not called from ConnectivityService handler thread, so send it a message. - mConnectivityServiceHandler.obtainMessage( - NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, - mSlot, BINDER_DIED, mNai.network).sendToTarget(); + stop(BINDER_DIED); } void unlinkDeathRecipient() { @@ -202,7 +233,26 @@ public class KeepaliveTracker { int error = isValid(); if (error == SUCCESS) { Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name()); - mNai.asyncChannel.sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + switch (mType) { + case TYPE_NATT: + mNai.asyncChannel + .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + break; + case TYPE_TCP: + mTcpController.startSocketMonitor(mFd, this, mSlot); + mNai.asyncChannel + .sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */, + mPacket); + // TODO: check result from apf and notify of failure as needed. + mNai.asyncChannel + .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket); + break; + default: + Log.wtf(TAG, "Starting keepalive with unknown type: " + mType); + handleStopKeepalive(mNai, mSlot, error); + return; + } + mStartedState = STARTING; } else { handleStopKeepalive(mNai, mSlot, error); return; @@ -216,15 +266,27 @@ public class KeepaliveTracker { Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network); } } - if (isStarted) { + if (NOT_STARTED != mStartedState) { Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); - mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); + if (mType == TYPE_NATT) { + mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); + } else if (mType == TYPE_TCP) { + mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot); + mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot); + mTcpController.stopSocketMonitor(mSlot); + } else { + Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType); + } } // TODO: at the moment we unconditionally return failure here. In cases where the // NetworkAgent is alive, should we ask it to reply, so it can return failure? notifyMessenger(mSlot, reason); unlinkDeathRecipient(); } + + void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) { + handleStopKeepalive(mNai, mSlot, socketKeepaliveReason); + } } void notifyMessenger(Messenger messenger, int slot, int err) { @@ -328,20 +390,38 @@ public class KeepaliveTracker { return; } - if (reason == SUCCESS && !ki.isStarted) { + // This can be called in a number of situations : + // - startedState is STARTING. + // - reason is SUCCESS => go to STARTED. + // - reason isn't SUCCESS => it's an error starting. Go to NOT_STARTED and stop keepalive. + // - startedState is STARTED. + // - reason is SUCCESS => it's a success stopping. Go to NOT_STARTED and stop keepalive. + // - reason isn't SUCCESS => it's an error in exec. Go to NOT_STARTED and stop keepalive. + // The control is not supposed to ever come here if the state is NOT_STARTED. This is + // because in NOT_STARTED state, the code will switch to STARTING before sending messages + // to start, and the only way to NOT_STARTED is this function, through the edges outlined + // above : in all cases, keepalive gets stopped and can't restart without going into + // STARTING as messages are ordered. This also depends on the hardware processing the + // messages in order. + // TODO : clarify this code and get rid of mStartedState. Using a StateMachine is an + // option. + if (reason == SUCCESS && KeepaliveInfo.STARTING == ki.mStartedState) { // Keepalive successfully started. if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name()); - ki.isStarted = true; + ki.mStartedState = KeepaliveInfo.STARTED; ki.notifyMessenger(slot, reason); } else { // Keepalive successfully stopped, or error. - ki.isStarted = false; + ki.mStartedState = KeepaliveInfo.NOT_STARTED; if (reason == SUCCESS) { + // The message indicated success stopping : don't call handleStopKeepalive. if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name()); } else { + // The message indicated some error trying to start or during the course of + // keepalive : do call handleStopKeepalive. + handleStopKeepalive(nai, slot, reason); if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason); } - handleStopKeepalive(nai, slot, reason); } } @@ -379,7 +459,47 @@ public class KeepaliveTracker { notifyMessenger(messenger, NO_KEEPALIVE, e.error); return; } - KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds); + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds, + KeepaliveInfo.TYPE_NATT, null); + mConnectivityServiceHandler.obtainMessage( + NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget(); + } + + /** + * Called by ConnectivityService to start TCP keepalive on a file descriptor. + * + * In order to offload keepalive for application correctly, sequence number, ack number and + * other fields are needed to form the keepalive packet. Thus, this function synchronously + * puts the socket into repair mode to get the necessary information. After the socket has been + * put into repair mode, the application cannot access the socket until reverted to normal. + * + * See {@link android.net.SocketKeepalive}. + **/ + public void startTcpKeepalive(@Nullable NetworkAgentInfo nai, + @NonNull FileDescriptor fd, + int intervalSeconds, + @NonNull Messenger messenger, + @NonNull IBinder binder) { + if (nai == null) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); + return; + } + + TcpKeepalivePacketData packet = null; + try { + TcpSocketInfo tsi = TcpKeepaliveController.switchToRepairMode(fd); + packet = TcpKeepalivePacketData.tcpKeepalivePacket(tsi); + } catch (InvalidPacketException | InvalidSocketException e) { + try { + TcpKeepaliveController.switchOutOfRepairMode(fd); + } catch (ErrnoException e1) { + Log.e(TAG, "Couldn't move fd out of repair mode after failure to start keepalive"); + } + notifyMessenger(messenger, NO_KEEPALIVE, e.error); + return; + } + KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds, + KeepaliveInfo.TYPE_TCP, fd); Log.d(TAG, "Created keepalive: " + ki.toString()); mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget(); } diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java index 640504ff6e29..8a9ac23cf06a 100644 --- a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java +++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java @@ -15,7 +15,6 @@ */ package com.android.server.connectivity; -import static android.net.NetworkAgent.EVENT_SOCKET_KEEPALIVE; import static android.net.SocketKeepalive.DATA_RECEIVED; import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET; import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE; @@ -31,10 +30,8 @@ import android.net.SocketKeepalive.InvalidSocketException; import android.net.TcpKeepalivePacketData.TcpSocketInfo; import android.net.TcpRepairWindow; import android.os.Handler; -import android.os.Message; import android.os.MessageQueue; import android.os.Messenger; -import android.os.RemoteException; import android.system.ErrnoException; import android.system.Int32Ref; import android.system.Os; @@ -42,6 +39,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo; import java.io.FileDescriptor; import java.net.InetAddress; @@ -111,7 +109,7 @@ public class TcpKeepaliveController { * tcp/ip information. */ // TODO : make this private. It's far too confusing that this gets called from outside - // at a time that nobody can understand, but the switch out is in this class only. + // at a time that nobody can understand. public static TcpSocketInfo switchToRepairMode(FileDescriptor fd) throws InvalidSocketException { if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd); @@ -199,7 +197,13 @@ public class TcpKeepaliveController { trw.rcvWndScale); } - private static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) + /** + * Switch the tcp socket out of repair mode. + * + * @param fd the fd of socket to switch back to normal. + */ + // TODO : make this private. + public static void switchOutOfRepairMode(@NonNull final FileDescriptor fd) throws ErrnoException { Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF); } @@ -212,7 +216,7 @@ public class TcpKeepaliveController { * @param slot keepalive slot. */ public void startSocketMonitor(@NonNull final FileDescriptor fd, - @NonNull final Messenger messenger, final int slot) { + @NonNull final KeepaliveInfo ki, final int slot) { synchronized (mListeners) { if (null != mListeners.get(slot)) { throw new IllegalArgumentException("This slot is already taken"); @@ -226,31 +230,13 @@ public class TcpKeepaliveController { // This can't be called twice because the queue guarantees that once the listener // is unregistered it can't be called again, even for a message that arrived // before it was unregistered. - int result; - try { - // First move the socket out of repair mode. - if (DBG) Log.d(TAG, "Moving socket out of repair mode for event : " + readyFd); - switchOutOfRepairMode(readyFd); - result = (0 != (events & EVENT_ERROR)) ? ERROR_INVALID_SOCKET : DATA_RECEIVED; - } catch (ErrnoException e) { - // Could not move the socket out of repair mode. Still continue with notifying - // the client - Log.e(TAG, "Cannot switch socket out of repair mode", e); - result = ERROR_INVALID_SOCKET; - } - // Prepare and send the message to the receiver. - final Message message = Message.obtain(); - message.what = EVENT_SOCKET_KEEPALIVE; - message.arg1 = slot; - message.arg2 = result; - try { - messenger.send(message); - } catch (RemoteException e) { - // Remote process died - } - synchronized (mListeners) { - mListeners.remove(slot); + final int reason; + if (0 != (events & EVENT_ERROR)) { + reason = ERROR_INVALID_SOCKET; + } else { + reason = DATA_RECEIVED; } + ki.onFileDescriptorInitiatedStop(reason); // The listener returns the new set of events to listen to. Because 0 means no // event, the listener gets unregistered. return 0; diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index e268e4458986..bfa7f9d439e3 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -899,9 +899,20 @@ public final class ContentService extends IContentService.Stub { @Override public void setIsSyncable(Account account, String providerName, int syncable) { + setIsSyncableAsUser(account, providerName, syncable, UserHandle.getCallingUserId()); + } + + /** + * @hide + */ + @Override + public void setIsSyncableAsUser(Account account, String providerName, int syncable, + int userId) { if (TextUtils.isEmpty(providerName)) { throw new IllegalArgumentException("Authority must not be empty"); } + enforceCrossUserPermission(userId, + "no permission to set the sync settings for user " + userId); mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); @@ -909,7 +920,6 @@ public final class ContentService extends IContentService.Stub { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); - int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 7096477ce5f4..99e07071f361 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -3261,7 +3261,7 @@ public class SyncManager { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "Account " + aau.account + " added, checking sync restore data"); } - AccountSyncSettingsBackupHelper.accountAdded(mContext); + AccountSyncSettingsBackupHelper.accountAdded(mContext, syncTargets.userId); break; } } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index efafdfaf2b54..c2a75aba28b9 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -611,18 +611,43 @@ public class Installer extends SystemService { } } - public boolean snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) + /** + * Snapshots user data of the given package. + * + * @param pkg name of the package to snapshot user data for. + * @param userId id of the user whose data to snapshot. + * @param storageFlags flags controlling which data (CE or DE) to snapshot. + * + * @return inode of the snapshot of users CE package data, or {@code 0} if a remote calls + * shouldn't be continued. See {@link #checkBeforeRemote}. + * + * @throws InstallerException if failed to snapshot user data. + */ + public long snapshotAppData(String pkg, @UserIdInt int userId, int storageFlags) throws InstallerException { - if (!checkBeforeRemote()) return false; + if (!checkBeforeRemote()) return 0; try { - mInstalld.snapshotAppData(null, pkg, userId, storageFlags); - return true; + return mInstalld.snapshotAppData(null, pkg, userId, storageFlags); } catch (Exception e) { throw InstallerException.from(e); } } + /** + * Restores user data snapshot of the given package. + * + * @param pkg name of the package to restore user data for. + * @param appId id of the package to restore user data for. + * @param ceDataInode inode of CE user data folder of this app. + * @param userId id of the user whose data to restore. + * @param storageFlags flags controlling which data (CE or DE) to restore. + * + * @return {@code true} if user data restore was successful, or {@code false} if a remote call + * shouldn't be continued. See {@link #checkBeforeRemote}. + * + * @throws InstallerException if failed to restore user data. + */ public boolean restoreAppDataSnapshot(String pkg, @AppIdInt int appId, long ceDataInode, String seInfo, @UserIdInt int userId, int storageFlags) throws InstallerException { if (!checkBeforeRemote()) return false; @@ -636,6 +661,31 @@ public class Installer extends SystemService { } } + /** + * Deletes user data snapshot of the given package. + * + * @param pkg name of the package to delete user data snapshot for. + * @param userId id of the user whose user data snapshot to delete. + * @param ceSnapshotInode inode of CE user data snapshot. + * @param storageFlags flags controlling which user data snapshot (CE or DE) to delete. + * + * @return {@code true} if user data snapshot was successfully deleted, or {@code false} if a + * remote call shouldn't be continued. See {@link #checkBeforeRemote}. + * + * @throws InstallerException if failed to delete user data snapshot. + */ + public boolean destroyAppDataSnapshot(String pkg, @UserIdInt int userId, long ceSnapshotInode, + int storageFlags) throws InstallerException { + if (!checkBeforeRemote()) return false; + + try { + mInstalld.destroyAppDataSnapshot(null, pkg, userId, ceSnapshotInode, storageFlags); + return true; + } catch (Exception e) { + throw InstallerException.from(e); + } + } + private static void assertValidInstructionSet(String instructionSet) throws InstallerException { for (String abi : Build.SUPPORTED_ABIS) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e18da7f7b319..32dc98837854 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14613,6 +14613,13 @@ public class PackageManagerService extends IPackageManager.Stub PACKAGE_MIME_TYPE); enableRollbackIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + // Allow the broadcast to be sent before boot complete. + // This is needed when committing the apk part of a staged + // session in early boot. The rollback manager registers + // its receiver early enough during the boot process that + // it will not miss the broadcast. + enableRollbackIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendOrderedBroadcastAsUser(enableRollbackIntent, getUser(), android.Manifest.permission.PACKAGE_ROLLBACK_AGENT, new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java index 8dd076028b43..f3b838560ebd 100644 --- a/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java +++ b/services/core/java/com/android/server/rollback/AppDataRollbackHelper.java @@ -22,6 +22,7 @@ import android.content.rollback.RollbackInfo; import android.os.storage.StorageManager; import android.util.IntArray; import android.util.Log; +import android.util.SparseLongArray; import com.android.internal.annotations.VisibleForTesting; import com.android.server.pm.Installer; @@ -51,11 +52,13 @@ public class AppDataRollbackHelper { * Creates an app data snapshot for a specified {@code packageName} for {@code installedUsers}, * a specified set of users for whom the package is installed. * - * @return a list of users for which the snapshot is pending, usually because data for one or - * more users is still credential locked. + * @return a {@link SnapshotAppDataResult}/ + * @see SnapshotAppDataResult */ - public IntArray snapshotAppData(String packageName, int[] installedUsers) { + public SnapshotAppDataResult snapshotAppData(String packageName, int[] installedUsers) { final IntArray pendingBackups = new IntArray(); + final SparseLongArray ceSnapshotInodes = new SparseLongArray(); + for (int user : installedUsers) { final int storageFlags; if (isUserCredentialLocked(user)) { @@ -69,14 +72,17 @@ public class AppDataRollbackHelper { } try { - mInstaller.snapshotAppData(packageName, user, storageFlags); + long ceSnapshotInode = mInstaller.snapshotAppData(packageName, user, storageFlags); + if ((storageFlags & Installer.FLAG_STORAGE_CE) != 0) { + ceSnapshotInodes.put(user, ceSnapshotInode); + } } catch (InstallerException ie) { Log.e(TAG, "Unable to create app data snapshot for: " + packageName + ", userId: " + user, ie); } } - return pendingBackups; + return new SnapshotAppDataResult(pendingBackups, ceSnapshotInodes); } /** @@ -138,6 +144,22 @@ public class AppDataRollbackHelper { } /** + * Deletes an app data data snapshot for a specified package {@code packageName} for a + * given {@code user}. + */ + public void destroyAppDataSnapshot(String packageName, int user, long ceSnapshotInode) { + int storageFlags = Installer.FLAG_STORAGE_DE; + if (ceSnapshotInode > 0) { + storageFlags |= Installer.FLAG_STORAGE_CE; + } + try { + mInstaller.destroyAppDataSnapshot(packageName, user, ceSnapshotInode, storageFlags); + } catch (InstallerException ie) { + Log.e(TAG, "Unable to delete app data snapshot for " + packageName, ie); + } + } + + /** * Computes the list of pending backups and restores for {@code userId} given lists of * available and recent rollbacks. Packages pending backup for the given user are added * to {@code pendingBackups} and packages pending restore are added to {@code pendingRestores} @@ -191,16 +213,28 @@ public class AppDataRollbackHelper { } /** - * Commits the list of pending backups and restores for a given {@code userId}. + * Commits the list of pending backups and restores for a given {@code userId}. For the pending + * backups updates corresponding {@code changedRollbackData} with a mapping from {@code userId} + * to a inode of theirs CE user data snapshot. */ public void commitPendingBackupAndRestoreForUser(int userId, - ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores) { + ArrayList<String> pendingBackups, Map<String, RestoreInfo> pendingRestores, + List<RollbackData> changedRollbackData) { if (!pendingBackups.isEmpty()) { for (String packageName : pendingBackups) { try { - mInstaller.snapshotAppData(packageName, userId, Installer.FLAG_STORAGE_CE); + long ceSnapshotInode = mInstaller.snapshotAppData(packageName, userId, + Installer.FLAG_STORAGE_CE); + for (RollbackData data : changedRollbackData) { + for (PackageRollbackInfo info : data.packages) { + if (info.getPackageName().equals(packageName)) { + info.putCeSnapshotInode(userId, ceSnapshotInode); + } + } + } } catch (InstallerException ie) { - Log.e(TAG, "Unable to create app data snapshot for: " + packageName, ie); + Log.e(TAG, "Unable to create app data snapshot for: " + packageName + + ", userId: " + userId, ie); } } } @@ -233,4 +267,26 @@ public class AppDataRollbackHelper { return StorageManager.isFileEncryptedNativeOrEmulated() && !StorageManager.isUserKeyUnlocked(userId); } + + /** + * Encapsulates a result of {@link #snapshotAppData} method. + */ + public static final class SnapshotAppDataResult { + + /** + * A list of users for which the snapshot is pending, usually because data for one or more + * users is still credential locked. + */ + public final IntArray pendingBackups; + + /** + * A mapping between user and an inode of theirs CE data snapshot. + */ + public final SparseLongArray ceSnapshotInodes; + + public SnapshotAppDataResult(IntArray pendingBackups, SparseLongArray ceSnapshotInodes) { + this.pendingBackups = pendingBackups; + this.ceSnapshotInodes = ceSnapshotInodes; + } + } } diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 27f7bcf36ddb..24d5bd14bc44 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -43,6 +43,7 @@ import android.os.Process; import android.util.IntArray; import android.util.Log; import android.util.SparseBooleanArray; +import android.util.SparseLongArray; import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; @@ -110,7 +111,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { private final HandlerThread mHandlerThread; private final Installer mInstaller; private final RollbackPackageHealthObserver mPackageHealthObserver; - private final AppDataRollbackHelper mUserdataHelper; + private final AppDataRollbackHelper mAppDataRollbackHelper; RollbackManagerServiceImpl(Context context) { mContext = context; @@ -124,7 +125,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { mRollbackStore = new RollbackStore(new File(Environment.getDataDirectory(), "rollback")); mPackageHealthObserver = new RollbackPackageHealthObserver(mContext); - mUserdataHelper = new AppDataRollbackHelper(mInstaller); + mAppDataRollbackHelper = new AppDataRollbackHelper(mInstaller); // Kick off loading of the rollback data from strorage in a background // thread. @@ -449,7 +450,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { for (PackageRollbackInfo info : data.packages) { if (info.getPackageName().equals(packageName)) { iter.remove(); - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); break; } } @@ -464,13 +465,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { final List<RollbackData> changed; synchronized (mLock) { ensureRollbackDataLoadedLocked(); - changed = mUserdataHelper.computePendingBackupsAndRestores(userId, + changed = mAppDataRollbackHelper.computePendingBackupsAndRestores(userId, pendingBackupPackages, pendingRestorePackages, mAvailableRollbacks, mRecentlyExecutedRollbacks); } - mUserdataHelper.commitPendingBackupAndRestoreForUser(userId, - pendingBackupPackages, pendingRestorePackages); + mAppDataRollbackHelper.commitPendingBackupAndRestoreForUser(userId, + pendingBackupPackages, pendingRestorePackages, changed); for (RollbackData rd : changed) { try { @@ -520,7 +521,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // mAvailableRollbacks, or is it okay to leave as // unavailable until the next reboot when it will go // away on its own? - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); } } } @@ -592,7 +593,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { info.getVersionRolledBackFrom(), installedVersion)) { iter.remove(); - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); break; } } @@ -705,7 +706,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { if (!now.isBefore(data.timestamp.plusMillis(ROLLBACK_LIFETIME_DURATION_MILLIS))) { iter.remove(); - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); } else if (oldest == null || oldest.isAfter(data.timestamp)) { oldest = data.timestamp; } @@ -821,9 +822,13 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { String packageName = newPackage.packageName; for (PackageRollbackInfo info : rd.packages) { if (info.getPackageName().equals(packageName)) { - IntArray pendingBackups = mUserdataHelper.snapshotAppData( - packageName, installedUsers); - info.getPendingBackups().addAll(pendingBackups); + AppDataRollbackHelper.SnapshotAppDataResult rs = + mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); + info.getPendingBackups().addAll(rs.pendingBackups); + for (int i = 0; i < rs.ceSnapshotInodes.size(); i++) { + info.putCeSnapshotInode(rs.ceSnapshotInodes.keyAt(i), + rs.ceSnapshotInodes.valueAt(i)); + } try { mRollbackStore.saveAvailableRollback(rd); } catch (IOException ioe) { @@ -892,13 +897,18 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { VersionedPackage installedVersion = new VersionedPackage(packageName, pkgInfo.getLongVersionCode()); - IntArray pendingBackups = IntArray.wrap(new int[0]); + final AppDataRollbackHelper.SnapshotAppDataResult result; if (snapshotUserData && !isApex) { - pendingBackups = mUserdataHelper.snapshotAppData(packageName, installedUsers); + result = mAppDataRollbackHelper.snapshotAppData(packageName, installedUsers); + } else { + result = new AppDataRollbackHelper.SnapshotAppDataResult(IntArray.wrap(new int[0]), + new SparseLongArray()); } PackageRollbackInfo info = new PackageRollbackInfo(newVersion, installedVersion, - pendingBackups, new ArrayList<>(), isApex); + result.pendingBackups, new ArrayList<>(), isApex, IntArray.wrap(installedUsers), + result.ceSnapshotInodes); + RollbackData data; try { int childSessionId = session.getSessionId(); @@ -948,9 +958,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { getHandler().post(() -> { final RollbackData rollbackData = getRollbackForPackage(packageName); for (int userId : userIds) { - final boolean changedRollbackData = mUserdataHelper.restoreAppData(packageName, - rollbackData, userId, appId, ceDataInode, seInfo); - + final boolean changedRollbackData = mAppDataRollbackHelper.restoreAppData( + packageName, rollbackData, userId, appId, ceDataInode, seInfo); // We've updated metadata about this rollback, so save it to flash. if (changedRollbackData) { try { @@ -1142,12 +1151,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { scheduleExpiration(ROLLBACK_LIFETIME_DURATION_MILLIS); } catch (IOException e) { Log.e(TAG, "Unable to enable rollback", e); - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); } } else { // The install session was aborted, clean up the pending // install. - mRollbackStore.deleteAvailableRollback(data); + deleteRollback(data); } } } @@ -1246,4 +1255,17 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { throw new IOException("Failed to allocate rollback ID"); } + + private void deleteRollback(RollbackData rollbackData) { + for (PackageRollbackInfo info : rollbackData.packages) { + IntArray installedUsers = info.getInstalledUsers(); + SparseLongArray ceSnapshotInodes = info.getCeSnapshotInodes(); + for (int i = 0; i < installedUsers.size(); i++) { + int userId = installedUsers.get(i); + mAppDataRollbackHelper.destroyAppDataSnapshot(info.getPackageName(), userId, + ceSnapshotInodes.get(userId, 0)); + } + } + mRollbackStore.deleteAvailableRollback(rollbackData); + } } diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java index d17ebaef0b6c..be904eacebff 100644 --- a/services/core/java/com/android/server/rollback/RollbackStore.java +++ b/services/core/java/com/android/server/rollback/RollbackStore.java @@ -23,6 +23,7 @@ import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.content.rollback.RollbackInfo; import android.util.IntArray; import android.util.Log; +import android.util.SparseLongArray; import libcore.io.IoUtils; @@ -160,6 +161,28 @@ class RollbackStore { return restoreInfos; } + private static @NonNull JSONArray ceSnapshotInodesToJson( + @NonNull SparseLongArray ceSnapshotInodes) throws JSONException { + JSONArray array = new JSONArray(); + for (int i = 0; i < ceSnapshotInodes.size(); i++) { + JSONObject entryJson = new JSONObject(); + entryJson.put("userId", ceSnapshotInodes.keyAt(i)); + entryJson.put("ceSnapshotInode", ceSnapshotInodes.valueAt(i)); + array.put(entryJson); + } + return array; + } + + private static @NonNull SparseLongArray ceSnapshotInodesFromJson(JSONArray json) + throws JSONException { + SparseLongArray ceSnapshotInodes = new SparseLongArray(json.length()); + for (int i = 0; i < json.length(); i++) { + JSONObject entry = json.getJSONObject(i); + ceSnapshotInodes.append(entry.getInt("userId"), entry.getLong("ceSnapshotInode")); + } + return ceSnapshotInodes; + } + /** * Reads the list of recently executed rollbacks from persistent storage. */ @@ -263,8 +286,6 @@ class RollbackStore { * rollback. */ void deleteAvailableRollback(RollbackData data) { - // TODO(narayan): Make sure we delete the userdata snapshot along with the backup of the - // actual app. removeFile(data.backupDir); } @@ -341,11 +362,15 @@ class RollbackStore { IntArray pendingBackups = info.getPendingBackups(); List<RestoreInfo> pendingRestores = info.getPendingRestores(); + IntArray installedUsers = info.getInstalledUsers(); json.put("pendingBackups", convertToJsonArray(pendingBackups)); json.put("pendingRestores", convertToJsonArray(pendingRestores)); json.put("isApex", info.isApex()); + json.put("installedUsers", convertToJsonArray(installedUsers)); + json.put("ceSnapshotInodes", ceSnapshotInodesToJson(info.getCeSnapshotInodes())); + return json; } @@ -362,8 +387,12 @@ class RollbackStore { final boolean isApex = json.getBoolean("isApex"); + final IntArray installedUsers = convertToIntArray(json.getJSONArray("installedUsers")); + final SparseLongArray ceSnapshotInodes = ceSnapshotInodesFromJson( + json.getJSONArray("ceSnapshotInodes")); + return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo, - pendingBackups, pendingRestores, isApex); + pendingBackups, pendingRestores, isApex, installedUsers, ceSnapshotInodes); } private JSONArray versionedPackagesToJson(List<VersionedPackage> packages) diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 1163d3916cb1..057b53e6232f 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -128,6 +128,8 @@ public final class TvInputManagerService extends SystemService { private final WatchLogHandler mWatchLogHandler; + private IBinder.DeathRecipient mDeathRecipient; + public TvInputManagerService(Context context) { super(context); @@ -674,6 +676,7 @@ public final class TvInputManagerService extends SystemService { if (sessionToken == userState.mainSessionToken) { setMainLocked(sessionToken, false, callingUid, userId); } + sessionState.session.asBinder().unlinkToDeath(sessionState, 0); sessionState.session.release(); } } catch (RemoteException | SessionNotFoundException e) { @@ -709,6 +712,7 @@ public final class TvInputManagerService extends SystemService { clientState.sessionTokens.remove(sessionToken); if (clientState.isEmpty()) { userState.clientStateMap.remove(sessionState.client.asBinder()); + sessionState.client.asBinder().unlinkToDeath(clientState, 0); } } @@ -1002,17 +1006,19 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { final UserState userState = getOrCreateUserStateLocked(resolvedUserId); userState.callbackSet.add(callback); - try { - callback.asBinder().linkToDeath(new IBinder.DeathRecipient() { - @Override - public void binderDied() { - synchronized (mLock) { - if (userState.callbackSet != null) { - userState.callbackSet.remove(callback); - } + mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + synchronized (mLock) { + if (userState.callbackSet != null) { + userState.callbackSet.remove(callback); } } - }, 0); + } + }; + + try { + callback.asBinder().linkToDeath(mDeathRecipient, 0); } catch (RemoteException e) { Slog.e(TAG, "client process has already died", e); } @@ -1031,6 +1037,7 @@ public final class TvInputManagerService extends SystemService { synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(resolvedUserId); userState.callbackSet.remove(callback); + callback.asBinder().unlinkToDeath(mDeathRecipient, 0); } } finally { Binder.restoreCallingIdentity(identity); diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 744efab7d78d..332df956d0fb 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -1067,8 +1067,9 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub { // Figure out the value returned when access is allowed final int allowedResult; - if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0) { - // If we're extending a persistable grant, then we need to return + if ((modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0 + || pi.forceUriPermissions) { + // If we're extending a persistable grant or need to force, then we need to return // "targetUid" so that we always create a grant data structure to // support take/release APIs allowedResult = targetUid; diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java index b8db3f39eb62..37909c3022d4 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java @@ -62,6 +62,7 @@ import org.robolectric.shadows.ShadowContextWrapper; import java.io.File; import java.io.FileDescriptor; +import java.io.IOException; import java.io.PrintWriter; /** Tests for the user-aware backup/restore system service {@link BackupManagerService}. */ @@ -1516,8 +1517,7 @@ public class BackupManagerServiceTest { public void testDump_onRegisteredUser_callsMethodForUser() throws Exception { BackupManagerService backupManagerService = createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); - File testFile = new File(mContext.getFilesDir(), "test"); - testFile.createNewFile(); + File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); String[] args = {"1", "2"}; @@ -1531,8 +1531,7 @@ public class BackupManagerServiceTest { @Test public void testDump_onUnknownUser_doesNotPropagateCall() throws Exception { BackupManagerService backupManagerService = createService(); - File testFile = new File(mContext.getFilesDir(), "test"); - testFile.createNewFile(); + File testFile = createTestFile(); FileDescriptor fileDescriptor = new FileDescriptor(); PrintWriter printWriter = new PrintWriter(testFile); String[] args = {"1", "2"}; @@ -1542,6 +1541,12 @@ public class BackupManagerServiceTest { verify(mUserOneService, never()).dump(fileDescriptor, printWriter, args); } + private File createTestFile() throws IOException { + File testFile = new File(mContext.getFilesDir(), "test"); + testFile.createNewFile(); + return testFile; + } + private BackupManagerService createService() { mShadowContext.grantPermissions(BACKUP); return new BackupManagerService( diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 427aed7364d2..c8d1eb413ecf 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -76,6 +76,7 @@ import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.io.File; +import java.io.IOException; import java.util.List; /** @@ -1158,6 +1159,58 @@ public class UserBackupManagerServiceTest { } /** + * Test that {@link UserBackupManagerService#getAncestralSerialNumber()} returns {@code -1} + * when value not set. + */ + @Test + public void testGetAncestralSerialNumber_notSet_returnsMinusOne() { + UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks(); + + assertThat(service.getAncestralSerialNumber()).isEqualTo(-1L); + } + + /** + * Test that {@link UserBackupManagerService#getAncestralSerialNumber()} returns correct value + * when value set. + */ + @Test + public void testGetAncestralSerialNumber_set_returnsCorrectValue() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks(); + service.setAncestralSerialNumberFile(createTestFile()); + + long testSerialNumber = 20L; + service.setAncestralSerialNumber(testSerialNumber); + + assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber); + } + + /** + * Test that {@link UserBackupManagerService#getAncestralSerialNumber()} returns correct value + * when value set. + */ + @Test + public void testGetAncestralSerialNumber_setTwice_returnsCorrectValue() throws Exception { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks(); + service.setAncestralSerialNumberFile(createTestFile()); + + long testSerialNumber = 20L; + long testSerialNumber2 = 21L; + service.setAncestralSerialNumber(testSerialNumber); + service.setAncestralSerialNumber(testSerialNumber2); + + assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2); + } + + private File createTestFile() throws IOException { + File testFile = new File(mContext.getFilesDir(), "test"); + testFile.createNewFile(); + return testFile; + } + + + /** * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method. */ diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java index 43e3eb0193b1..50dbaf570e9e 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java @@ -18,15 +18,19 @@ package com.android.server.rollback; import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.content.pm.VersionedPackage; import android.content.rollback.PackageRollbackInfo; import android.content.rollback.PackageRollbackInfo.RestoreInfo; import android.util.IntArray; +import android.util.SparseLongArray; import com.android.server.pm.Installer; @@ -38,6 +42,7 @@ import org.mockito.Mockito; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; @RunWith(JUnit4.class) public class AppDataRollbackHelperTest { @@ -50,10 +55,14 @@ public class AppDataRollbackHelperTest { // All users are unlocked so we should snapshot data for them. doReturn(true).when(helper).isUserCredentialLocked(eq(10)); doReturn(true).when(helper).isUserCredentialLocked(eq(11)); - IntArray pending = helper.snapshotAppData("com.foo.bar", new int[]{10, 11}); - assertEquals(2, pending.size()); - assertEquals(10, pending.get(0)); - assertEquals(11, pending.get(1)); + AppDataRollbackHelper.SnapshotAppDataResult result = helper.snapshotAppData("com.foo.bar", + new int[]{10, 11}); + + assertEquals(2, result.pendingBackups.size()); + assertEquals(10, result.pendingBackups.get(0)); + assertEquals(11, result.pendingBackups.get(1)); + + assertEquals(0, result.ceSnapshotInodes.size()); InOrder inOrder = Mockito.inOrder(installer); inOrder.verify(installer).snapshotAppData( @@ -65,10 +74,14 @@ public class AppDataRollbackHelperTest { // One of the users is unlocked but the other isn't doReturn(false).when(helper).isUserCredentialLocked(eq(10)); doReturn(true).when(helper).isUserCredentialLocked(eq(11)); + when(installer.snapshotAppData(anyString(), anyInt(), anyInt())).thenReturn(239L); - pending = helper.snapshotAppData("com.foo.bar", new int[]{10, 11}); - assertEquals(1, pending.size()); - assertEquals(11, pending.get(0)); + result = helper.snapshotAppData("com.foo.bar", new int[]{10, 11}); + assertEquals(1, result.pendingBackups.size()); + assertEquals(11, result.pendingBackups.get(0)); + + assertEquals(1, result.ceSnapshotInodes.size()); + assertEquals(239L, result.ceSnapshotInodes.get(10)); inOrder = Mockito.inOrder(installer); inOrder.verify(installer).snapshotAppData( @@ -83,7 +96,7 @@ public class AppDataRollbackHelperTest { RollbackData data = new RollbackData(1, new File("/does/not/exist"), -1, true); data.packages.add(new PackageRollbackInfo( new VersionedPackage(packageName, 1), new VersionedPackage(packageName, 1), - new IntArray(), new ArrayList<>(), false)); + new IntArray(), new ArrayList<>(), false, new IntArray(), new SparseLongArray())); data.inProgress = true; return data; @@ -173,4 +186,53 @@ public class AppDataRollbackHelperTest { ArrayList<RestoreInfo> pendingRestores = rd.packages.get(0).getPendingRestores(); assertEquals(0, pendingRestores.size()); } + + @Test + public void destroyAppData() throws Exception { + Installer installer = mock(Installer.class); + AppDataRollbackHelper helper = new AppDataRollbackHelper(installer); + SparseLongArray ceSnapshotInodes = new SparseLongArray(); + ceSnapshotInodes.put(11, 239L); + + helper.destroyAppDataSnapshot("com.foo.bar", 10, 0L); + helper.destroyAppDataSnapshot("com.foo.bar", 11, 239L); + + InOrder inOrder = Mockito.inOrder(installer); + inOrder.verify(installer).destroyAppDataSnapshot( + eq("com.foo.bar"), eq(10), eq(0L), + eq(Installer.FLAG_STORAGE_DE)); + inOrder.verify(installer).destroyAppDataSnapshot( + eq("com.foo.bar"), eq(11), eq(239L), + eq(Installer.FLAG_STORAGE_DE | Installer.FLAG_STORAGE_CE)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void commitPendingBackupAndRestoreForUser_updatesRollbackData() throws Exception { + Installer installer = mock(Installer.class); + AppDataRollbackHelper helper = new AppDataRollbackHelper(installer); + + ArrayList<RollbackData> changedRollbackData = new ArrayList<>(); + changedRollbackData.add(createInProgressRollbackData("com.foo.bar")); + + when(installer.snapshotAppData(anyString(), anyInt(), anyInt())).thenReturn(239L); + + ArrayList<String> pendingBackups = new ArrayList<>(); + pendingBackups.add("com.foo.bar"); + + helper.commitPendingBackupAndRestoreForUser(11, pendingBackups, + new HashMap<>() /* pendingRestores */, changedRollbackData); + + assertEquals(1, changedRollbackData.size()); + assertEquals(1, changedRollbackData.get(0).packages.size()); + PackageRollbackInfo info = changedRollbackData.get(0).packages.get(0); + + assertEquals(1, info.getCeSnapshotInodes().size()); + assertEquals(239L, info.getCeSnapshotInodes().get(11)); + + InOrder inOrder = Mockito.inOrder(installer); + inOrder.verify(installer).snapshotAppData("com.foo.bar", 11 /* userId */, + Installer.FLAG_STORAGE_CE); + inOrder.verifyNoMoreInteractions(); + } } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 3f3b99692e9c..bfda2eac25c9 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -37,8 +37,10 @@ <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> + <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" - android:testOnly="true"> + android:testOnly="true" + android:largeHeap="true"> <uses-library android:name="android.test.mock" android:required="true" /> <activity android:name="com.android.server.wm.TaskStackChangedListenerTest$ActivityA" /> diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java index 548227067b67..4539ab3dc310 100644 --- a/telephony/java/android/provider/Telephony.java +++ b/telephony/java/android/provider/Telephony.java @@ -2118,6 +2118,608 @@ public final class Telephony { } /** + * Columns for the "rcs_*" tables used by {@link android.telephony.ims.RcsMessageStore} classes. + * + * @hide - not meant for public use + */ + public interface RcsColumns { + /** + * The authority for the content provider + */ + String AUTHORITY = "rcs"; + + /** + * The URI to start building upon to use {@link com.android.providers.telephony.RcsProvider} + */ + Uri CONTENT_AND_AUTHORITY = Uri.parse("content://" + AUTHORITY); + + /** + * The value to be used whenever a transaction that expects an integer to be returned + * failed. + */ + int TRANSACTION_FAILED = Integer.MIN_VALUE; + + /** + * The value that denotes a timestamp was not set before (e.g. a message that is not + * delivered yet will not have a DELIVERED_TIMESTAMP) + */ + long TIMESTAMP_NOT_SET = 0; + + /** + * The table that {@link android.telephony.ims.RcsThread} gets persisted to + */ + interface RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsThread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_THREAD_URI_PART = "thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsThread} via the content + * provider. + */ + Uri RCS_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, RCS_THREAD_URI_PART); + + /** + * The unique identifier of an {@link android.telephony.ims.RcsThread} + */ + String RCS_THREAD_ID_COLUMN = "rcs_thread_id"; + } + + /** + * The table that {@link android.telephony.ims.Rcs1To1Thread} gets persisted to + */ + interface Rcs1To1ThreadColumns extends RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.Rcs1To1Thread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_1_TO_1_THREAD_URI_PART = "p2p_thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.Rcs1To1Thread}s via the + * content provider + */ + Uri RCS_1_TO_1_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_1_TO_1_THREAD_URI_PART); + + /** + * The SMS/MMS thread to fallback to in case of an RCS outage + */ + String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id"; + } + + /** + * The table that {@link android.telephony.ims.RcsGroupThread} gets persisted to + */ + interface RcsGroupThreadColumns extends RcsThreadColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsGroupThread}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_GROUP_THREAD_URI_PART = "group_thread"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsGroupThread}s via the + * content provider + */ + Uri RCS_GROUP_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_GROUP_THREAD_URI_PART); + + /** + * The owner/admin of the {@link android.telephony.ims.RcsGroupThread} + */ + String OWNER_PARTICIPANT_COLUMN = "owner_participant"; + + /** + * The user visible name of the group + */ + String GROUP_NAME_COLUMN = "group_name"; + + /** + * The user visible icon of the group + */ + String GROUP_ICON_COLUMN = "group_icon"; + + /** + * The RCS conference URI for this group + */ + String CONFERENCE_URI_COLUMN = "conference_uri"; + } + + /** + * The view that enables polling from all types of RCS threads at once + */ + interface RcsUnifiedThreadColumns extends RcsThreadColumns, Rcs1To1ThreadColumns, + RcsGroupThreadColumns { + /** + * The type of this {@link android.telephony.ims.RcsThread} + */ + String THREAD_TYPE_COLUMN = "thread_type"; + + /** + * Integer returned as a result from a database query that denotes the thread is 1 to 1 + */ + int THREAD_TYPE_1_TO_1 = 0; + + /** + * Integer returned as a result from a database query that denotes the thread is 1 to 1 + */ + int THREAD_TYPE_GROUP = 1; + } + + /** + * The table that {@link android.telephony.ims.RcsParticipant} gets persisted to + */ + interface RcsParticipantColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsParticipant}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_PARTICIPANT_URI_PART = "participant"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsParticipant}s via the + * content provider + */ + Uri RCS_PARTICIPANT_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_PARTICIPANT_URI_PART); + + /** + * The unique identifier of the entry in the database + */ + String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id"; + + /** + * A foreign key on canonical_address table, also used by SMS/MMS + */ + String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id"; + + /** + * The user visible RCS alias for this participant. + */ + String RCS_ALIAS_COLUMN = "rcs_alias"; + } + + /** + * Additional constants to enable access to {@link android.telephony.ims.RcsParticipant} + * related data + */ + interface RcsParticipantHelpers extends RcsParticipantColumns { + /** + * The view that unifies "rcs_participant" and "canonical_addresses" tables for easy + * access to participant address. + */ + String RCS_PARTICIPANT_WITH_ADDRESS_VIEW = "rcs_participant_with_address_view"; + + /** + * The view that unifies "rcs_participant", "canonical_addresses" and + * "rcs_thread_participant" junction table to get full information on participants that + * contribute to threads. + */ + String RCS_PARTICIPANT_WITH_THREAD_VIEW = "rcs_participant_with_thread_view"; + } + + /** + * The table that {@link android.telephony.ims.RcsMessage} gets persisted to + */ + interface RcsMessageColumns { + /** + * Denotes the type of this message (i.e. + * {@link android.telephony.ims.RcsIncomingMessage} or + * {@link android.telephony.ims.RcsOutgoingMessage} + */ + String MESSAGE_TYPE_COLUMN = "rcs_message_type"; + + /** + * The unique identifier for the message in the database - i.e. the primary key. + */ + String MESSAGE_ID_COLUMN = "rcs_message_row_id"; + + /** + * The globally unique RCS identifier for the message. Please see 4.4.5.2 - GSMA + * RCC.53 (RCS Device API 1.6 Specification) + */ + String GLOBAL_ID_COLUMN = "rcs_message_global_id"; + + /** + * The subscription where this message was sent from/to. + */ + String SUB_ID_COLUMN = "sub_id"; + + /** + * The sending status of the message. + * @see android.telephony.ims.RcsMessage.RcsMessageStatus + */ + String STATUS_COLUMN = "status"; + + /** + * The creation timestamp of the message. + */ + String ORIGINATION_TIMESTAMP_COLUMN = "origination_timestamp"; + + /** + * The text content of the message. + */ + String MESSAGE_TEXT_COLUMN = "rcs_text"; + + /** + * The latitude content of the message, if it contains a location. + */ + String LATITUDE_COLUMN = "latitude"; + + /** + * The longitude content of the message, if it contains a location. + */ + String LONGITUDE_COLUMN = "longitude"; + } + + /** + * The table that additional information of {@link android.telephony.ims.RcsIncomingMessage} + * gets persisted to. + */ + interface RcsIncomingMessageColumns extends RcsMessageColumns { + /** + The path that should be used for referring to + * {@link android.telephony.ims.RcsIncomingMessage}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String INCOMING_MESSAGE_URI_PART = "incoming_message"; + + /** + * The URI to query incoming messages through + * {@link com.android.providers.telephony.RcsProvider} + */ + Uri INCOMING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + INCOMING_MESSAGE_URI_PART); + + /** + * The ID of the {@link android.telephony.ims.RcsParticipant} that sent this message + */ + String SENDER_PARTICIPANT_ID_COLUMN = "sender_participant"; + + /** + * The timestamp of arrival for this message. + */ + String ARRIVAL_TIMESTAMP_COLUMN = "arrival_timestamp"; + + /** + * The time when the recipient has read this message. + */ + String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; + } + + /** + * The table that additional information of {@link android.telephony.ims.RcsOutgoingMessage} + * gets persisted to. + */ + interface RcsOutgoingMessageColumns extends RcsMessageColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsOutgoingMessage}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String OUTGOING_MESSAGE_URI_PART = "outgoing_message"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsOutgoingMessage}s via the + * content provider + */ + Uri OUTGOING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + OUTGOING_MESSAGE_URI_PART); + } + + /** + * The delivery information of an {@link android.telephony.ims.RcsOutgoingMessage} + */ + interface RcsMessageDeliveryColumns extends RcsOutgoingMessageColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsOutgoingMessageDelivery}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String DELIVERY_URI_PART = "delivery"; + + /** + * The timestamp of delivery of this message. + */ + String DELIVERED_TIMESTAMP_COLUMN = "delivered_timestamp"; + + /** + * The time when the recipient has read this message. + */ + String SEEN_TIMESTAMP_COLUMN = "seen_timestamp"; + } + + /** + * The views that allow querying {@link android.telephony.ims.RcsIncomingMessage} and + * {@link android.telephony.ims.RcsOutgoingMessage} at the same time. + */ + interface RcsUnifiedMessageColumns extends RcsIncomingMessageColumns, + RcsOutgoingMessageColumns { + /** + * The path that is used to query all {@link android.telephony.ims.RcsMessage} in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String UNIFIED_MESSAGE_URI_PART = "message"; + + /** + * The URI to query all types of {@link android.telephony.ims.RcsMessage}s + */ + Uri UNIFIED_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + UNIFIED_MESSAGE_URI_PART); + + /** + * The name of the view that unites rcs_message and rcs_incoming_message tables. + */ + String UNIFIED_INCOMING_MESSAGE_VIEW = "unified_incoming_message_view"; + + /** + * The name of the view that unites rcs_message and rcs_outgoing_message tables. + */ + String UNIFIED_OUTGOING_MESSAGE_VIEW = "unified_outgoing_message_view"; + + /** + * The column that shows from which table the message entry came from. + */ + String MESSAGE_TYPE_COLUMN = "message_type"; + + /** + * Integer returned as a result from a database query that denotes that the message is + * an incoming message + */ + int MESSAGE_TYPE_INCOMING = 1; + + /** + * Integer returned as a result from a database query that denotes that the message is + * an outgoing message + */ + int MESSAGE_TYPE_OUTGOING = 0; + } + + /** + * The table that {@link android.telephony.ims.RcsFileTransferPart} gets persisted to. + */ + interface RcsFileTransferColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsFileTransferPart}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String FILE_TRANSFER_URI_PART = "file_transfer"; + + /** + * The URI to query or modify {@link android.telephony.ims.RcsFileTransferPart}s via the + * content provider + */ + Uri FILE_TRANSFER_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + FILE_TRANSFER_URI_PART); + + /** + * The globally unique file transfer ID for this RCS file transfer. + */ + String FILE_TRANSFER_ID_COLUMN = "rcs_file_transfer_id"; + + /** + * The RCS session ID for this file transfer. The ID is implementation dependent but + * should be unique. + */ + String SESSION_ID_COLUMN = "session_id"; + + /** + * The URI that points to the content of this file transfer + */ + String CONTENT_URI_COLUMN = "content_uri"; + + /** + * The file type of this file transfer in bytes. The validity of types is not enforced + * in {@link android.telephony.ims.RcsMessageStore} APIs. + */ + String CONTENT_TYPE_COLUMN = "content_type"; + + /** + * The size of the file transfer in bytes. + */ + String FILE_SIZE_COLUMN = "file_size"; + + /** + * Number of bytes that was successfully transmitted for this file transfer + */ + String SUCCESSFULLY_TRANSFERRED_BYTES = "transfer_offset"; + + /** + * The status of this file transfer + * @see android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus + */ + String TRANSFER_STATUS_COLUMN = "transfer_status"; + + /** + * The on-screen width of the file transfer, if it contains multi-media + */ + String WIDTH_COLUMN = "width"; + + /** + * The on-screen height of the file transfer, if it contains multi-media + */ + String HEIGHT_COLUMN = "height"; + + /** + * The duration of the content in milliseconds if this file transfer contains + * multi-media + */ + String DURATION_MILLIS_COLUMN = "duration"; + + /** + * The URI to the preview of the content of this file transfer + */ + String PREVIEW_URI_COLUMN = "preview_uri"; + + /** + * The type of the preview of the content of this file transfer. The validity of types + * is not enforced in {@link android.telephony.ims.RcsMessageStore} APIs. + */ + String PREVIEW_TYPE_COLUMN = "preview_type"; + } + + /** + * The table that holds the information for + * {@link android.telephony.ims.RcsGroupThreadEvent} and its subclasses. + */ + interface RcsThreadEventColumns { + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to participant joined events (example URI: + * {@code content://rcs/group_thread/3/participant_joined_event}) + */ + String PARTICIPANT_JOINED_URI_PART = "participant_joined_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to participant left events. (example URI: + * {@code content://rcs/group_thread/3/participant_left_event/4}) + */ + String PARTICIPANT_LEFT_URI_PART = "participant_left_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to name changed events. (example URI: + * {@code content://rcs/group_thread/3/name_changed_event}) + */ + String NAME_CHANGED_URI_PART = "name_changed_event"; + + /** + * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to + * refer to icon changed events. (example URI: + * {@code content://rcs/group_thread/3/icon_changed_event}) + */ + String ICON_CHANGED_URI_PART = "icon_changed_event"; + + /** + * The unique ID of this event in the database, i.e. the primary key + */ + String EVENT_ID_COLUMN = "event_id"; + + /** + * The type of this event + * + * @see RcsEventTypes + */ + String EVENT_TYPE_COLUMN = "event_type"; + + /** + * The timestamp in milliseconds of when this event happened + */ + String TIMESTAMP_COLUMN = "origination_timestamp"; + + /** + * The participant that generated this event + */ + String SOURCE_PARTICIPANT_ID_COLUMN = "source_participant"; + + /** + * The receiving participant of this event if this was an + * {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} or + * {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} + */ + String DESTINATION_PARTICIPANT_ID_COLUMN = "destination_participant"; + + /** + * The URI for the new icon of the group thread if this was an + * {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} + */ + String NEW_ICON_URI_COLUMN = "new_icon_uri"; + + /** + * The URI for the new name of the group thread if this was an + * {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} + */ + String NEW_NAME_COLUMN = "new_name"; + } + + /** + * The table that {@link android.telephony.ims.RcsParticipantAliasChangedEvent} gets + * persisted to + */ + interface RcsParticipantEventColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsParticipantAliasChangedEvent}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String ALIAS_CHANGE_EVENT_URI_PART = "alias_change_event"; + + /** + * The new alias of the participant + */ + String NEW_ALIAS_COLUMN = "new_alias"; + } + + /** + * These values are used in {@link com.android.providers.telephony.RcsProvider} to determine + * what kind of event is present in the storage. + */ + interface RcsEventTypes { + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsParticipantAliasChangedEvent} + */ + int PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE = 1; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} + */ + int PARTICIPANT_JOINED_EVENT_TYPE = 2; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent} + */ + int PARTICIPANT_LEFT_EVENT_TYPE = 4; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadIconChangedEvent} + */ + int ICON_CHANGED_EVENT_TYPE = 8; + + /** + * Integer constant that is stored in the + * {@link com.android.providers.telephony.RcsProvider} database that denotes the event + * is of type {@link android.telephony.ims.RcsGroupThreadNameChangedEvent} + */ + int NAME_CHANGED_EVENT_TYPE = 16; + } + + /** + * The view that allows unified querying across all events + */ + interface RcsUnifiedEventHelper extends RcsParticipantEventColumns, RcsThreadEventColumns { + /** + * The path that should be used for referring to + * {@link android.telephony.ims.RcsEvent}s in + * {@link com.android.providers.telephony.RcsProvider} URIs. + */ + String RCS_EVENT_QUERY_URI_PATH = "event"; + + /** + * The URI to query {@link android.telephony.ims.RcsEvent}s via the content provider. + */ + Uri RCS_EVENT_QUERY_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, + RCS_EVENT_QUERY_URI_PATH); + } + } + + /** * Contains all MMS messages. */ public static final class Mms implements BaseMmsColumns { diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 739589412f0b..eab536f94290 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1326,13 +1326,15 @@ public class TelephonyManager { "android.intent.action.DATA_STALL_DETECTED"; /** - * A service action that identifies a {@link android.app.SmsAppService} subclass in the + * A service action that identifies + * a {@link android.service.carrier.CarrierMessagingClientService} subclass in the * AndroidManifest.xml. * - * <p>See {@link android.app.SmsAppService} for the details. + * <p>See {@link android.service.carrier.CarrierMessagingClientService} for the details. */ @SdkConstant(SdkConstantType.SERVICE_ACTION) - public static final String ACTION_SMS_APP_SERVICE = "android.telephony.action.SMS_APP_SERVICE"; + public static final String ACTION_CARRIER_MESSAGING_CLIENT_SERVICE = + "android.telephony.action.CARRIER_MESSAGING_CLIENT_SERVICE"; /** * An int extra used with {@link #ACTION_DATA_STALL_DETECTED} to indicate the diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.java b/telephony/java/android/telephony/ims/Rcs1To1Thread.java index 709b3aa0f804..cc28ee0758c2 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java +++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java @@ -15,42 +15,72 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.WorkerThread; /** * Rcs1To1Thread represents a single RCS conversation thread with a total of two - * {@link RcsParticipant}s. - * @hide - TODO(sahinc) make this public + * {@link RcsParticipant}s. Please see Section 5 (1-to-1 Messaging) - GSMA RCC.71 (RCS Universal + * Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public */ public class Rcs1To1Thread extends RcsThread { + private int mThreadId; + + /** + * Public constructor only for RcsMessageStoreController to initialize new threads. + * + * @hide + */ public Rcs1To1Thread(int threadId) { super(threadId); + mThreadId = threadId; } - public static final Creator<Rcs1To1Thread> CREATOR = new Creator<Rcs1To1Thread>() { - @Override - public Rcs1To1Thread createFromParcel(Parcel in) { - return new Rcs1To1Thread(in); - } - - @Override - public Rcs1To1Thread[] newArray(int size) { - return new Rcs1To1Thread[size]; - } - }; + /** + * @return Returns {@code false} as this is always a 1 to 1 thread. + */ + @Override + public boolean isGroup() { + return false; + } - protected Rcs1To1Thread(Parcel in) { - super(in); + /** + * {@link Rcs1To1Thread}s can fall back to SMS as a back-up protocol. This function returns the + * thread id to be used to query {@code content://mms-sms/conversation/#} to get the fallback + * thread. + * + * @return The thread id to be used to query the mms-sms authority + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getFallbackThreadId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadFallbackThreadId(mThreadId)); } - @Override - public int describeContents() { - return 0; + /** + * If the RCS client allows falling back to SMS, it needs to create an MMS-SMS thread in the + * SMS/MMS Provider( see {@link android.provider.Telephony.MmsSms#CONTENT_CONVERSATIONS_URI}. + * Use this function to link the {@link Rcs1To1Thread} to the MMS-SMS thread. This function + * also updates the storage. + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFallbackThreadId(long fallbackThreadId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.set1To1ThreadFallbackThreadId(mThreadId, fallbackThreadId)); } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(RCS_1_TO_1_TYPE); - super.writeToParcel(dest, flags); + /** + * @return Returns the {@link RcsParticipant} that receives the messages sent in this thread. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @NonNull + @WorkerThread + public RcsParticipant getRecipient() throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadOtherParticipantId(mThreadId))); } } diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java new file mode 100644 index 000000000000..5512c4c7b19d --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsControllerCall.java @@ -0,0 +1,64 @@ +/* + * 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.telephony.ims; + +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.telephony.ims.aidl.IRcs; + +/** + * A wrapper class around RPC calls that {@link RcsMessageStore} APIs to minimize boilerplate code. + * + * @hide - not meant for public use + */ +class RcsControllerCall { + static <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException { + IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE)); + if (iRcs == null) { + throw new RcsMessageStoreException("Could not connect to RCS storage service"); + } + + try { + return serviceCall.methodOnIRcs(iRcs); + } catch (RemoteException exception) { + throw new RcsMessageStoreException(exception.getMessage()); + } + } + + static void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall) + throws RcsMessageStoreException { + IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE)); + if (iRcs == null) { + throw new RcsMessageStoreException("Could not connect to RCS storage service"); + } + + try { + serviceCall.methodOnIRcs(iRcs); + } catch (RemoteException exception) { + throw new RcsMessageStoreException(exception.getMessage()); + } + } + + interface RcsServiceCall<R> { + R methodOnIRcs(IRcs iRcs) throws RemoteException; + } + + interface RcsServiceCallWithNoReturn { + void methodOnIRcs(IRcs iRcs) throws RemoteException; + } +} diff --git a/telephony/java/android/telephony/ims/RcsPart.aidl b/telephony/java/android/telephony/ims/RcsEvent.aidl index 8b8077d57676..08974e0a771c 100644 --- a/telephony/java/android/telephony/ims/RcsPart.aidl +++ b/telephony/java/android/telephony/ims/RcsEvent.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsPart; +parcelable RcsEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsEvent.java index 931e93dc8bf1..744ac76a7828 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java +++ b/telephony/java/android/telephony/ims/RcsEvent.java @@ -13,40 +13,48 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.telephony.ims; import android.os.Parcel; import android.os.Parcelable; /** - * A continuation token to provide for {@link RcsMessageStore#getRcsThreads}. Use this token to - * break large queries into manageable chunks - * @hide - TODO make this public + * The base class for events that can happen on {@link RcsParticipant}s and {@link RcsThread}s. + * @hide - TODO(109759350) make this public */ -public class RcsThreadQueryContinuationToken implements Parcelable { - protected RcsThreadQueryContinuationToken(Parcel in) { +public abstract class RcsEvent implements Parcelable { + protected long mTimestamp; + + protected RcsEvent(long timestamp) { + mTimestamp = timestamp; } - public static final Creator<RcsThreadQueryContinuationToken> CREATOR = - new Creator<RcsThreadQueryContinuationToken>() { - @Override - public RcsThreadQueryContinuationToken createFromParcel(Parcel in) { - return new RcsThreadQueryContinuationToken(in); - } + /** + * @return Returns the time of when this event happened. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getTimestamp() { + return mTimestamp; + } - @Override - public RcsThreadQueryContinuationToken[] newArray(int size) { - return new RcsThreadQueryContinuationToken[size]; - } - }; + /** + * Persists the event to the data store + * + * @hide + */ + abstract void persist() throws RcsMessageStoreException; - @Override - public int describeContents() { - return 0; + RcsEvent(Parcel in) { + mTimestamp = in.readLong(); } @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(mTimestamp); + } + + @Override + public int describeContents() { + return 0; } } diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.aidl b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl index c4ce5299e512..9a3600bbae90 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsGroupThread; +parcelable RcsEventQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsEventQueryParameters.java b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java new file mode 100644 index 000000000000..6aee56f56f45 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java @@ -0,0 +1,322 @@ +/* + * 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.telephony.ims; + +import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE; +import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} in order to select a + * subset of {@link RcsEvent}s present in the message store. + * + * @hide TODO - make the Builder and builder() public. The rest should stay internal only. + */ +public class RcsEventQueryParameters implements Parcelable { + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return all types of + * {@link RcsEvent}s + */ + public static final int ALL_EVENTS = -1; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return sub-types of + * {@link RcsGroupThreadEvent}s + */ + public static final int ALL_GROUP_THREAD_EVENTS = 0; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only + * {@link RcsParticipantAliasChangedEvent}s + */ + public static final int PARTICIPANT_ALIAS_CHANGED_EVENT = + PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only + * {@link RcsGroupThreadParticipantJoinedEvent}s + */ + public static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT = + PARTICIPANT_JOINED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only + * {@link RcsGroupThreadParticipantLeftEvent}s + */ + public static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT = + PARTICIPANT_LEFT_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only + * {@link RcsGroupThreadNameChangedEvent}s + */ + public static final int GROUP_THREAD_NAME_CHANGED_EVENT = NAME_CHANGED_EVENT_TYPE; + + /** + * Flag to be used with {@link Builder#setEventType(int)} to make + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only + * {@link RcsGroupThreadIconChangedEvent}s + */ + public static final int GROUP_THREAD_ICON_CHANGED_EVENT = ICON_CHANGED_EVENT_TYPE; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ALL_EVENTS, ALL_GROUP_THREAD_EVENTS, PARTICIPANT_ALIAS_CHANGED_EVENT, + GROUP_THREAD_PARTICIPANT_JOINED_EVENT, GROUP_THREAD_PARTICIPANT_LEFT_EVENT, + GROUP_THREAD_NAME_CHANGED_EVENT, GROUP_THREAD_ICON_CHANGED_EVENT}) + public @interface EventType { + } + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted + * in the order of creation for faster query results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted + * with respect to {@link RcsEvent#getTimestamp()} + */ + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { + } + + /** + * The key to pass into a Bundle, for usage in RcsProvider.query(Bundle) + * @hide - not meant for public use + */ + public static final String EVENT_QUERY_PARAMETERS_KEY = "event_query_parameters"; + + // Which types of events the results should be limited to + private @EventType int mEventType; + // The property which the results should be sorted against + private int mSortingProperty; + // Whether the results should be sorted in ascending order + private boolean mIsAscending; + // The number of results that should be returned with this query + private int mLimit; + // The thread that the results are limited to + private int mThreadId; + + RcsEventQueryParameters(@EventType int eventType, int threadId, + @SortingProperty int sortingProperty, boolean isAscending, int limit) { + mEventType = eventType; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + mThreadId = threadId; + } + + /** + * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParameters} is + * set to query for. + */ + public @EventType int getEventType() { + return mEventType; + } + + /** + * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParameters} is + * set to query for. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property where the results should be sorted against. + * @see SortingProperty + */ + public int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * @return Returns the ID of the {@link RcsGroupThread} that the results are limited to. As this + * API exposes an ID, it should stay hidden. + * + * @hide + */ + public int getThreadId() { + return mThreadId; + } + + /** + * A helper class to build the {@link RcsEventQueryParameters}. + */ + public static class Builder { + private @EventType int mEventType; + private @SortingProperty int mSortingProperty; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId; + + /** + * Creates a new builder for {@link RcsEventQueryParameters} to be used in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} + */ + public Builder() { + // empty implementation + } + + /** + * Desired number of events to be returned from the query. Passing in 0 will return all + * existing events at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the type of events to be returned from the query. + * + * @param eventType The type of event to be returned. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setEventType(@EventType int eventType) { + mEventType = eventType; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsEventQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Limits the results to the given {@link RcsGroupThread}. Setting this value prevents + * returning any instances of {@link RcsParticipantAliasChangedEvent}. + * + * @param groupThread The thread to limit the results to. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setGroupThread(@NonNull RcsGroupThread groupThread) { + mThreadId = groupThread.getThreadId(); + return this; + } + + /** + * Builds the {@link RcsEventQueryParameters} to use in + * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} + * + * @return An instance of {@link RcsEventQueryParameters} to use with the event query. + */ + public RcsEventQueryParameters build() { + return new RcsEventQueryParameters(mEventType, mThreadId, mSortingProperty, + mIsAscending, mLimit); + } + } + + protected RcsEventQueryParameters(Parcel in) { + mEventType = in.readInt(); + mThreadId = in.readInt(); + mSortingProperty = in.readInt(); + mIsAscending = in.readBoolean(); + mLimit = in.readInt(); + } + + public static final Creator<RcsEventQueryParameters> CREATOR = + new Creator<RcsEventQueryParameters>() { + @Override + public RcsEventQueryParameters createFromParcel(Parcel in) { + return new RcsEventQueryParameters(in); + } + + @Override + public RcsEventQueryParameters[] newArray(int size) { + return new RcsEventQueryParameters[size]; + } + }; + + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mEventType); + dest.writeInt(mThreadId); + dest.writeInt(mSortingProperty); + dest.writeBoolean(mIsAscending); + dest.writeInt(mLimit); + } +} diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl index 6552a82c9072..7d133350973c 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsIncomingMessage; +parcelable RcsEventQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsEventQueryResult.java b/telephony/java/android/telephony/ims/RcsEventQueryResult.java new file mode 100644 index 000000000000..27898ab0d9a2 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.java @@ -0,0 +1,90 @@ +/* + * 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.telephony.ims; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} + * call. This class allows getting the token for querying the next batch of events in order to + * prevent handling large amounts of data at once. + * + * @hide + */ +public class RcsEventQueryResult implements Parcelable { + private RcsQueryContinuationToken mContinuationToken; + private List<RcsEvent> mEvents; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsEventQueryResult( + RcsQueryContinuationToken continuationToken, + List<RcsEvent> events) { + mContinuationToken = continuationToken; + mEvents = events; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsEvent}s. + */ + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsEvent}s in the current query result. Call {@link + * RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)} to get the next batch + * of {@link RcsEvent}s. + */ + public List<RcsEvent> getEvents() { + return mEvents; + } + + protected RcsEventQueryResult(Parcel in) { + } + + public static final Creator<RcsEventQueryResult> CREATOR = new Creator<RcsEventQueryResult>() { + @Override + public RcsEventQueryResult createFromParcel(Parcel in) { + return new RcsEventQueryResult(in); + } + + @Override + public RcsEventQueryResult[] newArray(int size) { + return new RcsEventQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl new file mode 100644 index 000000000000..5fec021525af --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsFileTransferCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java new file mode 100644 index 000000000000..bd7cb4bc90bc --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java @@ -0,0 +1,360 @@ +/* + * 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.telephony.ims; + +import android.annotation.CheckResult; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Pass an instance of this class to + * {@link RcsMessage#insertFileTransfer(RcsFileTransferCreationParameters)} create an + * {@link RcsFileTransferPart} and save it into storage. + * + * @hide - TODO(109759350) make this public + */ +public class RcsFileTransferCreationParameters implements Parcelable { + private String mRcsFileTransferSessionId; + private Uri mContentUri; + private String mContentMimeType; + private long mFileSize; + private long mTransferOffset; + private int mWidth; + private int mHeight; + private long mMediaDuration; + private Uri mPreviewUri; + private String mPreviewMimeType; + private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus; + + /** + * @return Returns the globally unique RCS file transfer session ID for the + * {@link RcsFileTransferPart} to be created + */ + public String getRcsFileTransferSessionId() { + return mRcsFileTransferSessionId; + } + + /** + * @return Returns the URI for the content of the {@link RcsFileTransferPart} to be created + */ + public Uri getContentUri() { + return mContentUri; + } + + /** + * @return Returns the MIME type for the content of the {@link RcsFileTransferPart} to be + * created + */ + public String getContentMimeType() { + return mContentMimeType; + } + + /** + * @return Returns the file size in bytes for the {@link RcsFileTransferPart} to be created + */ + public long getFileSize() { + return mFileSize; + } + + /** + * @return Returns the transfer offset for the {@link RcsFileTransferPart} to be created. The + * file transfer offset is defined as how many bytes have been successfully transferred to the + * receiver of this file transfer. + */ + public long getTransferOffset() { + return mTransferOffset; + } + + /** + * @return Returns the width of the {@link RcsFileTransferPart} to be created. The value is in + * pixels. + */ + public int getWidth() { + return mWidth; + } + + /** + * @return Returns the height of the {@link RcsFileTransferPart} to be created. The value is in + * pixels. + */ + public int getHeight() { + return mHeight; + } + + /** + * @return Returns the duration of the {@link RcsFileTransferPart} to be created. + */ + public long getMediaDuration() { + return mMediaDuration; + } + + /** + * @return Returns the URI of the preview of the content of the {@link RcsFileTransferPart} to + * be created. This should only be used for multi-media files. + */ + public Uri getPreviewUri() { + return mPreviewUri; + } + + /** + * @return Returns the MIME type of the preview of the content of the + * {@link RcsFileTransferPart} to be created. This should only be used for multi-media files. + */ + public String getPreviewMimeType() { + return mPreviewMimeType; + } + + /** + * @return Returns the status of the {@link RcsFileTransferPart} to be created. + */ + public @RcsFileTransferPart.RcsFileTransferStatus int getFileTransferStatus() { + return mFileTransferStatus; + } + + /** + * @hide + */ + RcsFileTransferCreationParameters(Builder builder) { + mRcsFileTransferSessionId = builder.mRcsFileTransferSessionId; + mContentUri = builder.mContentUri; + mContentMimeType = builder.mContentMimeType; + mFileSize = builder.mFileSize; + mTransferOffset = builder.mTransferOffset; + mWidth = builder.mWidth; + mHeight = builder.mHeight; + mMediaDuration = builder.mLength; + mPreviewUri = builder.mPreviewUri; + mPreviewMimeType = builder.mPreviewMimeType; + mFileTransferStatus = builder.mFileTransferStatus; + } + + /** + * A builder to create instances of {@link RcsFileTransferCreationParameters} + */ + public class Builder { + private String mRcsFileTransferSessionId; + private Uri mContentUri; + private String mContentMimeType; + private long mFileSize; + private long mTransferOffset; + private int mWidth; + private int mHeight; + private long mLength; + private Uri mPreviewUri; + private String mPreviewMimeType; + private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus; + + /** + * Sets the globally unique RCS file transfer session ID for the {@link RcsFileTransferPart} + * to be created + * + * @param sessionId The RCS file transfer session ID + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileTransferSessionId(String sessionId) { + mRcsFileTransferSessionId = sessionId; + return this; + } + + /** + * Sets the URI for the content of the {@link RcsFileTransferPart} to be created + * + * @param contentUri The URI for the file + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setContentUri(Uri contentUri) { + mContentUri = contentUri; + return this; + } + + /** + * Sets the MIME type for the content of the {@link RcsFileTransferPart} to be created + * + * @param contentType The MIME type of the file + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setContentMimeType(String contentType) { + mContentMimeType = contentType; + return this; + } + + /** + * Sets the file size for the {@link RcsFileTransferPart} to be created + * + * @param size The size of the file in bytes + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileSize(long size) { + mFileSize = size; + return this; + } + + /** + * Sets the transfer offset for the {@link RcsFileTransferPart} to be created. The file + * transfer offset is defined as how many bytes have been successfully transferred to the + * receiver of this file transfer. + * + * @param offset The transfer offset in bytes + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setTransferOffset(long offset) { + mTransferOffset = offset; + return this; + } + + /** + * Sets the width of the {@link RcsFileTransferPart} to be created. This should only be used + * for multi-media files. + * + * @param width The width of the multi-media file in pixels. + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setWidth(int width) { + mWidth = width; + return this; + } + + /** + * Sets the height of the {@link RcsFileTransferPart} to be created. This should only be + * used for multi-media files. + * + * @param height The height of the multi-media file in pixels. + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setHeight(int height) { + mHeight = height; + return this; + } + + /** + * Sets the length of the {@link RcsFileTransferPart} to be created. This should only be + * used for multi-media files such as audio or video. + * + * @param length The length of the multi-media file in milliseconds + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setMediaDuration(long length) { + mLength = length; + return this; + } + + /** + * Sets the URI of the preview of the content of the {@link RcsFileTransferPart} to be + * created. This should only be used for multi-media files. + * + * @param previewUri The URI of the preview of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setPreviewUri(Uri previewUri) { + mPreviewUri = previewUri; + return this; + } + + /** + * Sets the MIME type of the preview of the content of the {@link RcsFileTransferPart} to + * be created. This should only be used for multi-media files. + * + * @param previewType The MIME type of the preview of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setPreviewMimeType(String previewType) { + mPreviewMimeType = previewType; + return this; + } + + /** + * Sets the status of the {@link RcsFileTransferPart} to be created. + * + * @param status The status of the file transfer + * @return The same instance of {@link Builder} to chain methods + */ + @CheckResult + public Builder setFileTransferStatus( + @RcsFileTransferPart.RcsFileTransferStatus int status) { + mFileTransferStatus = status; + return this; + } + + /** + * Creates an instance of {@link RcsFileTransferCreationParameters} with the given + * parameters. + * + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#insertFileTransfer(RcsFileTransferCreationParameters) + */ + public RcsFileTransferCreationParameters build() { + return new RcsFileTransferCreationParameters(this); + } + } + + protected RcsFileTransferCreationParameters(Parcel in) { + mRcsFileTransferSessionId = in.readString(); + mContentUri = in.readParcelable(Uri.class.getClassLoader()); + mContentMimeType = in.readString(); + mFileSize = in.readLong(); + mTransferOffset = in.readLong(); + mWidth = in.readInt(); + mHeight = in.readInt(); + mMediaDuration = in.readLong(); + mPreviewUri = in.readParcelable(Uri.class.getClassLoader()); + mPreviewMimeType = in.readString(); + mFileTransferStatus = in.readInt(); + } + + public static final Creator<RcsFileTransferCreationParameters> CREATOR = + new Creator<RcsFileTransferCreationParameters>() { + @Override + public RcsFileTransferCreationParameters createFromParcel(Parcel in) { + return new RcsFileTransferCreationParameters(in); + } + + @Override + public RcsFileTransferCreationParameters[] newArray(int size) { + return new RcsFileTransferCreationParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mRcsFileTransferSessionId); + dest.writeParcelable(mContentUri, flags); + dest.writeString(mContentMimeType); + dest.writeLong(mFileSize); + dest.writeLong(mTransferOffset); + dest.writeInt(mWidth); + dest.writeInt(mHeight); + dest.writeLong(mMediaDuration); + dest.writeParcelable(mPreviewUri, flags); + dest.writeString(mPreviewMimeType); + dest.writeInt(mFileTransferStatus); + } +} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.java b/telephony/java/android/telephony/ims/RcsFileTransferPart.java index 39c58dd9c15b..2eadc4a1724e 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.java +++ b/telephony/java/android/telephony/ims/RcsFileTransferPart.java @@ -15,34 +15,346 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.net.Uri; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; /** - * A part of a composite {@link RcsMessage} that holds a file transfer. - * @hide - TODO(sahinc) make this public + * A part of a composite {@link RcsMessage} that holds a file transfer. Please see Section 7 + * (File Transfer) - GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public */ -public class RcsFileTransferPart extends RcsPart { - public static final Creator<RcsFileTransferPart> CREATOR = new Creator<RcsFileTransferPart>() { - @Override - public RcsFileTransferPart createFromParcel(Parcel in) { - return new RcsFileTransferPart(in); - } +public class RcsFileTransferPart { + /** + * The status to indicate that this {@link RcsFileTransferPart} is not set yet. + */ + public static final int NOT_SET = 0; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is a draft and is not in the + * process of sending yet. + */ + public static final int DRAFT = 1; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is actively being sent right + * now. + */ + public static final int SENDING = 2; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was being sent, but the user has + * paused the sending process. + */ + public static final int SENDING_PAUSED = 3; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to + * send. + */ + public static final int SENDING_FAILED = 4; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to + * send. + */ + public static final int SENDING_CANCELLED = 5; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is actively being downloaded + * right now. + */ + public static final int DOWNLOADING = 6; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was being downloaded, but the + * user paused the downloading process. + */ + public static final int DOWNLOADING_PAUSED = 7; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to + * download. + */ + public static final int DOWNLOADING_FAILED = 8; + + /** + * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to + * download. + */ + public static final int DOWNLOADING_CANCELLED = 9; + + /** + * The status to indicate that this {@link RcsFileTransferPart} was successfully sent or + * received. + */ + public static final int SUCCEEDED = 10; + + @IntDef({ + DRAFT, SENDING, SENDING_PAUSED, SENDING_FAILED, SENDING_CANCELLED, DOWNLOADING, + DOWNLOADING_PAUSED, DOWNLOADING_FAILED, DOWNLOADING_CANCELLED, SUCCEEDED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RcsFileTransferStatus { + } + + private int mId; + + /** + * @hide + */ + RcsFileTransferPart(int id) { + mId = id; + } + + /** + * @hide + */ + public void setId(int id) { + mId = id; + } + + /** + * @hide + */ + public int getId() { + return mId; + } + + /** + * Sets the RCS file transfer session ID for this file transfer and persists into storage. + * + * @param sessionId The session ID to be used for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileTransferSessionId(String sessionId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferSessionId(mId, sessionId)); + } + + /** + * @return Returns the file transfer session ID. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getFileTransferSessionId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferSessionId(mId)); + } + + /** + * Sets the content URI for this file transfer and persists into storage. The file transfer + * should be reachable using this URI. + * + * @param contentUri The URI for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setContentUri(Uri contentUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferContentUri(mId, contentUri)); + } + + /** + * @return Returns the URI for this file transfer + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public Uri getContentUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentUri(mId)); + } - @Override - public RcsFileTransferPart[] newArray(int size) { - return new RcsFileTransferPart[size]; - } - }; + /** + * Sets the MIME type of this file transfer and persists into storage. Whether this type + * actually matches any known or supported types is not checked. + * + * @param contentMimeType The type of this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setContentMimeType(String contentMimeType) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferContentType(mId, contentMimeType)); + } + + /** + * @return Returns the content type of this file transfer + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + @Nullable + public String getContentMimeType() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentType(mId)); + } + + /** + * Sets the content length (i.e. file size) for this file transfer and persists into storage. + * + * @param contentLength The content length of this file transfer + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileSize(long contentLength) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferFileSize(mId, contentLength)); + } + + /** + * @return Returns the content length (i.e. file size) for this file transfer. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getFileSize() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferFileSize(mId)); + } + + /** + * Sets the transfer offset for this file transfer and persists into storage. The file transfer + * offset is defined as how many bytes have been successfully transferred to the receiver of + * this file transfer. + * + * @param transferOffset The transfer offset for this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setTransferOffset(long transferOffset) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferTransferOffset(mId, transferOffset)); + } + + /** + * @return Returns the number of bytes that have successfully transferred. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getTransferOffset() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferTransferOffset(mId)); + } + + /** + * Sets the status for this file transfer and persists into storage. + * + * @param status The status of this file transfer. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setFileTransferStatus(@RcsFileTransferStatus int status) + throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferStatus(mId, status)); + } + + /** + * @return Returns the status of this file transfer. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsFileTransferStatus int getFileTransferStatus() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferStatus(mId)); + } + + /** + * @return Returns the width of this multi-media message part in pixels. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public int getWidth() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferWidth(mId)); + } + + /** + * Sets the width of this RCS multi-media message part and persists into storage. + * + * @param width The width value in pixels + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setWidth(int width) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferWidth(mId, width)); + } + + /** + * @return Returns the height of this multi-media message part in pixels. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public int getHeight() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferHeight(mId)); + } + + /** + * Sets the height of this RCS multi-media message part and persists into storage. + * + * @param height The height value in pixels + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setHeight(int height) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferHeight(mId, height)); + } + + /** + * @return Returns the length of this multi-media file (e.g. video or audio) in milliseconds. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getLength() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferLength(mId)); + } + + /** + * Sets the length of this multi-media file (e.g. video or audio) and persists into storage. + * + * @param length The length of the file in milliseconds. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLength(long length) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferLength(mId, length)); + } + + /** + * @return Returns the URI for the preview of this multi-media file (e.g. an image thumbnail for + * a video) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public Uri getPreviewUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewUri(mId)); + } - protected RcsFileTransferPart(Parcel in) { + /** + * Sets the URI for the preview of this multi-media file and persists into storage. + * + * @param previewUri The URI to access to the preview file. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setPreviewUri(Uri previewUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferPreviewUri(mId, previewUri)); } - @Override - public int describeContents() { - return 0; + /** + * @return Returns the MIME type of this multi-media file's preview. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getPreviewMimeType() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewType(mId)); } - @Override - public void writeToParcel(Parcel dest, int flags) { + /** + * Sets the MIME type for this multi-media file's preview and persists into storage. + * + * @param previewMimeType The MIME type for the preview + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setPreviewMimeType(String previewMimeType) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setFileTransferPreviewType(mId, previewMimeType)); } } diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java index d954b2d70ac3..6f6258e3d894 100644 --- a/telephony/java/android/telephony/ims/RcsGroupThread.java +++ b/telephony/java/android/telephony/ims/RcsGroupThread.java @@ -15,38 +15,191 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.net.Uri; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; /** * RcsGroupThread represents a single RCS conversation thread where {@link RcsParticipant}s can join - * or leave. + * or leave. Please see Section 6 (Group Chat) - GSMA RCC.71 (RCS Universal Profile Service + * Definition Document) + * * @hide - TODO(sahinc) make this public */ public class RcsGroupThread extends RcsThread { - public static final Creator<RcsGroupThread> CREATOR = new Creator<RcsGroupThread>() { - @Override - public RcsGroupThread createFromParcel(Parcel in) { - return new RcsGroupThread(in); + /** + * Public constructor only for RcsMessageStoreController to initialize new threads. + * + * @hide + */ + public RcsGroupThread(int threadId) { + super(threadId); + } + + /** + * @return Returns {@code true} as this is always a group thread + */ + @Override + public boolean isGroup() { + return true; + } + + /** + * @return Returns the given name of this {@link RcsGroupThread}. Please see US6-2 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public String getGroupName() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadName(mThreadId)); + } + + /** + * Sets the name of this {@link RcsGroupThread} and saves it into storage. Please see US6-2 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setGroupName(String groupName) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadName(mThreadId, groupName)); + } + + /** + * @return Returns a URI that points to the group's icon {@link RcsGroupThread}. Please see + * US6-2 - GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + public Uri getGroupIcon() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadIcon(mThreadId)); + } + + /** + * Sets the icon for this {@link RcsGroupThread} and saves it into storage. Please see US6-2 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setGroupIcon(@Nullable Uri groupIcon) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadIcon(mThreadId, groupIcon)); + } + + /** + * @return Returns the owner of this thread or {@code null} if there doesn't exist an owner + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public RcsParticipant getOwner() throws RcsMessageStoreException { + return new RcsParticipant(RcsControllerCall.call( + iRcs -> iRcs.getGroupThreadOwner(mThreadId))); + } + + /** + * Sets the owner of this {@link RcsGroupThread} and saves it into storage. This is intended to + * be used for selecting a new owner for a group thread if the owner leaves the thread. The + * owner needs to be in the list of existing participants. + * + * @param participant The new owner of the thread. {@code null} values are allowed. + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void setOwner(@Nullable RcsParticipant participant) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGroupThreadOwner(mThreadId, participant.getId())); + } + + /** + * Adds a new {@link RcsParticipant} to this group thread and persists into storage. If the user + * is actively participating in this {@link RcsGroupThread}, an {@link RcsParticipant} on behalf + * of them should be added. + * + * @param participant The new participant to be added to the thread. + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void addParticipant(@NonNull RcsParticipant participant) + throws RcsMessageStoreException { + if (participant == null) { + return; } - @Override - public RcsGroupThread[] newArray(int size) { - return new RcsGroupThread[size]; + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.addParticipantToGroupThread(mThreadId, participant.getId())); + } + + /** + * Removes an {@link RcsParticipant} from this group thread and persists into storage. If the + * removed participant was the owner of this group, the owner will become null. + * + * @throws RcsMessageStoreException if the operation could not be persisted into storage + */ + @WorkerThread + public void removeParticipant(@NonNull RcsParticipant participant) + throws RcsMessageStoreException { + if (participant == null) { + return; } - }; - protected RcsGroupThread(Parcel in) { - super(in); + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.removeParticipantFromGroupThread(mThreadId, participant.getId())); } - @Override - public int describeContents() { - return 0; + /** + * Returns the set of {@link RcsParticipant}s that contribute to this group thread. The + * returned set does not support modifications, please use + * {@link RcsGroupThread#addParticipant(RcsParticipant)} + * and {@link RcsGroupThread#removeParticipant(RcsParticipant)} instead. + * + * @return the immutable set of {@link RcsParticipant} in this group thread. + * @throws RcsMessageStoreException if the values could not be read from the storage + */ + @WorkerThread + @NonNull + public Set<RcsParticipant> getParticipants() throws RcsMessageStoreException { + RcsParticipantQueryParameters queryParameters = + new RcsParticipantQueryParameters.Builder().setThread(this).build(); + + RcsParticipantQueryResult queryResult = RcsControllerCall.call( + iRcs -> iRcs.getParticipants(queryParameters)); + + List<RcsParticipant> participantList = queryResult.getParticipants(); + Set<RcsParticipant> participantSet = new LinkedHashSet<>(participantList); + return Collections.unmodifiableSet(participantSet); } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(RCS_GROUP_TYPE); - super.writeToParcel(dest, flags); + /** + * Returns the conference URI for this {@link RcsGroupThread}. Please see 4.4.5.2 - GSMA RCC.53 + * (RCS Device API 1.6 Specification + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @Nullable + @WorkerThread + public Uri getConferenceUri() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadConferenceUri(mThreadId)); + } + + /** + * Sets the conference URI for this {@link RcsGroupThread} and persists into storage. Please see + * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + * + * @param conferenceUri The URI as String to be used as the conference URI. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @Nullable + @WorkerThread + public void setConferenceUri(Uri conferenceUri) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGroupThreadConferenceUri(mThreadId, conferenceUri)); } } diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl index 9fdc41d2bd5f..77a23722f080 100644 --- a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl +++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl @@ -1,5 +1,4 @@ /* - * * Copyright 2019, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,4 +16,4 @@ package android.telephony.ims; -parcelable Rcs1To1Thread; +parcelable RcsGroupThreadEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java new file mode 100644 index 000000000000..a18437b8366e --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; + +/** + * An event that happened on an {@link RcsGroupThread}. + * + * @hide - TODO(109759350) make this public + */ +public abstract class RcsGroupThreadEvent extends RcsEvent { + private final int mRcsGroupThreadId; + private final int mOriginatingParticipantId; + + RcsGroupThreadEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId) { + super(timestamp); + mRcsGroupThreadId = rcsGroupThreadId; + mOriginatingParticipantId = originatingParticipantId; + } + + /** + * @return Returns the {@link RcsGroupThread} that this event happened on. + */ + @NonNull + public RcsGroupThread getRcsGroupThread() { + return new RcsGroupThread(mRcsGroupThreadId); + } + + /** + * @return Returns the {@link RcsParticipant} that performed the event. + */ + @NonNull + public RcsParticipant getOriginatingParticipant() { + return new RcsParticipant(mOriginatingParticipantId); + } + + RcsGroupThreadEvent(Parcel in) { + super(in); + mRcsGroupThreadId = in.readInt(); + mOriginatingParticipantId = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mRcsGroupThreadId); + dest.writeInt(mOriginatingParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl new file mode 100644 index 000000000000..daea7922f3df --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 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.telephony.ims; + +parcelable RcsGroupThreadIconChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java new file mode 100644 index 000000000000..7beed3ba7ec2 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.Uri; +import android.os.Parcel; + +/** + * An event that indicates an {@link RcsGroupThread}'s icon was changed. Please see R6-2-5 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadIconChangedEvent extends RcsGroupThreadEvent { + private final Uri mNewIcon; + + /** + * Creates a new {@link RcsGroupThreadIconChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that changed the + * {@link RcsGroupThread}'s icon. + * @param newIcon {@link Uri} to the new icon of this {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadIconChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread, + @NonNull RcsParticipant originatingParticipant, @Nullable Uri newIcon) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mNewIcon = newIcon; + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadIconChangedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, @Nullable Uri newIcon) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mNewIcon = newIcon; + } + + /** + * @return Returns the {@link Uri} to the icon of the {@link RcsGroupThread} after this + * {@link RcsGroupThreadIconChangedEvent} occured. + */ + @Nullable + public Uri getNewIcon() { + return mNewIcon; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + // TODO ensure failure throws + RcsControllerCall.call(iRcs -> iRcs.createGroupThreadIconChangedEvent( + getTimestamp(), getRcsGroupThread().getThreadId(), + getOriginatingParticipant().getId(), mNewIcon)); + } + + public static final Creator<RcsGroupThreadIconChangedEvent> CREATOR = + new Creator<RcsGroupThreadIconChangedEvent>() { + @Override + public RcsGroupThreadIconChangedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadIconChangedEvent(in); + } + + @Override + public RcsGroupThreadIconChangedEvent[] newArray(int size) { + return new RcsGroupThreadIconChangedEvent[size]; + } + }; + + protected RcsGroupThreadIconChangedEvent(Parcel in) { + super(in); + mNewIcon = in.readParcelable(Uri.class.getClassLoader()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mNewIcon, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl new file mode 100644 index 000000000000..3ed9bd11dc70 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsGroupThreadNameChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java new file mode 100644 index 000000000000..0d2ea4febfbc --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; + +/** + * An event that indicates an {@link RcsGroupThread}'s name was changed. Please see R6-2-5 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadNameChangedEvent extends RcsGroupThreadEvent { + private String mNewName; + + /** + * Creates a new {@link RcsGroupThreadNameChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that changed the + * {@link RcsGroupThread}'s icon. + * @param newName The new name of the {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadNameChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread, + @NonNull RcsParticipant originatingParticipant, @Nullable String newName) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mNewName = newName; + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadNameChangedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, @Nullable String newName) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mNewName = newName; + } + + /** + * @return Returns the name of this {@link RcsGroupThread} after this + * {@link RcsGroupThreadNameChangedEvent} happened. + */ + @Nullable + public String getNewName() { + return mNewName; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call(iRcs -> iRcs.createGroupThreadNameChangedEvent( + getTimestamp(), getRcsGroupThread().getThreadId(), + getOriginatingParticipant().getId(), mNewName)); + } + + public static final Creator<RcsGroupThreadNameChangedEvent> CREATOR = + new Creator<RcsGroupThreadNameChangedEvent>() { + @Override + public RcsGroupThreadNameChangedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadNameChangedEvent(in); + } + + @Override + public RcsGroupThreadNameChangedEvent[] newArray(int size) { + return new RcsGroupThreadNameChangedEvent[size]; + } + }; + + protected RcsGroupThreadNameChangedEvent(Parcel in) { + super(in); + mNewName = in.readString(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mNewName); + } +} diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl new file mode 100644 index 000000000000..420abffa067a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 2018, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telephony.ims; + +parcelable RcsGroupThreadParticipantJoinedEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java new file mode 100644 index 000000000000..2adafc7e1bb1 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; + +/** + * An event that indicates an RCS participant has joined an {@link RcsThread}. Please see US6-3 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadParticipantJoinedEvent extends RcsGroupThreadEvent { + private int mJoinedParticipantId; + + /** + * Creates a new {@link RcsGroupThreadParticipantJoinedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that added or invited the new + * {@link RcsParticipant} into the {@link RcsGroupThread} + * @param joinedParticipant The new {@link RcsParticipant} that joined the + * {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadParticipantJoinedEvent(long timestamp, + @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant, + @NonNull RcsParticipant joinedParticipant) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mJoinedParticipantId = joinedParticipant.getId(); + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadParticipantJoinedEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, int joinedParticipantId) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mJoinedParticipantId = joinedParticipantId; + } + + /** + * @return Returns the {@link RcsParticipant} that joined the associated {@link RcsGroupThread} + */ + public RcsParticipant getJoinedParticipant() { + return new RcsParticipant(mJoinedParticipantId); + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call( + iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(), + getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(), + getJoinedParticipant().getId())); + } + + public static final Creator<RcsGroupThreadParticipantJoinedEvent> CREATOR = + new Creator<RcsGroupThreadParticipantJoinedEvent>() { + @Override + public RcsGroupThreadParticipantJoinedEvent createFromParcel(Parcel in) { + return new RcsGroupThreadParticipantJoinedEvent(in); + } + + @Override + public RcsGroupThreadParticipantJoinedEvent[] newArray(int size) { + return new RcsGroupThreadParticipantJoinedEvent[size]; + } + }; + + protected RcsGroupThreadParticipantJoinedEvent(Parcel in) { + super(in); + mJoinedParticipantId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mJoinedParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessage.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl index b32cd1208c40..ff139ac0ab1e 100644 --- a/telephony/java/android/telephony/ims/RcsMessage.aidl +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsMessage; +parcelable RcsGroupThreadParticipantLeftEvent; diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java new file mode 100644 index 000000000000..87c1852964ee --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.os.Parcel; + +/** + * An event that indicates an RCS participant has left an {@link RcsThread}. Please see US6-23 - + * GSMA RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public + */ +public class RcsGroupThreadParticipantLeftEvent extends RcsGroupThreadEvent { + private int mLeavingParticipantId; + + /** + * Creates a new {@link RcsGroupThreadParticipantLeftEvent}. his event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on + * @param originatingParticipant The {@link RcsParticipant} that removed the + * {@link RcsParticipant} from the {@link RcsGroupThread}. It is + * possible that originatingParticipant and leavingParticipant are + * the same (i.e. {@link RcsParticipant} left the group + * themselves) + * @param leavingParticipant The {@link RcsParticipant} that left the {@link RcsGroupThread} + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsGroupThreadParticipantLeftEvent(long timestamp, + @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant, + @NonNull RcsParticipant leavingParticipant) { + super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId()); + mLeavingParticipantId = leavingParticipant.getId(); + } + + /** + * @hide - internal constructor for queries + */ + public RcsGroupThreadParticipantLeftEvent(long timestamp, int rcsGroupThreadId, + int originatingParticipantId, int leavingParticipantId) { + super(timestamp, rcsGroupThreadId, originatingParticipantId); + mLeavingParticipantId = leavingParticipantId; + } + + /** + * @return Returns the {@link RcsParticipant} that left the associated {@link RcsGroupThread} + * after this {@link RcsGroupThreadParticipantLeftEvent} happened. + */ + @NonNull + public RcsParticipant getLeavingParticipantId() { + return new RcsParticipant(mLeavingParticipantId); + } + + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call( + iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(), + getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(), + getLeavingParticipantId().getId())); + } + + public static final Creator<RcsGroupThreadParticipantLeftEvent> CREATOR = + new Creator<RcsGroupThreadParticipantLeftEvent>() { + @Override + public RcsGroupThreadParticipantLeftEvent createFromParcel(Parcel in) { + return new RcsGroupThreadParticipantLeftEvent(in); + } + + @Override + public RcsGroupThreadParticipantLeftEvent[] newArray(int size) { + return new RcsGroupThreadParticipantLeftEvent[size]; + } + }; + + protected RcsGroupThreadParticipantLeftEvent(Parcel in) { + super(in); + mLeavingParticipantId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mLeavingParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.java b/telephony/java/android/telephony/ims/RcsIncomingMessage.java index f39e06db068a..2ea115bd675b 100644 --- a/telephony/java/android/telephony/ims/RcsIncomingMessage.java +++ b/telephony/java/android/telephony/ims/RcsIncomingMessage.java @@ -15,34 +15,82 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.WorkerThread; /** * This is a single instance of a message received over RCS. - * @hide - TODO(sahinc) make this public + * + * @hide - TODO(109759350) make this public */ public class RcsIncomingMessage extends RcsMessage { - public static final Creator<RcsIncomingMessage> CREATOR = new Creator<RcsIncomingMessage>() { - @Override - public RcsIncomingMessage createFromParcel(Parcel in) { - return new RcsIncomingMessage(in); - } - - @Override - public RcsIncomingMessage[] newArray(int size) { - return new RcsIncomingMessage[size]; - } - }; - - protected RcsIncomingMessage(Parcel in) { + /** + * @hide + */ + RcsIncomingMessage(int id) { + super(id); } - @Override - public int describeContents() { - return 0; + /** + * Sets the timestamp of arrival for this message and persists into storage. The timestamp is + * defined as milliseconds passed after midnight, January 1, 1970 UTC + * + * @param arrivalTimestamp The timestamp to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setArrivalTimestamp(long arrivalTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageArrivalTimestamp(mId, true, arrivalTimestamp)); + } + + /** + * @return Returns the timestamp of arrival for this message. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getArrivalTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageArrivalTimestamp(mId, true)); + } + + /** + * Sets the timestamp of when the user saw this message and persists into storage. The timestamp + * is defined as milliseconds passed after midnight, January 1, 1970 UTC + * + * @param notifiedTimestamp The timestamp to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setSeenTimestamp(long notifiedTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageSeenTimestamp(mId, true, notifiedTimestamp)); + } + + /** + * @return Returns the timestamp of when the user saw this message. The timestamp is defined as + * milliseconds passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getSeenTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSeenTimestamp(mId, true)); + } + + /** + * @return Returns the sender of this incoming message. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public RcsParticipant getSenderParticipant() throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.getSenderParticipant(mId))); } + /** + * @return Returns {@code true} as this is an incoming message + */ @Override - public void writeToParcel(Parcel dest, int flags) { + public boolean isIncoming() { + return true; } } diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl new file mode 100644 index 000000000000..76073c22af0c --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsIncomingMessageCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java new file mode 100644 index 000000000000..acde8af0d295 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java @@ -0,0 +1,180 @@ +/* + * 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.telephony.ims; + +import android.annotation.CheckResult; +import android.os.Parcel; +import android.os.Parcelable; + +/** + * {@link RcsIncomingMessageCreationParameters} is a collection of parameters that should be passed + * into {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} to generate an + * {@link RcsIncomingMessage} on that {@link RcsThread} + * + * @hide TODO:make public + */ +public class RcsIncomingMessageCreationParameters extends RcsMessageCreationParameters implements + Parcelable { + // The arrival timestamp for the RcsIncomingMessage to be created + private final long mArrivalTimestamp; + // The seen timestamp for the RcsIncomingMessage to be created + private final long mSeenTimestamp; + // The participant that sent this incoming message + private final int mSenderParticipantId; + + /** + * Builder to help create an {@link RcsIncomingMessageCreationParameters} + * + * @see RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters) + */ + public static class Builder extends RcsMessageCreationParameters.Builder { + private RcsParticipant mSenderParticipant; + private long mArrivalTimestamp; + private long mSeenTimestamp; + + /** + * Creates a {@link Builder} to create an instance of + * {@link RcsIncomingMessageCreationParameters} + * + * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination + * timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @param arrivalTimestamp The timestamp of arrival, defined as milliseconds passed after + * midnight, January 1, 1970 UTC + * @param subscriptionId The subscription ID that was used to send or receive this + * {@link RcsMessage} + */ + public Builder(long originationTimestamp, long arrivalTimestamp, int subscriptionId) { + super(originationTimestamp, subscriptionId); + mArrivalTimestamp = arrivalTimestamp; + } + + /** + * Sets the {@link RcsParticipant} that send this {@link RcsIncomingMessage} + * + * @param senderParticipant The {@link RcsParticipant} that sent this + * {@link RcsIncomingMessage} + * @return The same instance of {@link Builder} to chain methods. + */ + @CheckResult + public Builder setSenderParticipant(RcsParticipant senderParticipant) { + mSenderParticipant = senderParticipant; + return this; + } + + /** + * Sets the time of the arrival of this {@link RcsIncomingMessage} + + * @return The same instance of {@link Builder} to chain methods. + * @see RcsIncomingMessage#setArrivalTimestamp(long) + */ + @CheckResult + public Builder setArrivalTimestamp(long arrivalTimestamp) { + mArrivalTimestamp = arrivalTimestamp; + return this; + } + + /** + * Sets the time of the when this user saw the {@link RcsIncomingMessage} + * @param seenTimestamp The seen timestamp , defined as milliseconds passed after midnight, + * January 1, 1970 UTC + * @return The same instance of {@link Builder} to chain methods. + * @see RcsIncomingMessage#setSeenTimestamp(long) + */ + @CheckResult + public Builder setSeenTimestamp(long seenTimestamp) { + mSeenTimestamp = seenTimestamp; + return this; + } + + /** + * Creates parameters for creating a new incoming message. + * @return A new instance of {@link RcsIncomingMessageCreationParameters} to create a new + * {@link RcsIncomingMessage} + */ + public RcsIncomingMessageCreationParameters build() { + return new RcsIncomingMessageCreationParameters(this); + } + } + + private RcsIncomingMessageCreationParameters(Builder builder) { + super(builder); + mArrivalTimestamp = builder.mArrivalTimestamp; + mSeenTimestamp = builder.mSeenTimestamp; + mSenderParticipantId = builder.mSenderParticipant.getId(); + } + + protected RcsIncomingMessageCreationParameters(Parcel in) { + super(in); + mArrivalTimestamp = in.readLong(); + mSeenTimestamp = in.readLong(); + mSenderParticipantId = in.readInt(); + } + + /** + * @return Returns the arrival timestamp for the {@link RcsIncomingMessage} to be created. + * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getArrivalTimestamp() { + return mArrivalTimestamp; + } + + /** + * @return Returns the seen timestamp for the {@link RcsIncomingMessage} to be created. + * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC + */ + public long getSeenTimestamp() { + return mSeenTimestamp; + } + + /** + * Helper getter for {@link com.android.internal.telephony.ims.RcsMessageStoreController} to + * create {@link RcsIncomingMessage}s + * + * Since the API doesn't expose any ID's to API users, this should be hidden. + * @hide + */ + public int getSenderParticipantId() { + return mSenderParticipantId; + } + + public static final Creator<RcsIncomingMessageCreationParameters> CREATOR = + new Creator<RcsIncomingMessageCreationParameters>() { + @Override + public RcsIncomingMessageCreationParameters createFromParcel(Parcel in) { + return new RcsIncomingMessageCreationParameters(in); + } + + @Override + public RcsIncomingMessageCreationParameters[] newArray(int size) { + return new RcsIncomingMessageCreationParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeLong(mArrivalTimestamp); + dest.writeLong(mSeenTimestamp); + dest.writeInt(mSenderParticipantId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.aidl b/telephony/java/android/telephony/ims/RcsLocationPart.aidl deleted file mode 100644 index 4fe5ca97a30d..000000000000 --- a/telephony/java/android/telephony/ims/RcsLocationPart.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsLocationPart; diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.java b/telephony/java/android/telephony/ims/RcsLocationPart.java deleted file mode 100644 index 19be4ceaf688..000000000000 --- a/telephony/java/android/telephony/ims/RcsLocationPart.java +++ /dev/null @@ -1,48 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a location - * @hide - TODO(sahinc) make this public - */ -public class RcsLocationPart extends RcsPart { - public static final Creator<RcsLocationPart> CREATOR = new Creator<RcsLocationPart>() { - @Override - public RcsLocationPart createFromParcel(Parcel in) { - return new RcsLocationPart(in); - } - - @Override - public RcsLocationPart[] newArray(int size) { - return new RcsLocationPart[size]; - } - }; - - protected RcsLocationPart(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java index df108c88e3b0..e84d4ed9e7be 100644 --- a/telephony/java/android/telephony/ims/RcsManager.java +++ b/telephony/java/android/telephony/ims/RcsManager.java @@ -28,7 +28,7 @@ public class RcsManager { private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore(); /** - * Returns an instance of RcsMessageStore. + * Returns an instance of {@link RcsMessageStore} */ public RcsMessageStore getRcsMessageStore() { return sRcsMessageStoreInstance; diff --git a/telephony/java/android/telephony/ims/RcsMessage.java b/telephony/java/android/telephony/ims/RcsMessage.java index d46685c4a572..b70a9a7973f5 100644 --- a/telephony/java/android/telephony/ims/RcsMessage.java +++ b/telephony/java/android/telephony/ims/RcsMessage.java @@ -15,11 +15,314 @@ */ package android.telephony.ims; -import android.os.Parcelable; +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; /** * This is a single instance of a message sent or received over RCS. - * @hide - TODO(sahinc) make this public + * + * @hide - TODO(109759350) make this public */ -public abstract class RcsMessage implements Parcelable { +public abstract class RcsMessage { + /** + * The value to indicate that this {@link RcsMessage} does not have any location information. + */ + public static final double LOCATION_NOT_SET = Double.MIN_VALUE; + + /** + * The status to indicate that this {@link RcsMessage}s status is not set yet. + */ + public static final int NOT_SET = 0; + + /** + * The status to indicate that this {@link RcsMessage} is a draft and is not in the process of + * sending yet. + */ + public static final int DRAFT = 1; + + /** + * The status to indicate that this {@link RcsMessage} was successfully sent. + */ + public static final int QUEUED = 2; + + /** + * The status to indicate that this {@link RcsMessage} is actively being sent. + */ + public static final int SENDING = 3; + + /** + * The status to indicate that this {@link RcsMessage} was successfully sent. + */ + public static final int SENT = 4; + + /** + * The status to indicate that this {@link RcsMessage} failed to send in an attempt before, and + * now being retried. + */ + public static final int RETRYING = 5; + + /** + * The status to indicate that this {@link RcsMessage} has permanently failed to send. + */ + public static final int FAILED = 6; + + /** + * The status to indicate that this {@link RcsMessage} was successfully received. + */ + public static final int RECEIVED = 7; + + /** + * The status to indicate that this {@link RcsMessage} was seen. + */ + public static final int SEEN = 9; + + protected int mId; + + @IntDef({ + DRAFT, QUEUED, SENDING, SENT, RETRYING, FAILED, RECEIVED, SEEN + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RcsMessageStatus { + } + + RcsMessage(int id) { + mId = id; + } + + /** + * Returns the row Id from the common message. + * + * @hide + */ + public int getId() { + return mId; + } + + /** + * @return Returns the subscription ID that this {@link RcsMessage} was sent from, or delivered + * to. + * @throws RcsMessageStoreException if the value could not be read from the storage + * @see android.telephony.SubscriptionInfo#getSubscriptionId + */ + public int getSubscriptionId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSubId(mId, isIncoming())); + } + + /** + * Sets the subscription ID that this {@link RcsMessage} was sent from, or delivered to and + * persists it into storage. + * + * @param subId The subscription ID to persists into storage. + * @throws RcsMessageStoreException if the value could not be persisted into storage + * @see android.telephony.SubscriptionInfo#getSubscriptionId + */ + @WorkerThread + public void setSubscriptionId(int subId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setMessageSubId(mId, isIncoming(), subId)); + } + + /** + * Sets the status of this message and persists it into storage. Please see + * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers. + * + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setStatus(@RcsMessageStatus int rcsMessageStatus) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageStatus(mId, isIncoming(), rcsMessageStatus)); + } + + /** + * @return Returns the status of this message. Please see + * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsMessageStatus int getStatus() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageStatus(mId, isIncoming())); + } + + /** + * Sets the origination timestamp of this message and persists it into storage. Origination is + * defined as when the sender tapped the send button. + * + * @param timestamp The origination timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setOriginationTimestamp(long timestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setMessageOriginationTimestamp(mId, isIncoming(), timestamp)); + } + + /** + * @return Returns the origination timestamp of this message in milliseconds passed after + * midnight, January 1, 1970 UTC. Origination is defined as when the sender tapped the send + * button. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getOriginationTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getMessageOriginationTimestamp(mId, isIncoming())); + } + + /** + * Sets the globally unique RCS message identifier for this message and persists it into + * storage. This function does not confirm that this message id is unique. Please see 4.4.5.2 + * - GSMA RCC.53 (RCS Device API 1.6 Specification + * + * @param rcsMessageGlobalId The globally RCS message identifier + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setRcsMessageId(String rcsMessageGlobalId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setGlobalMessageIdForMessage(mId, isIncoming(), rcsMessageGlobalId)); + } + + /** + * @return Returns the globally unique RCS message identifier for this message. Please see + * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getRcsMessageId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getGlobalMessageIdForMessage(mId, isIncoming())); + } + + /** + * @return Returns the user visible text included in this message. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public String getText() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getTextForMessage(mId, isIncoming())); + } + + /** + * Sets the user visible text for this message and persists in storage. + * + * @param text The text this message now has + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setText(String text) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setTextForMessage(mId, isIncoming(), text)); + } + + /** + * @return Returns the associated latitude for this message, or + * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location. + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public double getLatitude() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getLatitudeForMessage(mId, isIncoming())); + } + + /** + * Sets the latitude for this message and persists in storage. + * + * @param latitude The latitude for this location message. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLatitude(double latitude) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setLatitudeForMessage(mId, isIncoming(), latitude)); + } + + /** + * @return Returns the associated longitude for this message, or + * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location. + * + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public double getLongitude() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getLongitudeForMessage(mId, isIncoming())); + } + + /** + * Sets the longitude for this message and persists in storage. + * + * @param longitude The longitude for this location message. + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setLongitude(double longitude) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.setLongitudeForMessage(mId, isIncoming(), longitude)); + } + + /** + * Attaches an {@link RcsFileTransferPart} to this message and persists into storage. + * + * @param fileTransferCreationParameters The parameters to be used to create the + * {@link RcsFileTransferPart} + * @return A new instance of {@link RcsFileTransferPart} + * @throws RcsMessageStoreException if the file transfer could not be persisted into storage. + */ + @NonNull + @WorkerThread + public RcsFileTransferPart insertFileTransfer( + RcsFileTransferCreationParameters fileTransferCreationParameters) + throws RcsMessageStoreException { + return new RcsFileTransferPart(RcsControllerCall.call( + iRcs -> iRcs.storeFileTransfer(mId, isIncoming(), fileTransferCreationParameters))); + } + + /** + * @return Returns all the {@link RcsFileTransferPart}s associated with this message in an + * unmodifiable set. + * @throws RcsMessageStoreException if the file transfers could not be read from the storage + */ + @NonNull + @WorkerThread + public Set<RcsFileTransferPart> getFileTransferParts() throws RcsMessageStoreException { + Set<RcsFileTransferPart> fileTransferParts = new HashSet<>(); + + int[] fileTransferIds = RcsControllerCall.call( + iRcs -> iRcs.getFileTransfersAttachedToMessage(mId, isIncoming())); + + for (int fileTransfer : fileTransferIds) { + fileTransferParts.add(new RcsFileTransferPart(fileTransfer)); + } + + return Collections.unmodifiableSet(fileTransferParts); + } + + /** + * Removes a {@link RcsFileTransferPart} from this message, and deletes it in storage. + * + * @param fileTransferPart The part to delete. + * @throws RcsMessageStoreException if the file transfer could not be removed from storage + */ + @WorkerThread + public void removeFileTransferPart(@NonNull RcsFileTransferPart fileTransferPart) + throws RcsMessageStoreException { + if (fileTransferPart == null) { + return; + } + + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.deleteFileTransfer(fileTransferPart.getId())); + } + + /** + * @return Returns {@code true} if this message was received on this device, {@code false} if it + * was sent. + */ + public abstract boolean isIncoming(); } diff --git a/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl new file mode 100644 index 000000000000..5774d00ca934 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsMessageCreationParameters; diff --git a/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java new file mode 100644 index 000000000000..ff3f33ed2e1b --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java @@ -0,0 +1,265 @@ +/* + * 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.telephony.ims; + +import static android.telephony.ims.RcsMessage.LOCATION_NOT_SET; + +import android.annotation.CheckResult; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.SubscriptionInfo; + +/** + * The collection of parameters to be passed into + * {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} and + * {@link RcsThread#addOutgoingMessage(RcsMessageCreationParameters)} to create and persist + * {@link RcsMessage}s on an {@link RcsThread} + * + * @hide TODO - make public + */ +public class RcsMessageCreationParameters implements Parcelable { + // The globally unique id of the RcsMessage to be created. + private final String mRcsMessageGlobalId; + + // The subscription that this message was/will be received/sent from. + private final int mSubId; + // The sending/receiving status of the message + private final @RcsMessage.RcsMessageStatus int mMessageStatus; + // The timestamp of message creation + private final long mOriginationTimestamp; + // The user visible content of the message + private final String mText; + // The latitude of the message if this is a location message + private final double mLatitude; + // The longitude of the message if this is a location message + private final double mLongitude; + + /** + * @return Returns the globally unique RCS Message ID for the {@link RcsMessage} to be created. + * Please see 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification + */ + @Nullable + public String getRcsMessageGlobalId() { + return mRcsMessageGlobalId; + } + + /** + * @return Returns the subscription ID that was used to send or receive the {@link RcsMessage} + * to be created. + */ + public int getSubId() { + return mSubId; + } + + /** + * @return Returns the status for the {@link RcsMessage} to be created. + * @see RcsMessage.RcsMessageStatus + */ + public int getMessageStatus() { + return mMessageStatus; + } + + /** + * @return Returns the origination timestamp of the {@link RcsMessage} to be created in + * milliseconds passed after midnight, January 1, 1970 UTC. Origination is defined as when + * the sender tapped the send button. + */ + public long getOriginationTimestamp() { + return mOriginationTimestamp; + } + + /** + * @return Returns the user visible text contained in the {@link RcsMessage} to be created + */ + @Nullable + public String getText() { + return mText; + } + + /** + * @return Returns the latitude of the {@link RcsMessage} to be created, or + * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location. + */ + public double getLatitude() { + return mLatitude; + } + + /** + * @return Returns the longitude of the {@link RcsMessage} to be created, or + * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location. + */ + public double getLongitude() { + return mLongitude; + } + + /** + * The base builder for creating {@link RcsMessage}s on {@link RcsThread}s. + * + * @see RcsIncomingMessageCreationParameters + */ + public static class Builder { + private String mRcsMessageGlobalId; + private int mSubId; + private @RcsMessage.RcsMessageStatus int mMessageStatus; + private long mOriginationTimestamp; + private String mText; + private double mLatitude = LOCATION_NOT_SET; + private double mLongitude = LOCATION_NOT_SET; + + /** + * Creates a new {@link Builder} to create an instance of + * {@link RcsMessageCreationParameters}. + * + * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination + * timestamp value in milliseconds passed after midnight, + * January 1, 1970 UTC + * @param subscriptionId The subscription ID that was used to send or receive this + * {@link RcsMessage} + * @see SubscriptionInfo#getSubscriptionId() + */ + public Builder(long originationTimestamp, int subscriptionId) { + mOriginationTimestamp = originationTimestamp; + mSubId = subscriptionId; + } + + /** + * Sets the status of the {@link RcsMessage} to be built. + * + * @param rcsMessageStatus The status to be set + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setStatus(int) + */ + @CheckResult + public Builder setStatus(@RcsMessage.RcsMessageStatus int rcsMessageStatus) { + mMessageStatus = rcsMessageStatus; + return this; + } + + /** + * Sets the globally unique RCS message identifier for the {@link RcsMessage} to be built. + * This function does not confirm that this message id is unique. Please see 4.4.5.2 - GSMA + * RCC.53 (RCS Device API 1.6 Specification) + * + * @param rcsMessageId The ID to be set + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setRcsMessageId(String) + */ + @CheckResult + public Builder setRcsMessageId(String rcsMessageId) { + mRcsMessageGlobalId = rcsMessageId; + return this; + } + + /** + * Sets the text of the {@link RcsMessage} to be built. + * + * @param text The user visible text of the message + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setText(String) + */ + @CheckResult + public Builder setText(String text) { + mText = text; + return this; + } + + /** + * Sets the latitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * + * @param latitude The latitude of the location information associated with this message. + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setLatitude(double) + */ + @CheckResult + public Builder setLatitude(double latitude) { + mLatitude = latitude; + return this; + } + + /** + * Sets the longitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71 + * (RCS Universal Profile Service Definition Document) + * + * @param longitude The longitude of the location information associated with this message. + * @return The same instance of {@link Builder} to chain methods + * @see RcsMessage#setLongitude(double) + */ + @CheckResult + public Builder setLongitude(double longitude) { + mLongitude = longitude; + return this; + } + + /** + * @hide + */ + public RcsMessageCreationParameters build() { + return new RcsMessageCreationParameters(this); + } + } + + protected RcsMessageCreationParameters(Builder builder) { + mRcsMessageGlobalId = builder.mRcsMessageGlobalId; + mSubId = builder.mSubId; + mMessageStatus = builder.mMessageStatus; + mOriginationTimestamp = builder.mOriginationTimestamp; + mText = builder.mText; + mLatitude = builder.mLatitude; + mLongitude = builder.mLongitude; + } + + protected RcsMessageCreationParameters(Parcel in) { + mRcsMessageGlobalId = in.readString(); + mSubId = in.readInt(); + mMessageStatus = in.readInt(); + mOriginationTimestamp = in.readLong(); + mText = in.readString(); + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); + } + + public static final Creator<RcsMessageCreationParameters> CREATOR = + new Creator<RcsMessageCreationParameters>() { + @Override + public RcsMessageCreationParameters createFromParcel(Parcel in) { + return new RcsMessageCreationParameters(in); + } + + @Override + public RcsMessageCreationParameters[] newArray(int size) { + return new RcsMessageCreationParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mRcsMessageGlobalId); + dest.writeInt(mSubId); + dest.writeInt(mMessageStatus); + dest.writeLong(mOriginationTimestamp); + dest.writeString(mText); + dest.writeDouble(mLatitude); + dest.writeDouble(mLongitude); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl new file mode 100644 index 000000000000..c325c23ba9bd --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsMessageQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java new file mode 100644 index 000000000000..c964cf9af7c5 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java @@ -0,0 +1,361 @@ +/* + * 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.telephony.ims; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} in order to select a + * subset of {@link RcsMessage}s present in the message store. + * + * @hide TODO - make the Builder and builder() public. The rest should stay internal only. + */ +public class RcsMessageQueryParameters implements Parcelable { + /** + * @hide - not meant for public use + */ + public static final int THREAD_ID_NOT_SET = -1; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted in the same order of {@link RcsMessage}s that got persisted into storage for faster + * results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted according to the timestamp of {@link RcsMessage#getOriginationTimestamp()} + */ + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { + } + + /** + * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return + * {@link RcsIncomingMessage}s. + */ + public static final int MESSAGE_TYPE_INCOMING = 0x0001; + + /** + * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return + * {@link RcsOutgoingMessage}s. + */ + public static final int MESSAGE_TYPE_OUTGOING = 0x0002; + + /** + * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return {@link RcsMessage}s + * that have an {@link RcsFileTransferPart} attached. + */ + public static final int MESSAGES_WITH_FILE_TRANSFERS = 0x0004; + + /** + * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return {@link RcsMessage}s + * that don't have an {@link RcsFileTransferPart} attached. + */ + public static final int MESSAGES_WITHOUT_FILE_TRANSFERS = 0x0008; + + /** + * @hide - not meant for public use + */ + public static final String MESSAGE_QUERY_PARAMETERS_KEY = "message_query_parameters"; + + // Whether the result should be filtered against incoming or outgoing messages + private int mMessageType; + // Whether the result should have file transfer messages attached or not + private int mFileTransferPresence; + // The SQL "Like" clause to filter messages + private String mMessageLike; + // The property the messages should be sorted against + private @SortingProperty int mSortingProperty; + // Whether the messages should be sorted in ascending order + private boolean mIsAscending; + // The number of results that should be returned with this query + private int mLimit; + // The thread that the results should be limited to + private int mThreadId; + + RcsMessageQueryParameters(int messageType, int fileTransferPresence, String messageLike, + int threadId, @SortingProperty int sortingProperty, boolean isAscending, int limit) { + mMessageType = messageType; + mFileTransferPresence = fileTransferPresence; + mMessageLike = messageLike; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + mThreadId = threadId; + } + + /** + * @return Returns the type of {@link RcsMessage}s that this {@link RcsMessageQueryParameters} + * is set to query for. + */ + public int getMessageType() { + return mMessageType; + } + + /** + * @return Returns whether the result query should return {@link RcsMessage}s with + * {@link RcsFileTransferPart}s or not + */ + public int getFileTransferPresence() { + return mFileTransferPresence; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match {@link RcsMessage}s + */ + public String getMessageLike() { + return mMessageLike; + } + + /** + * @return Returns the number of {@link RcsThread}s to be returned from the query. A value of + * 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty + */ + public @SortingProperty int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get + * the thread that the result query should be limited to. + * + * As we do not expose any sort of integer ID's to public usage, this should be hidden. + * + * @hide - not meant for public use + */ + public int getThreadId() { + return mThreadId; + } + + /** + * A helper class to build the {@link RcsMessageQueryParameters}. + */ + public static class Builder { + private @SortingProperty int mSortingProperty; + private int mMessageType; + private int mFileTransferPresence; + private String mMessageLike; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId = THREAD_ID_NOT_SET; + + /** + * Creates a new builder for {@link RcsMessageQueryParameters} to be used in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * + */ + public Builder() { + // empty implementation + } + + /** + * Desired number of threads to be returned from the query. Passing in 0 will return all + * existing threads at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the type of messages to be returned from the query. + * + * @param messageType The type of message to be returned. + * @return The same instance of the builder to chain parameters. + * @see RcsMessageQueryParameters#MESSAGE_TYPE_INCOMING + * @see RcsMessageQueryParameters#MESSAGE_TYPE_OUTGOING + */ + @CheckResult + public Builder setMessageType(int messageType) { + mMessageType = messageType; + return this; + } + + /** + * Sets whether file transfer messages should be included in the query result or not. + * + * @param fileTransferPresence Whether file transfers should be included in the result + * @return The same instance of the builder to chain parameters. + * @see RcsMessageQueryParameters#MESSAGES_WITH_FILE_TRANSFERS + * @see RcsMessageQueryParameters#MESSAGES_WITHOUT_FILE_TRANSFERS + */ + @CheckResult + public Builder setFileTransferPresence(int fileTransferPresence) { + mFileTransferPresence = fileTransferPresence; + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with messages. Using a percent sign ('%') + * wildcard matches any sequence of zero or more characters. Using an underscore ('_') + * wildcard matches any single character. Not using any wildcards would only perform a + * string match. The input string is case-insensitive. + * + * The input "Wh%" would match messages "who", "where" and "what", while the input "Wh_" + * would only match "who" + * + * @param messageLike The "like" clause for matching {@link RcsMessage}s. + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setMessageLike(String messageLike) { + mMessageLike = messageLike; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsMessageQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Limits the results to the given thread. + * + * @param thread the {@link RcsThread} that results should be limited to. If set to + * {@code null}, messages on all threads will be queried + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setThread(@Nullable RcsThread thread) { + if (thread == null) { + mThreadId = THREAD_ID_NOT_SET; + } else { + mThreadId = thread.getThreadId(); + } + return this; + } + + /** + * Builds the {@link RcsMessageQueryParameters} to use in + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * + * @return An instance of {@link RcsMessageQueryParameters} to use with the message + * query. + */ + public RcsMessageQueryParameters build() { + return new RcsMessageQueryParameters(mMessageType, mFileTransferPresence, mMessageLike, + mThreadId, mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + protected RcsMessageQueryParameters(Parcel in) { + mMessageType = in.readInt(); + mFileTransferPresence = in.readInt(); + mMessageLike = in.readString(); + mSortingProperty = in.readInt(); + mIsAscending = in.readBoolean(); + mLimit = in.readInt(); + mThreadId = in.readInt(); + } + + public static final Creator<RcsMessageQueryParameters> CREATOR = + new Creator<RcsMessageQueryParameters>() { + @Override + public RcsMessageQueryParameters createFromParcel(Parcel in) { + return new RcsMessageQueryParameters(in); + } + + @Override + public RcsMessageQueryParameters[] newArray(int size) { + return new RcsMessageQueryParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mMessageType); + dest.writeInt(mFileTransferPresence); + dest.writeString(mMessageLike); + dest.writeInt(mSortingProperty); + dest.writeBoolean(mIsAscending); + dest.writeInt(mLimit); + dest.writeInt(mThreadId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl index eaf312877deb..a73ba50b6591 100644 --- a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsFileTransferPart; +parcelable RcsMessageQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.java b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java new file mode 100644 index 000000000000..c3846fdebf2e --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java @@ -0,0 +1,115 @@ +/* + * 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.telephony.ims; + +import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.ims.RcsTypeIdPair; + +import java.util.ArrayList; +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * call. This class allows getting the token for querying the next batch of messages in order to + * prevent handling large amounts of data at once. + * + * @hide + */ +public class RcsMessageQueryResult implements Parcelable { + // The token to continue the query to get the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The message type and message ID pairs for all the messages in this query result + private List<RcsTypeIdPair> mMessageTypeIdPairs; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsMessageQueryResult( + RcsQueryContinuationToken continuationToken, + List<RcsTypeIdPair> messageTypeIdPairs) { + mContinuationToken = continuationToken; + mMessageTypeIdPairs = messageTypeIdPairs; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsMessage}s. + */ + @Nullable + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsMessage}s in the current query result. Call {@link + * RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)} to get the next batch + * of {@link RcsMessage}s. + */ + @NonNull + public List<RcsMessage> getMessages() { + List<RcsMessage> messages = new ArrayList<>(); + for (RcsTypeIdPair typeIdPair : mMessageTypeIdPairs) { + if (typeIdPair.getType() == MESSAGE_TYPE_INCOMING) { + messages.add(new RcsIncomingMessage(typeIdPair.getId())); + } else { + messages.add(new RcsOutgoingMessage(typeIdPair.getId())); + } + } + + return messages; + } + + protected RcsMessageQueryResult(Parcel in) { + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + in.readTypedList(mMessageTypeIdPairs, RcsTypeIdPair.CREATOR); + } + + public static final Creator<RcsMessageQueryResult> CREATOR = + new Creator<RcsMessageQueryResult>() { + @Override + public RcsMessageQueryResult createFromParcel(Parcel in) { + return new RcsMessageQueryResult(in); + } + + @Override + public RcsMessageQueryResult[] newArray(int size) { + return new RcsMessageQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + dest.writeTypedList(mMessageTypeIdPairs); + } +} diff --git a/telephony/java/android/telephony/ims/RcsManager.aidl b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl index 63bc71c5ee46..99b8eb704e00 100644 --- a/telephony/java/android/telephony/ims/RcsManager.aidl +++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl @@ -17,4 +17,4 @@ package android.telephony.ims; -parcelable RcsManager; +parcelable RcsMessageSnippet; diff --git a/telephony/java/android/telephony/ims/RcsMessageSnippet.java b/telephony/java/android/telephony/ims/RcsMessageSnippet.java new file mode 100644 index 000000000000..9399c2003827 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.java @@ -0,0 +1,98 @@ +/* + * 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.telephony.ims; + +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.ims.RcsMessage.RcsMessageStatus; + +/** + * An immutable summary of the latest {@link RcsMessage} on an {@link RcsThread} + * + * @hide TODO: make public + */ +public class RcsMessageSnippet implements Parcelable { + private final String mText; + private final @RcsMessageStatus int mStatus; + private final long mTimestamp; + + /** + * @hide + */ + public RcsMessageSnippet(String text, @RcsMessageStatus int status, long timestamp) { + mText = text; + mStatus = status; + mTimestamp = timestamp; + } + + /** + * @return Returns the text of the {@link RcsMessage} with highest origination timestamp value + * (i.e. latest) in this thread + */ + @Nullable + public String getSnippetText() { + return mText; + } + + /** + * @return Returns the status of the {@link RcsMessage} with highest origination timestamp value + * (i.e. latest) in this thread + */ + public @RcsMessageStatus int getSnippetStatus() { + return mStatus; + } + + /** + * @return Returns the timestamp of the {@link RcsMessage} with highest origination timestamp + * value (i.e. latest) in this thread + */ + public long getSnippetTimestamp() { + return mTimestamp; + } + + protected RcsMessageSnippet(Parcel in) { + mText = in.readString(); + mStatus = in.readInt(); + mTimestamp = in.readLong(); + } + + public static final Creator<RcsMessageSnippet> CREATOR = + new Creator<RcsMessageSnippet>() { + @Override + public RcsMessageSnippet createFromParcel(Parcel in) { + return new RcsMessageSnippet(in); + } + + @Override + public RcsMessageSnippet[] newArray(int size) { + return new RcsMessageSnippet[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mText); + dest.writeInt(mStatus); + dest.writeLong(mTimestamp); + } +} diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java index 1bf6ffd81ca0..c8c36a8e479b 100644 --- a/telephony/java/android/telephony/ims/RcsMessageStore.java +++ b/telephony/java/android/telephony/ims/RcsMessageStore.java @@ -16,106 +16,223 @@ package android.telephony.ims; +import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.WorkerThread; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.telephony.Rlog; -import android.telephony.ims.aidl.IRcs; +import android.net.Uri; + +import java.util.List; /** * RcsMessageStore is the application interface to RcsProvider and provides access methods to * RCS related database tables. + * * @hide - TODO make this public */ public class RcsMessageStore { - static final String TAG = "RcsMessageStore"; - /** * Returns the first chunk of existing {@link RcsThread}s in the common storage. + * * @param queryParameters Parameters to specify to return a subset of all RcsThreads. * Passing a value of null will return all threads. + * @throws RcsMessageStoreException if the query could not be completed on the storage */ @WorkerThread - public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.getRcsThreads(queryParameters); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re); - } - - return null; + @NonNull + public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsThreads(queryParameters)); } /** * Returns the next chunk of {@link RcsThread}s in the common storage. + * * @param continuationToken A token to continue the query to get the next chunk. This is - * obtained through {@link RcsThreadQueryResult#nextChunkToken}. + * obtained through {@link RcsThreadQueryResult#getContinuationToken}. + * @throws RcsMessageStoreException if the query could not be completed on the storage */ @WorkerThread - public RcsThreadQueryResult getRcsThreads(RcsThreadQueryContinuationToken continuationToken) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.getRcsThreadsWithToken(continuationToken); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re); - } + @NonNull + public RcsThreadQueryResult getRcsThreads(@NonNull RcsQueryContinuationToken continuationToken) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsThreadsWithToken(continuationToken)); + } + + /** + * Returns the first chunk of existing {@link RcsParticipant}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsParticipants. + * Passing a value of null will return all participants. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsParticipantQueryResult getRcsParticipants( + @Nullable RcsParticipantQueryParameters queryParameters) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters)); + } + + /** + * Returns the next chunk of {@link RcsParticipant}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through + * {@link RcsParticipantQueryResult#getContinuationToken} + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsParticipantQueryResult getRcsParticipants( + @NonNull RcsQueryContinuationToken continuationToken) + throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken)); + } - return null; + /** + * Returns the first chunk of existing {@link RcsMessage}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsMessages. + * Passing a value of null will return all messages. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getRcsMessages( + @Nullable RcsMessageQueryParameters queryParameters) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters)); + } + + /** + * Returns the next chunk of {@link RcsMessage}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through {@link RcsMessageQueryResult#getContinuationToken} + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getRcsMessages( + @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessagesWithToken(continuationToken)); + } + + /** + * Returns the first chunk of existing {@link RcsEvent}s in the common storage. + * + * @param queryParameters Parameters to specify to return a subset of all RcsEvents. + * Passing a value of null will return all events. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsEventQueryResult getRcsEvents( + @Nullable RcsEventQueryParameters queryParameters) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getEvents(queryParameters)); + } + + /** + * Returns the next chunk of {@link RcsEvent}s in the common storage. + * + * @param continuationToken A token to continue the query to get the next chunk. This is + * obtained through {@link RcsEventQueryResult#getContinuationToken}. + * @throws RcsMessageStoreException if the query could not be completed on the storage + */ + @WorkerThread + @NonNull + public RcsEventQueryResult getRcsEvents( + @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getEventsWithToken(continuationToken)); + } + + /** + * Persists an {@link RcsEvent} to common storage. + * + * @param persistableEvent The {@link RcsEvent} to persist into storage. + * @throws RcsMessageStoreException if the query could not be completed on the storage + * + * @see RcsGroupThreadNameChangedEvent + * @see RcsGroupThreadIconChangedEvent + * @see RcsGroupThreadParticipantJoinedEvent + * @see RcsGroupThreadParticipantLeftEvent + * @see RcsParticipantAliasChangedEvent + */ + @WorkerThread + @NonNull + public void persistRcsEvent(RcsEvent persistableEvent) throws RcsMessageStoreException { + persistableEvent.persist(); } /** * Creates a new 1 to 1 thread with the given participant and persists it in the storage. + * + * @param recipient The {@link RcsParticipant} that will receive the messages in this thread. + * @return The newly created {@link Rcs1To1Thread} + * @throws RcsMessageStoreException if the thread could not be persisted in the storage */ @WorkerThread - public Rcs1To1Thread createRcs1To1Thread(RcsParticipant recipient) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.createRcs1To1Thread(recipient); + @NonNull + public Rcs1To1Thread createRcs1To1Thread(@NonNull RcsParticipant recipient) + throws RcsMessageStoreException { + return new Rcs1To1Thread( + RcsControllerCall.call(iRcs -> iRcs.createRcs1To1Thread(recipient.getId()))); + } + + /** + * Creates a new group thread with the given participants and persists it in the storage. + * + * @throws RcsMessageStoreException if the thread could not be persisted in the storage + */ + @WorkerThread + @NonNull + public RcsGroupThread createGroupThread(@Nullable List<RcsParticipant> recipients, + @Nullable String groupName, @Nullable Uri groupIcon) throws RcsMessageStoreException { + int[] recipientIds = null; + if (recipients != null) { + recipientIds = new int[recipients.size()]; + + for (int i = 0; i < recipients.size(); i++) { + recipientIds[i] = recipients.get(i).getId(); } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcs1To1Thread", re); } - return null; + int[] finalRecipientIds = recipientIds; + return new RcsGroupThread(RcsControllerCall.call( + iRcs -> iRcs.createGroupThread(finalRecipientIds, groupName, groupIcon))); } /** - * Delete the {@link RcsThread} identified by the given threadId. - * @param threadId threadId of the thread to be deleted. + * Delete the given {@link RcsThread} from the storage. + * + * @param thread The thread to be deleted. + * @throws RcsMessageStoreException if the thread could not be deleted from the storage */ @WorkerThread - public void deleteThread(int threadId) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.deleteThread(threadId); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during deleteThread", re); + public void deleteThread(@NonNull RcsThread thread) throws RcsMessageStoreException { + if (thread == null) { + return; + } + + boolean isDeleteSucceeded = RcsControllerCall.call( + iRcs -> iRcs.deleteThread(thread.getThreadId(), thread.getThreadType())); + + if (!isDeleteSucceeded) { + throw new RcsMessageStoreException("Could not delete RcsThread"); } } /** * Creates a new participant and persists it in the storage. + * * @param canonicalAddress The defining address (e.g. phone number) of the participant. + * @param alias The RCS alias for the participant. + * @throws RcsMessageStoreException if the participant could not be created on the storage */ - public RcsParticipant createRcsParticipant(String canonicalAddress) { - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - return iRcs.createRcsParticipant(canonicalAddress); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcsParticipant", re); - } - - return null; + @WorkerThread + @NonNull + public RcsParticipant createRcsParticipant(String canonicalAddress, @Nullable String alias) + throws RcsMessageStoreException { + return new RcsParticipant( + RcsControllerCall.call(iRcs -> iRcs.createRcsParticipant(canonicalAddress, alias))); } } diff --git a/telephony/java/android/telephony/ims/RcsPart.java b/telephony/java/android/telephony/ims/RcsMessageStoreException.java index da501738a0bf..e158f1a55aec 100644 --- a/telephony/java/android/telephony/ims/RcsPart.java +++ b/telephony/java/android/telephony/ims/RcsMessageStoreException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 The Android Open Source Project + * 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. @@ -13,13 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.telephony.ims; -import android.os.Parcelable; +package android.telephony.ims; /** - * A part of a composite {@link RcsMessage}. - * @hide - TODO(sahinc) make this public + * An exception that happened on {@link RcsMessageStore} or one of the derived storage classes in + * {@link android.telephony.ims} + * + * @hide TODO: make public */ -public abstract class RcsPart implements Parcelable { +public class RcsMessageStoreException extends Exception { + + /** + * Constructs an {@link RcsMessageStoreException} with the specified detail message. + * @param message The detail message + * @see Throwable#getMessage() + */ + public RcsMessageStoreException(String message) { + super(message); + } } diff --git a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java b/telephony/java/android/telephony/ims/RcsMultiMediaPart.java deleted file mode 100644 index d295fba365f0..000000000000 --- a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java +++ /dev/null @@ -1,50 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a media that is rendered on the screen - * (i.e. image, video etc) - * @hide - TODO(sahinc) make this public - */ -public class RcsMultiMediaPart extends RcsFileTransferPart { - public static final Creator<RcsMultiMediaPart> CREATOR = new Creator<RcsMultiMediaPart>() { - @Override - public RcsMultiMediaPart createFromParcel(Parcel in) { - return new RcsMultiMediaPart(in); - } - - @Override - public RcsMultiMediaPart[] newArray(int size) { - return new RcsMultiMediaPart[size]; - } - }; - - protected RcsMultiMediaPart(Parcel in) { - super(in); - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl b/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl deleted file mode 100644 index 5992d95c3b9c..000000000000 --- a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsMultimediaPart; diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl b/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl deleted file mode 100644 index 6e0c80f3af81..000000000000 --- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsOutgoingMessage; diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java index bfb161133618..0bd55ec2d25a 100644 --- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java @@ -15,34 +15,53 @@ */ package android.telephony.ims; -import android.os.Parcel; +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import java.util.ArrayList; +import java.util.List; /** * This is a single instance of a message sent over RCS. - * @hide - TODO(sahinc) make this public + * + * @hide - TODO(109759350) make this public */ public class RcsOutgoingMessage extends RcsMessage { - public static final Creator<RcsOutgoingMessage> CREATOR = new Creator<RcsOutgoingMessage>() { - @Override - public RcsOutgoingMessage createFromParcel(Parcel in) { - return new RcsOutgoingMessage(in); - } + RcsOutgoingMessage(int id) { + super(id); + } - @Override - public RcsOutgoingMessage[] newArray(int size) { - return new RcsOutgoingMessage[size]; - } - }; + /** + * @return Returns the {@link RcsOutgoingMessageDelivery}s associated with this message. Please + * note that the deliveries returned for the {@link RcsOutgoingMessage} may not always match the + * {@link RcsParticipant}s on the {@link RcsGroupThread} as the group recipients may have + * changed. + * @throws RcsMessageStoreException if the outgoing deliveries could not be read from storage. + */ + @NonNull + @WorkerThread + public List<RcsOutgoingMessageDelivery> getOutgoingDeliveries() + throws RcsMessageStoreException { + int[] deliveryParticipants; + List<RcsOutgoingMessageDelivery> messageDeliveries = new ArrayList<>(); - protected RcsOutgoingMessage(Parcel in) { - } + deliveryParticipants = RcsControllerCall.call( + iRcs -> iRcs.getMessageRecipients(mId)); - @Override - public int describeContents() { - return 0; + if (deliveryParticipants != null) { + for (Integer deliveryParticipant : deliveryParticipants) { + messageDeliveries.add(new RcsOutgoingMessageDelivery(deliveryParticipant, mId)); + } + } + + return messageDeliveries; } + /** + * @return Returns {@code false} as this is not an incoming message. + */ @Override - public void writeToParcel(Parcel dest, int flags) { + public boolean isIncoming() { + return false; } } diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java new file mode 100644 index 000000000000..b93f892df295 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +/** + * This class holds the delivery information of an {@link RcsOutgoingMessage} for each + * {@link RcsParticipant} that the message was intended for. + * + * @hide - TODO(109759350) make this public + */ +public class RcsOutgoingMessageDelivery { + // The participant that this delivery is intended for + private final int mRecipientId; + // The message this delivery is associated with + private final int mRcsOutgoingMessageId; + + /** + * Constructor to be used with RcsOutgoingMessage.getDelivery() + * + * @hide + */ + RcsOutgoingMessageDelivery(int recipientId, int messageId) { + mRecipientId = recipientId; + mRcsOutgoingMessageId = messageId; + } + + /** + * Sets the delivery time of this outgoing delivery and persists into storage. + * + * @param deliveredTimestamp The timestamp to set to delivery. It is defined as milliseconds + * passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setDeliveredTimestamp(long deliveredTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryDeliveredTimestamp( + mRcsOutgoingMessageId, mRecipientId, deliveredTimestamp)); + } + + /** + * @return Returns the delivered timestamp of the associated message to the associated + * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC. + * Returns 0 if the {@link RcsOutgoingMessage} is not delivered yet. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getDeliveredTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getOutgoingDeliveryDeliveredTimestamp( + mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * Sets the seen time of this outgoing delivery and persists into storage. + * + * @param seenTimestamp The timestamp to set to delivery. It is defined as milliseconds + * passed after midnight, January 1, 1970 UTC + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setSeenTimestamp(long seenTimestamp) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliverySeenTimestamp( + mRcsOutgoingMessageId, mRecipientId, seenTimestamp)); + } + + /** + * @return Returns the seen timestamp of the associated message by the associated + * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC. + * Returns 0 if the {@link RcsOutgoingMessage} is not seen yet. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public long getSeenTimestamp() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getOutgoingDeliverySeenTimestamp(mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * Sets the status of this outgoing delivery and persists into storage. + * + * @param status The status of the associated {@link RcsMessage}s delivery to the associated + * {@link RcsParticipant} + * @throws RcsMessageStoreException if the value could not be persisted into storage + */ + @WorkerThread + public void setStatus(@RcsMessage.RcsMessageStatus int status) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryStatus( + mRcsOutgoingMessageId, mRecipientId, status)); + } + + /** + * @return Returns the status of this outgoing delivery. + * @throws RcsMessageStoreException if the value could not be read from the storage + */ + @WorkerThread + public @RcsMessage.RcsMessageStatus int getStatus() throws RcsMessageStoreException { + return RcsControllerCall.call( + iRcs -> iRcs.getOutgoingDeliveryStatus(mRcsOutgoingMessageId, mRecipientId)); + } + + /** + * @return Returns the recipient associated with this delivery. + */ + @NonNull + public RcsParticipant getRecipient() { + return new RcsParticipant(mRecipientId); + } + + /** + * @return Returns the {@link RcsOutgoingMessage} associated with this delivery. + */ + @NonNull + public RcsOutgoingMessage getMessage() { + return new RcsOutgoingMessage(mRcsOutgoingMessageId); + } +} diff --git a/telephony/java/android/telephony/ims/RcsParticipant.aidl b/telephony/java/android/telephony/ims/RcsParticipant.aidl deleted file mode 100644 index 1c4436367e54..000000000000 --- a/telephony/java/android/telephony/ims/RcsParticipant.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsParticipant; diff --git a/telephony/java/android/telephony/ims/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java index f678ec7e435b..ce0ad2c4749b 100644 --- a/telephony/java/android/telephony/ims/RcsParticipant.java +++ b/telephony/java/android/telephony/ims/RcsParticipant.java @@ -15,33 +15,17 @@ */ package android.telephony.ims; -import static android.telephony.ims.RcsMessageStore.TAG; - -import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.WorkerThread; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.telephony.Rlog; -import android.telephony.ims.aidl.IRcs; -import android.text.TextUtils; - -import com.android.internal.util.Preconditions; /** * RcsParticipant is an RCS capable contact that can participate in {@link RcsThread}s. - * @hide - TODO(sahinc) make this public + * + * @hide - TODO(109759350) make this public */ -public class RcsParticipant implements Parcelable { +public class RcsParticipant { // The row ID of this participant in the database private int mId; - // The phone number of this participant - private String mCanonicalAddress; - // The RCS alias of this participant. This is different than the name of the contact in the - // Contacts app - i.e. RCS protocol allows users to define aliases for themselves that doesn't - // require other users to add them as contacts and give them a name. - private String mAlias; /** * Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} @@ -49,106 +33,95 @@ public class RcsParticipant implements Parcelable { * * @hide */ - public RcsParticipant(int id, @NonNull String canonicalAddress) { + public RcsParticipant(int id) { mId = id; - mCanonicalAddress = canonicalAddress; } /** - * @return Returns the canonical address (i.e. normalized phone number) for this participant + * @return Returns the canonical address (i.e. normalized phone number) for this + * {@link RcsParticipant} + * @throws RcsMessageStoreException if the value could not be read from the storage */ - public String getCanonicalAddress() { - return mCanonicalAddress; + @Nullable + @WorkerThread + public String getCanonicalAddress() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantCanonicalAddress(mId)); } /** - * Sets the canonical address for this participant and updates it in storage. - * @param canonicalAddress the canonical address to update to. + * @return Returns the alias for this {@link RcsParticipant}. Alias is usually the real name of + * the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal Profile Service + * Definition Document) + * @throws RcsMessageStoreException if the value could not be read from the storage */ + @Nullable @WorkerThread - public void setCanonicalAddress(@NonNull String canonicalAddress) { - Preconditions.checkNotNull(canonicalAddress); - if (canonicalAddress.equals(mCanonicalAddress)) { - return; - } - - mCanonicalAddress = canonicalAddress; - - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.updateRcsParticipantCanonicalAddress(mId, mCanonicalAddress); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re); - } + public String getAlias() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantAlias(mId)); } /** - * @return Returns the alias for this participant. Alias is usually the real name of the person - * themselves. + * Sets the alias for this {@link RcsParticipant} and persists it in storage. Alias is usually + * the real name of the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal + * Profile Service Definition Document) + * + * @param alias The alias to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage */ - public String getAlias() { - return mAlias; + @WorkerThread + public void setAlias(String alias) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantAlias(mId, alias)); } /** - * Sets the alias for this participant and persists it in storage. Alias is usually the real - * name of the person themselves. + * @return Returns the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for + * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device + * API 1.6 Specification) + * @throws RcsMessageStoreException if the value could not be read from the storage */ + @Nullable @WorkerThread - public void setAlias(String alias) { - if (TextUtils.equals(mAlias, alias)) { - return; - } - mAlias = alias; - - try { - IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs")); - if (iRcs != null) { - iRcs.updateRcsParticipantAlias(mId, mAlias); - } - } catch (RemoteException re) { - Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re); - } + public String getContactId() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantContactId(mId)); } /** - * Returns the row id of this participant. This is not meant to be part of the SDK + * Sets the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for + * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device + * API 1.6 Specification) * - * @hide + * @param contactId The contact ID to set to. + * @throws RcsMessageStoreException if the value could not be persisted into storage */ - public int getId() { - return mId; + @WorkerThread + public void setContactId(String contactId) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantContactId(mId, contactId)); } - public static final Creator<RcsParticipant> CREATOR = new Creator<RcsParticipant>() { - @Override - public RcsParticipant createFromParcel(Parcel in) { - return new RcsParticipant(in); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; } - - @Override - public RcsParticipant[] newArray(int size) { - return new RcsParticipant[size]; + if (!(obj instanceof RcsParticipant)) { + return false; } - }; + RcsParticipant other = (RcsParticipant) obj; - protected RcsParticipant(Parcel in) { - mId = in.readInt(); - mCanonicalAddress = in.readString(); - mAlias = in.readString(); + return mId == other.mId; } @Override - public int describeContents() { - return 0; + public int hashCode() { + return mId; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mId); - dest.writeString(mCanonicalAddress); - dest.writeString(mAlias); + /** + * Returns the row id of this participant. This is not meant to be part of the SDK + * + * @hide + */ + public int getId() { + return mId; } } diff --git a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java index b9ca5a86f84d..04cdf86df9c0 100644 --- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java +++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java @@ -15,27 +15,93 @@ */ package android.telephony.ims; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; /** - * An event that indicates an {@link RcsParticipant}'s alias was changed. - * @hide - TODO(sahinc) make this public + * An event that indicates an {@link RcsParticipant}'s alias was changed. Please see US18-2 - GSMA + * RCC.71 (RCS Universal Profile Service Definition Document) + * + * @hide - TODO(109759350) make this public */ -public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent { +public class RcsParticipantAliasChangedEvent extends RcsEvent { + // The ID of the participant that changed their alias + private int mParticipantId; + // The new alias of the above participant + private String mNewAlias; + + /** + * Creates a new {@link RcsParticipantAliasChangedEvent}. This event is not persisted into + * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called. + * + * @param timestamp The timestamp of when this event happened, in milliseconds passed after + * midnight, January 1st, 1970 UTC + * @param participant The {@link RcsParticipant} that got their alias changed + * @param newAlias The new alias the {@link RcsParticipant} has. + * @see RcsMessageStore#persistRcsEvent(RcsEvent) + */ + public RcsParticipantAliasChangedEvent(long timestamp, @NonNull RcsParticipant participant, + @Nullable String newAlias) { + super(timestamp); + mParticipantId = participant.getId(); + mNewAlias = newAlias; + } + + /** + * @hide - internal constructor for queries + */ + public RcsParticipantAliasChangedEvent(long timestamp, int participantId, + @Nullable String newAlias) { + super(timestamp); + mParticipantId = participantId; + mNewAlias = newAlias; + } + + /** + * @return Returns the {@link RcsParticipant} whose alias was changed. + */ + @NonNull + public RcsParticipant getParticipantId() { + return new RcsParticipant(mParticipantId); + } + + /** + * @return Returns the alias of the associated {@link RcsParticipant} after this event happened + */ + @Nullable + public String getNewAlias() { + return mNewAlias; + } + + /** + * Persists the event to the data store. + * + * @hide - not meant for public use. + */ + @Override + public void persist() throws RcsMessageStoreException { + RcsControllerCall.call(iRcs -> iRcs.createParticipantAliasChangedEvent( + getTimestamp(), getParticipantId().getId(), getNewAlias())); + } + public static final Creator<RcsParticipantAliasChangedEvent> CREATOR = new Creator<RcsParticipantAliasChangedEvent>() { - @Override - public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) { - return new RcsParticipantAliasChangedEvent(in); - } + @Override + public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) { + return new RcsParticipantAliasChangedEvent(in); + } - @Override - public RcsParticipantAliasChangedEvent[] newArray(int size) { - return new RcsParticipantAliasChangedEvent[size]; - } - }; + @Override + public RcsParticipantAliasChangedEvent[] newArray(int size) { + return new RcsParticipantAliasChangedEvent[size]; + } + }; protected RcsParticipantAliasChangedEvent(Parcel in) { + super(in); + mNewAlias = in.readString(); + mParticipantId = in.readInt(); } @Override @@ -45,5 +111,8 @@ public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent { @Override public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mNewAlias); + dest.writeInt(mParticipantId); } } diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl b/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl deleted file mode 100644 index c0a77897abd5..000000000000 --- a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsParticipantEvent; diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.java b/telephony/java/android/telephony/ims/RcsParticipantEvent.java deleted file mode 100644 index 371b8b723d0a..000000000000 --- a/telephony/java/android/telephony/ims/RcsParticipantEvent.java +++ /dev/null @@ -1,25 +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 android.telephony.ims; - -import android.os.Parcelable; - -/** - * An event that is associated with an {@link RcsParticipant} - * @hide - TODO(sahinc) make this public - */ -public abstract class RcsParticipantEvent implements Parcelable { -} diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl new file mode 100644 index 000000000000..ea8288cd9f6a --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsParticipantQueryParameters; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java new file mode 100644 index 000000000000..3611fc187fc4 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java @@ -0,0 +1,310 @@ +/* + * 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.telephony.ims; + +import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.security.InvalidParameterException; + +/** + * The parameters to pass into + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} in order to select a + * subset of {@link RcsThread}s present in the message store. + * + * @hide TODO - make the Builder and builder() public. The rest should stay internal only. + */ +public class RcsParticipantQueryParameters implements Parcelable { + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort the results in the order of + * creation time for faster query results + */ + public static final int SORT_BY_CREATION_ORDER = 0; + + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the + * {@link RcsParticipant} aliases + */ + public static final int SORT_BY_ALIAS = 1; + + /** + * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the + * {@link RcsParticipant} canonical addresses + */ + public static final int SORT_BY_CANONICAL_ADDRESS = 2; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_ALIAS, SORT_BY_CANONICAL_ADDRESS}) + public @interface SortingProperty { + } + + // The SQL "like" statement to filter against participant aliases + private String mAliasLike; + // The SQL "like" statement to filter against canonical addresses + private String mCanonicalAddressLike; + // The property to sort the result against + private @SortingProperty int mSortingProperty; + // Whether to sort the result in ascending order + private boolean mIsAscending; + // The number of results to be returned from the query + private int mLimit; + // Used to limit the results to participants of a single thread + private int mThreadId; + + /** + * @hide + */ + public static final String PARTICIPANT_QUERY_PARAMETERS_KEY = "participant_query_parameters"; + + RcsParticipantQueryParameters(int rcsThreadId, String aliasLike, String canonicalAddressLike, + @SortingProperty int sortingProperty, boolean isAscending, + int limit) { + mThreadId = rcsThreadId; + mAliasLike = aliasLike; + mCanonicalAddressLike = canonicalAddressLike; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + mLimit = limit; + } + + /** + * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get + * the thread that the result query should be limited to. + * + * As we do not expose any sort of integer ID's to public usage, this should be hidden. + * + * @hide - not meant for public use + */ + public int getThreadId() { + return mThreadId; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match + * {@link RcsParticipant}s with respect to their aliases + * + * @see RcsParticipant#getAlias() + */ + public String getAliasLike() { + return mAliasLike; + } + + /** + * @return Returns the SQL-inspired "LIKE" clause that will be used to match + * {@link RcsParticipant}s with respect to their canonical addresses. + * + * @see RcsParticipant#getCanonicalAddress() + */ + public String getCanonicalAddressLike() { + return mCanonicalAddressLike; + } + + /** + * @return Returns the number of {@link RcsParticipant}s to be returned from the query. A value + * of 0 means there is no set limit. + */ + public int getLimit() { + return mLimit; + } + + /** + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty + */ + public int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { + return mIsAscending; + } + + /** + * A helper class to build the {@link RcsParticipantQueryParameters}. + */ + public static class Builder { + private String mAliasLike; + private String mCanonicalAddressLike; + private @SortingProperty int mSortingProperty; + private boolean mIsAscending; + private int mLimit = 100; + private int mThreadId; + + /** + * Creates a new builder for {@link RcsParticipantQueryParameters} to be used in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} + */ + public Builder() { + // empty implementation + } + + /** + * Limits the resulting {@link RcsParticipant}s to only the given {@link RcsThread} + * + * @param rcsThread The thread that the participants should be searched in. + * @return The same {@link Builder} to chain methods. + */ + @CheckResult + public Builder setThread(RcsThread rcsThread) { + mThreadId = rcsThread.getThreadId(); + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with participant aliases. Using a percent + * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore + * ('_') wildcard matches any single character. Not using any wildcards would only perform a + * string match.The input string is case-insensitive. + * + * The input "An%e" would match {@link RcsParticipant}s with names Anne, Annie, Antonie, + * while the input "An_e" would only match Anne. + * + * @param likeClause The like clause to use for matching {@link RcsParticipant} aliases. + * @return The same {@link Builder} to chain methods + */ + @CheckResult + public Builder setAliasLike(String likeClause) { + mAliasLike = likeClause; + return this; + } + + /** + * Sets an SQL-inspired "like" clause to match with participant addresses. Using a percent + * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore + * ('_') wildcard matches any single character. Not using any wildcards would only perform a + * string match. The input string is case-insensitive. + * + * The input "+999%111" would match {@link RcsParticipant}s with addresses like "+9995111" + * or "+99955555111", while the input "+999_111" would only match "+9995111". + * + * @param likeClause The like clause to use for matching {@link RcsParticipant} canonical + * addresses. + * @return The same {@link Builder} to chain methods + */ + @CheckResult + public Builder setCanonicalAddressLike(String likeClause) { + mCanonicalAddressLike = likeClause; + return this; + } + + /** + * Desired number of threads to be returned from the query. Passing in 0 will return all + * existing threads at once. The limit defaults to 100. + * + * @param limit The number to limit the query result to. + * @return The same instance of the builder to chain parameters. + * @throws InvalidParameterException If the given limit is negative. + */ + @CheckResult + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { + if (limit < 0) { + throw new InvalidParameterException("The query limit must be non-negative"); + } + + mLimit = limit; + return this; + } + + /** + * Sets the property where the results should be sorted against. Defaults to + * {@link RcsParticipantQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER} + * + * @param sortingProperty against which property the results should be sorted + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending + * + * @param isAscending whether the results should be sorted ascending + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortDirection(boolean isAscending) { + mIsAscending = isAscending; + return this; + } + + /** + * Builds the {@link RcsParticipantQueryParameters} to use in + * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} + * + * @return An instance of {@link RcsParticipantQueryParameters} to use with the participant + * query. + */ + public RcsParticipantQueryParameters build() { + return new RcsParticipantQueryParameters(mThreadId, mAliasLike, mCanonicalAddressLike, + mSortingProperty, mIsAscending, mLimit); + } + } + + /** + * Parcelable boilerplate below. + */ + protected RcsParticipantQueryParameters(Parcel in) { + mAliasLike = in.readString(); + mCanonicalAddressLike = in.readString(); + mSortingProperty = in.readInt(); + mIsAscending = in.readByte() == 1; + mLimit = in.readInt(); + mThreadId = in.readInt(); + } + + public static final Creator<RcsParticipantQueryParameters> CREATOR = + new Creator<RcsParticipantQueryParameters>() { + @Override + public RcsParticipantQueryParameters createFromParcel(Parcel in) { + return new RcsParticipantQueryParameters(in); + } + + @Override + public RcsParticipantQueryParameters[] newArray(int size) { + return new RcsParticipantQueryParameters[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mAliasLike); + dest.writeString(mCanonicalAddressLike); + dest.writeInt(mSortingProperty); + dest.writeByte((byte) (mIsAscending ? 1 : 0)); + dest.writeInt(mLimit); + dest.writeInt(mThreadId); + } + +} diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl new file mode 100644 index 000000000000..db5c00c8ce00 --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsParticipantQueryResult; diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java new file mode 100644 index 000000000000..2f4ab465a6cf --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java @@ -0,0 +1,105 @@ +/* + * 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.telephony.ims; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; + +/** + * The result of a {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} + * call. This class allows getting the token for querying the next batch of participants in order to + * prevent handling large amounts of data at once. + * + * @hide + */ +public class RcsParticipantQueryResult implements Parcelable { + // A token for the caller to continue their query for the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The list of participant IDs returned with this query + private List<Integer> mParticipants; + + /** + * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} + * to create query results + * + * @hide + */ + public RcsParticipantQueryResult( + RcsQueryContinuationToken continuationToken, + List<Integer> participants) { + mContinuationToken = continuationToken; + mParticipants = participants; + } + + /** + * Returns a token to call + * {@link RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)} + * to get the next batch of {@link RcsParticipant}s. + */ + @Nullable + public RcsQueryContinuationToken getContinuationToken() { + return mContinuationToken; + } + + /** + * Returns all the {@link RcsParticipant}s in the current query result. Call {@link + * RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)} to get the next + * batch of {@link RcsParticipant}s. + */ + @NonNull + public List<RcsParticipant> getParticipants() { + List<RcsParticipant> participantList = new ArrayList<>(); + for (Integer participantId : mParticipants) { + participantList.add(new RcsParticipant(participantId)); + } + + return participantList; + } + + protected RcsParticipantQueryResult(Parcel in) { + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + } + + public static final Creator<RcsParticipantQueryResult> CREATOR = + new Creator<RcsParticipantQueryResult>() { + @Override + public RcsParticipantQueryResult createFromParcel(Parcel in) { + return new RcsParticipantQueryResult(in); + } + + @Override + public RcsParticipantQueryResult[] newArray(int size) { + return new RcsParticipantQueryResult[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mContinuationToken, flags); + } +} diff --git a/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl new file mode 100644 index 000000000000..319379a462de --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl @@ -0,0 +1,20 @@ +/* + * + * Copyright 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.telephony.ims; + +parcelable RcsQueryContinuationToken; diff --git a/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java new file mode 100644 index 000000000000..e880651d6cdb --- /dev/null +++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java @@ -0,0 +1,151 @@ +/* + * 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.telephony.ims; + +import android.annotation.IntDef; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * This interface allows using the same implementation for continuation token usage in + * {@link com.android.providers.telephony.RcsProvider} + * @hide - TODO make getQueryType() and types public - the rest should stay internal + */ +public class RcsQueryContinuationToken implements Parcelable { + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsEvent} queries + */ + public static final int EVENT_QUERY_CONTINUATION_TOKEN_TYPE = 0; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsMessage} queries + */ + public static final int MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE = 1; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsParticipant} queries + */ + public static final int PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE = 2; + + /** + * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing + * {@link RcsThread} queries + */ + public static final int THREAD_QUERY_CONTINUATION_TOKEN_TYPE = 3; + + /** + * @hide - not meant for public use + */ + public static final String QUERY_CONTINUATION_TOKEN = "query_continuation_token"; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({EVENT_QUERY_CONTINUATION_TOKEN_TYPE, MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE, + PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE, THREAD_QUERY_CONTINUATION_TOKEN_TYPE}) + public @interface ContinuationTokenType {} + + // The type of query this token should allow to continue + private @ContinuationTokenType int mQueryType; + // The raw query string for the initial query + private final String mRawQuery; + // The number of results that is returned with each query + private final int mLimit; + // The offset value that this query should start the query from + private int mOffset; + + /** + * @hide + */ + public RcsQueryContinuationToken(@ContinuationTokenType int queryType, String rawQuery, + int limit, int offset) { + mQueryType = queryType; + mRawQuery = rawQuery; + mLimit = limit; + mOffset = offset; + } + + /** + * Returns the original raw query used on {@link com.android.providers.telephony.RcsProvider} + * @hide + */ + public String getRawQuery() { + return mRawQuery; + } + + /** + * Returns which index this continuation query should start from + * @hide + */ + public int getOffset() { + return mOffset; + } + + /** + * Increments the offset by the amount of result rows returned with the continuation query for + * the next query. + * @hide + */ + public void incrementOffset() { + mOffset += mLimit; + } + + /** + * Returns the type of query that this {@link RcsQueryContinuationToken} is intended to be used + * to continue. + */ + public @ContinuationTokenType int getQueryType() { + return mQueryType; + } + + protected RcsQueryContinuationToken(Parcel in) { + mQueryType = in.readInt(); + mRawQuery = in.readString(); + mLimit = in.readInt(); + mOffset = in.readInt(); + } + + public static final Creator<RcsQueryContinuationToken> CREATOR = + new Creator<RcsQueryContinuationToken>() { + @Override + public RcsQueryContinuationToken createFromParcel(Parcel in) { + return new RcsQueryContinuationToken(in); + } + + @Override + public RcsQueryContinuationToken[] newArray(int size) { + return new RcsQueryContinuationToken[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mQueryType); + dest.writeString(mRawQuery); + dest.writeInt(mLimit); + dest.writeInt(mOffset); + } +} diff --git a/telephony/java/android/telephony/ims/RcsTextPart.aidl b/telephony/java/android/telephony/ims/RcsTextPart.aidl deleted file mode 100644 index 4f9fe1fe26fe..000000000000 --- a/telephony/java/android/telephony/ims/RcsTextPart.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsTextPart; diff --git a/telephony/java/android/telephony/ims/RcsTextPart.java b/telephony/java/android/telephony/ims/RcsTextPart.java deleted file mode 100644 index 2a72df17f32a..000000000000 --- a/telephony/java/android/telephony/ims/RcsTextPart.java +++ /dev/null @@ -1,48 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * A part of a composite {@link RcsMessage} that holds a string - * @hide - TODO(sahinc) make this public - */ -public class RcsTextPart extends RcsPart { - public static final Creator<RcsTextPart> CREATOR = new Creator<RcsTextPart>() { - @Override - public RcsTextPart createFromParcel(Parcel in) { - return new RcsTextPart(in); - } - - @Override - public RcsTextPart[] newArray(int size) { - return new RcsTextPart[size]; - } - }; - - protected RcsTextPart(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThread.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl deleted file mode 100644 index d9cf6dbc0ff0..000000000000 --- a/telephony/java/android/telephony/ims/RcsThread.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony; - -parcelable RcsThread;
\ No newline at end of file diff --git a/telephony/java/android/telephony/ims/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java index c0a0d946d204..238f5e7ce625 100644 --- a/telephony/java/android/telephony/ims/RcsThread.java +++ b/telephony/java/android/telephony/ims/RcsThread.java @@ -16,60 +16,117 @@ package android.telephony.ims; -import android.os.Parcel; -import android.os.Parcelable; -import android.util.Log; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_GROUP; + +import android.annotation.NonNull; +import android.annotation.WorkerThread; + +import com.android.internal.annotations.VisibleForTesting; /** * RcsThread represents a single RCS conversation thread. It holds messages that were sent and * received and events that occurred on that thread. - * @hide - TODO(sahinc) make this public + * + * @hide - TODO(109759350) make this public */ -public abstract class RcsThread implements Parcelable { - // Since this is an abstract class that gets parcelled, the sub-classes need to write these - // magic values into the parcel so that we know which type to unparcel into. - protected static final int RCS_1_TO_1_TYPE = 998; - protected static final int RCS_GROUP_TYPE = 999; - +public abstract class RcsThread { + // The rcs_participant_thread_id that represents this thread in the database protected int mThreadId; + /** + * @hide + */ protected RcsThread(int threadId) { mThreadId = threadId; } - protected RcsThread(Parcel in) { - mThreadId = in.readInt(); + /** + * @return Returns the summary of the latest message in this {@link RcsThread} packaged in an + * {@link RcsMessageSnippet} object + */ + @WorkerThread + @NonNull + public RcsMessageSnippet getSnippet() throws RcsMessageStoreException { + return RcsControllerCall.call(iRcs -> iRcs.getMessageSnippet(mThreadId)); + } + + /** + * Adds a new {@link RcsIncomingMessage} to this RcsThread and persists it in storage. + * + * @throws RcsMessageStoreException if the message could not be persisted into storage. + */ + @WorkerThread + @NonNull + public RcsIncomingMessage addIncomingMessage( + @NonNull RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters) + throws RcsMessageStoreException { + return new RcsIncomingMessage(RcsControllerCall.call(iRcs -> iRcs.addIncomingMessage( + mThreadId, rcsIncomingMessageCreationParameters))); } - public static final Creator<RcsThread> CREATOR = new Creator<RcsThread>() { - @Override - public RcsThread createFromParcel(Parcel in) { - int type = in.readInt(); + /** + * Adds a new {@link RcsOutgoingMessage} to this RcsThread and persists it in storage. + * + * @throws RcsMessageStoreException if the message could not be persisted into storage. + */ + @WorkerThread + @NonNull + public RcsOutgoingMessage addOutgoingMessage( + @NonNull RcsMessageCreationParameters rcsMessageCreationParameters) + throws RcsMessageStoreException { + int messageId = RcsControllerCall.call(iRcs -> iRcs.addOutgoingMessage( + mThreadId, rcsMessageCreationParameters)); - switch (type) { - case RCS_1_TO_1_TYPE: - return new Rcs1To1Thread(in); - case RCS_GROUP_TYPE: - return new RcsGroupThread(in); - default: - Log.e(RcsMessageStore.TAG, "Cannot unparcel RcsThread, wrong type: " + type); - } - return null; - } + return new RcsOutgoingMessage(messageId); + } + + /** + * Deletes an {@link RcsMessage} from this RcsThread and updates the storage. + * + * @param rcsMessage The message to delete from the thread + * @throws RcsMessageStoreException if the message could not be deleted + */ + @WorkerThread + public void deleteMessage(@NonNull RcsMessage rcsMessage) throws RcsMessageStoreException { + RcsControllerCall.callWithNoReturn( + iRcs -> iRcs.deleteMessage(rcsMessage.getId(), rcsMessage.isIncoming(), mThreadId, + isGroup())); + } + + /** + * Convenience function for loading all the {@link RcsMessage}s in this {@link RcsThread}. For + * a more detailed and paginated query, please use + * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} + * + * @return Loads the {@link RcsMessage}s in this thread and returns them in an immutable list. + * @throws RcsMessageStoreException if the messages could not be read from the storage + */ + @WorkerThread + @NonNull + public RcsMessageQueryResult getMessages() throws RcsMessageStoreException { + RcsMessageQueryParameters queryParameters = + new RcsMessageQueryParameters.Builder().setThread(this).build(); + return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters)); + } - @Override - public RcsThread[] newArray(int size) { - return new RcsThread[0]; - } - }; + /** + * @return Returns whether this is a group thread or not + */ + public abstract boolean isGroup(); - @Override - public int describeContents() { - return 0; + /** + * @hide + */ + @VisibleForTesting + public int getThreadId() { + return mThreadId; } - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mThreadId); + /** + * @hide + */ + public int getThreadType() { + return isGroup() ? THREAD_TYPE_GROUP : THREAD_TYPE_1_TO_1; } } diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadEvent.aidl deleted file mode 100644 index 4a40d8906bbb..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsThreadEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.java b/telephony/java/android/telephony/ims/RcsThreadEvent.java deleted file mode 100644 index e10baab9d8c5..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadEvent.java +++ /dev/null @@ -1,25 +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 android.telephony.ims; - -import android.os.Parcelable; - -/** - * An event that happened on an {@link RcsThread}. - * @hide - TODO(sahinc) make this public - */ -public abstract class RcsThreadEvent implements Parcelable { -} diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl deleted file mode 100644 index 82d985df4c6c..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsThreadIconChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java deleted file mode 100644 index b308fef46435..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java +++ /dev/null @@ -1,49 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * An event that indicates an {@link RcsGroupThread}'s icon was changed. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadIconChangedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadIconChangedEvent> CREATOR = - new Creator<RcsThreadIconChangedEvent>() { - @Override - public RcsThreadIconChangedEvent createFromParcel(Parcel in) { - return new RcsThreadIconChangedEvent(in); - } - - @Override - public RcsThreadIconChangedEvent[] newArray(int size) { - return new RcsThreadIconChangedEvent[size]; - } - }; - - protected RcsThreadIconChangedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl deleted file mode 100644 index 54a311d02958..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsThreadNameChangedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java deleted file mode 100644 index 6f5cfdf3b4c4..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java +++ /dev/null @@ -1,49 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * An event that indicates an {@link RcsGroupThread}'s name was changed. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadNameChangedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadNameChangedEvent> CREATOR = - new Creator<RcsThreadNameChangedEvent>() { - @Override - public RcsThreadNameChangedEvent createFromParcel(Parcel in) { - return new RcsThreadNameChangedEvent(in); - } - - @Override - public RcsThreadNameChangedEvent[] newArray(int size) { - return new RcsThreadNameChangedEvent[size]; - } - }; - - protected RcsThreadNameChangedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl deleted file mode 100644 index 047a42466ee7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsThreadParticipantJoinedEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java deleted file mode 100644 index 5c4073c430e7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java +++ /dev/null @@ -1,49 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * An event that indicates an RCS participant has joined an {@link RcsGroupThread}. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadParticipantJoinedEvent extends RcsThreadEvent { - public static final Creator<RcsThreadParticipantJoinedEvent> CREATOR = - new Creator<RcsThreadParticipantJoinedEvent>() { - @Override - public RcsThreadParticipantJoinedEvent createFromParcel(Parcel in) { - return new RcsThreadParticipantJoinedEvent(in); - } - - @Override - public RcsThreadParticipantJoinedEvent[] newArray(int size) { - return new RcsThreadParticipantJoinedEvent[size]; - } - }; - - protected RcsThreadParticipantJoinedEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl deleted file mode 100644 index 52f9bbd3cd93..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* - * - * Copyright 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.telephony.ims; - -parcelable RcsThreadParticipantLeftEvent; diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java deleted file mode 100644 index 4bf86b90ebb7..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java +++ /dev/null @@ -1,49 +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 android.telephony.ims; - -import android.os.Parcel; - -/** - * An event that indicates an RCS participant has left an {@link RcsGroupThread}. - * @hide - TODO(sahinc) make this public - */ -public class RcsThreadParticipantLeftEvent extends RcsThreadEvent { - public static final Creator<RcsThreadParticipantLeftEvent> CREATOR = - new Creator<RcsThreadParticipantLeftEvent>() { - @Override - public RcsThreadParticipantLeftEvent createFromParcel(Parcel in) { - return new RcsThreadParticipantLeftEvent(in); - } - - @Override - public RcsThreadParticipantLeftEvent[] newArray(int size) { - return new RcsThreadParticipantLeftEvent[size]; - } - }; - - protected RcsThreadParticipantLeftEvent(Parcel in) { - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - } -} diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl deleted file mode 100644 index 7bcebfa08fcb..000000000000 --- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl +++ /dev/null @@ -1,20 +0,0 @@ -/* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ - -package android.telephony.ims; - -parcelable RcsThreadQueryContinuationToken; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl index feb2d4dec094..52e73ce02407 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl @@ -1,19 +1,19 @@ /* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + * + * Copyright 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.telephony.ims; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java index f2c4ab1884ca..4aa42073b8f8 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java +++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -17,72 +17,133 @@ package android.telephony.ims; import android.annotation.CheckResult; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.security.InvalidParameterException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; /** * The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} in * order to select a subset of {@link RcsThread}s present in the message store. + * * @hide TODO - make the Builder and builder() public. The rest should stay internal only. */ public class RcsThreadQueryParameters implements Parcelable { - private final boolean mIsGroup; - private final Set<RcsParticipant> mRcsParticipants; + /** + * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} return + * {@link RcsGroupThread}s. + */ + public static final int THREAD_TYPE_GROUP = 0x0001; + + /** + * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make + * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} return + * {@link Rcs1To1Thread}s. + */ + public static final int THREAD_TYPE_1_TO_1 = 0x0002; + + // The type of threads to be filtered with the query + private final int mThreadType; + // The list of participants that are expected in the resulting threads + private final List<Integer> mRcsParticipantIds; + // The number of RcsThread's that should be returned with this query private final int mLimit; + // The property which the result of the query should be sorted against + private final @SortingProperty int mSortingProperty; + // Whether the sorting should be done in ascending private final boolean mIsAscending; - RcsThreadQueryParameters(boolean isGroup, Set<RcsParticipant> participants, int limit, - boolean isAscending) { - mIsGroup = isGroup; - mRcsParticipants = participants; - mLimit = limit; - mIsAscending = isAscending; - } + /** + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted in the order of {@link RcsThread} creation time for faster results. + */ + public static final int SORT_BY_CREATION_ORDER = 0; /** - * Returns a new builder to build a query with. - * TODO - make public + * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should + * be sorted according to the timestamp of {@link RcsThread#getSnippet()} */ - public static Builder builder() { - return new Builder(); + public static final int SORT_BY_TIMESTAMP = 1; + + @Retention(RetentionPolicy.SOURCE) + @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP}) + public @interface SortingProperty { } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the list of participants. * @hide */ - public Set<RcsParticipant> getRcsParticipants() { - return mRcsParticipants; + public static final String THREAD_QUERY_PARAMETERS_KEY = "thread_query_parameters"; + + RcsThreadQueryParameters(int threadType, Set<RcsParticipant> participants, + int limit, int sortingProperty, boolean isAscending) { + mThreadType = threadType; + mRcsParticipantIds = convertParticipantSetToIdList(participants); + mLimit = limit; + mSortingProperty = sortingProperty; + mIsAscending = isAscending; + } + + private static List<Integer> convertParticipantSetToIdList(Set<RcsParticipant> participants) { + List<Integer> ids = new ArrayList<>(participants.size()); + for (RcsParticipant participant : participants) { + ids.add(participant.getId()); + } + return ids; } /** * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * whether group threads should be queried - * @hide + * the list of participant IDs. + * + * As we don't expose any integer ID's to API users, this should stay hidden + * + * @hide - not meant for public use */ - public boolean isGroupThread() { - return mIsGroup; + public List<Integer> getRcsParticipantsIds() { + return Collections.unmodifiableList(mRcsParticipantIds); } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get - * the number of tuples the result query should be limited to. + * @return Returns the bitmask flag for types of {@link RcsThread}s that this query should + * return. + */ + public int getThreadType() { + return mThreadType; + } + + /** + * @return Returns the number of {@link RcsThread}s to be returned from the query. A value + * of 0 means there is no set limit. */ public int getLimit() { return mLimit; } /** - * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to - * determine the sort order. + * @return Returns the property that will be used to sort the result against. + * @see SortingProperty */ - public boolean isAscending() { + public @SortingProperty int getSortingProperty() { + return mSortingProperty; + } + + /** + * @return Returns {@code true} if the result set will be sorted in ascending order, + * {@code false} if it will be sorted in descending order. + */ + public boolean getSortDirection() { return mIsAscending; } @@ -90,64 +151,74 @@ public class RcsThreadQueryParameters implements Parcelable { * A helper class to build the {@link RcsThreadQueryParameters}. */ public static class Builder { - private boolean mIsGroupThread; + private int mThreadType; private Set<RcsParticipant> mParticipants; private int mLimit = 100; + private @SortingProperty int mSortingProperty; private boolean mIsAscending; /** - * Package private constructor for {@link RcsThreadQueryParameters.Builder}. To obtain this, - * {@link RcsThreadQueryParameters#builder()} needs to be called. + * Constructs a {@link RcsThreadQueryParameters.Builder} to help build an + * {@link RcsThreadQueryParameters} */ - Builder() { + public Builder() { mParticipants = new HashSet<>(); } /** * Limits the query to only return group threads. - * @param isGroupThread Whether to limit the query result to group threads. + * + * @param threadType Whether to limit the query result to group threads. * @return The same instance of the builder to chain parameters. + * @see RcsThreadQueryParameters#THREAD_TYPE_GROUP + * @see RcsThreadQueryParameters#THREAD_TYPE_1_TO_1 */ @CheckResult - public Builder isGroupThread(boolean isGroupThread) { - mIsGroupThread = isGroupThread; + public Builder setThreadType(int threadType) { + mThreadType = threadType; return this; } /** - * Limits the query to only return threads that contain the given participant. + * Limits the query to only return threads that contain the given participant. If this + * property was not set, participants will not be taken into account while querying for + * threads. + * * @param participant The participant that must be included in all of the returned threads. * @return The same instance of the builder to chain parameters. */ @CheckResult - public Builder withParticipant(RcsParticipant participant) { + public Builder setParticipant(@NonNull RcsParticipant participant) { mParticipants.add(participant); return this; } /** - * Limits the query to only return threads that contain the given list of participants. + * Limits the query to only return threads that contain the given list of participants. If + * this property was not set, participants will not be taken into account while querying + * for threads. + * * @param participants An iterable list of participants that must be included in all of the * returned threads. * @return The same instance of the builder to chain parameters. */ @CheckResult - public Builder withParticipants(Iterable<RcsParticipant> participants) { - for (RcsParticipant participant : participants) { - mParticipants.add(participant); - } + public Builder setParticipants(@NonNull List<RcsParticipant> participants) { + mParticipants.addAll(participants); return this; } /** * Desired number of threads to be returned from the query. Passing in 0 will return all * existing threads at once. The limit defaults to 100. + * * @param limit The number to limit the query result to. * @return The same instance of the builder to chain parameters. * @throws InvalidParameterException If the given limit is negative. */ @CheckResult - public Builder limitResultsTo(int limit) throws InvalidParameterException { + public Builder setResultLimit(@IntRange(from = 0) int limit) + throws InvalidParameterException { if (limit < 0) { throw new InvalidParameterException("The query limit must be non-negative"); } @@ -157,15 +228,26 @@ public class RcsThreadQueryParameters implements Parcelable { } /** - * Sorts the results returned from the query via thread IDs. + * Sets the property where the results should be sorted against. Defaults to + * {@link SortingProperty#SORT_BY_CREATION_ORDER} * - * TODO - add sorting support for other fields + * @param sortingProperty whether to sort in ascending order or not + * @return The same instance of the builder to chain parameters. + */ + @CheckResult + public Builder setSortProperty(@SortingProperty int sortingProperty) { + mSortingProperty = sortingProperty; + return this; + } + + /** + * Sets whether the results should be sorted ascending or descending * - * @param isAscending whether to sort in ascending order or not + * @param isAscending whether the results should be sorted ascending * @return The same instance of the builder to chain parameters. */ @CheckResult - public Builder sort(boolean isAscending) { + public Builder setSortDirection(boolean isAscending) { mIsAscending = isAscending; return this; } @@ -177,8 +259,8 @@ public class RcsThreadQueryParameters implements Parcelable { * @return An instance of {@link RcsThreadQueryParameters} to use with the thread query. */ public RcsThreadQueryParameters build() { - return new RcsThreadQueryParameters( - mIsGroupThread, mParticipants, mLimit, mIsAscending); + return new RcsThreadQueryParameters(mThreadType, mParticipants, mLimit, + mSortingProperty, mIsAscending); } } @@ -186,14 +268,12 @@ public class RcsThreadQueryParameters implements Parcelable { * Parcelable boilerplate below. */ protected RcsThreadQueryParameters(Parcel in) { - mIsGroup = in.readBoolean(); - - ArrayList<RcsParticipant> participantArrayList = new ArrayList<>(); - in.readTypedList(participantArrayList, RcsParticipant.CREATOR); - mRcsParticipants = new HashSet<>(participantArrayList); - + mThreadType = in.readInt(); + mRcsParticipantIds = new ArrayList<>(); + in.readList(mRcsParticipantIds, Integer.class.getClassLoader()); mLimit = in.readInt(); - mIsAscending = in.readBoolean(); + mSortingProperty = in.readInt(); + mIsAscending = in.readByte() == 1; } public static final Creator<RcsThreadQueryParameters> CREATOR = @@ -216,10 +296,10 @@ public class RcsThreadQueryParameters implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeBoolean(mIsGroup); - dest.writeTypedList(new ArrayList<>(mRcsParticipants)); + dest.writeInt(mThreadType); + dest.writeList(mRcsParticipantIds); dest.writeInt(mLimit); - dest.writeBoolean(mIsAscending); + dest.writeInt(mSortingProperty); + dest.writeByte((byte) (mIsAscending ? 1 : 0)); } - } diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl index 4b06529d1294..b1d5cf4c7211 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl +++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl @@ -1,19 +1,19 @@ /* -** -** Copyright 2018, The Android Open Source Project -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -*/ + * + * Copyright 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.telephony.ims; diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java index 47715f8410d6..6515933fff0e 100644 --- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java +++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018 The Android Open Source Project + * 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. @@ -16,22 +16,30 @@ package android.telephony.ims; +import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1; + +import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import com.android.ims.RcsTypeIdPair; + +import java.util.ArrayList; import java.util.List; /** - * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken, - * RcsThreadQueryParameters)} + * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} * call. This class allows getting the token for querying the next batch of threads in order to * prevent handling large amounts of data at once. * * @hide */ public class RcsThreadQueryResult implements Parcelable { - private RcsThreadQueryContinuationToken mContinuationToken; - private List<RcsThread> mRcsThreads; + // A token for the caller to continue their query for the next batch of results + private RcsQueryContinuationToken mContinuationToken; + // The list of thread IDs returned with this query + private List<RcsTypeIdPair> mRcsThreadIds; /** * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController} @@ -40,31 +48,47 @@ public class RcsThreadQueryResult implements Parcelable { * @hide */ public RcsThreadQueryResult( - RcsThreadQueryContinuationToken continuationToken, List<RcsThread> rcsThreads) { + RcsQueryContinuationToken continuationToken, + List<RcsTypeIdPair> rcsThreadIds) { mContinuationToken = continuationToken; - mRcsThreads = rcsThreads; + mRcsThreadIds = rcsThreadIds; } /** * Returns a token to call - * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} + * {@link RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)} * to get the next batch of {@link RcsThread}s. */ - public RcsThreadQueryContinuationToken nextChunkToken() { + @Nullable + public RcsQueryContinuationToken getContinuationToken() { return mContinuationToken; } /** * Returns all the RcsThreads in the current query result. Call {@link - * RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} to get the next batch of + * RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)} to get the next batch of * {@link RcsThread}s. */ + @NonNull public List<RcsThread> getThreads() { - return mRcsThreads; + List<RcsThread> rcsThreads = new ArrayList<>(); + + for (RcsTypeIdPair typeIdPair : mRcsThreadIds) { + if (typeIdPair.getType() == THREAD_TYPE_1_TO_1) { + rcsThreads.add(new Rcs1To1Thread(typeIdPair.getId())); + } else { + rcsThreads.add(new RcsGroupThread(typeIdPair.getId())); + } + } + + return rcsThreads; } protected RcsThreadQueryResult(Parcel in) { - // TODO - implement + mContinuationToken = in.readParcelable( + RcsQueryContinuationToken.class.getClassLoader()); + mRcsThreadIds = new ArrayList<>(); + in.readList(mRcsThreadIds, Integer.class.getClassLoader()); } public static final Creator<RcsThreadQueryResult> CREATOR = @@ -87,6 +111,7 @@ public class RcsThreadQueryResult implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - // TODO - implement + dest.writeParcelable(mContinuationToken, flags); + dest.writeList(mRcsThreadIds); } } diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl index 0c958ba719f3..a399786dec54 100644 --- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl +++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl @@ -16,9 +16,18 @@ package android.telephony.ims.aidl; -import android.telephony.ims.RcsParticipant; -import android.telephony.ims.Rcs1To1Thread; -import android.telephony.ims.RcsThreadQueryContinuationToken; +import android.net.Uri; +import android.telephony.ims.RcsEventQueryParameters; +import android.telephony.ims.RcsEventQueryResult; +import android.telephony.ims.RcsFileTransferCreationParameters; +import android.telephony.ims.RcsIncomingMessageCreationParameters; +import android.telephony.ims.RcsMessageCreationParameters; +import android.telephony.ims.RcsMessageSnippet; +import android.telephony.ims.RcsMessageQueryParameters; +import android.telephony.ims.RcsMessageQueryResult; +import android.telephony.ims.RcsParticipantQueryParameters; +import android.telephony.ims.RcsParticipantQueryResult; +import android.telephony.ims.RcsQueryContinuationToken; import android.telephony.ims.RcsThreadQueryParameters; import android.telephony.ims.RcsThreadQueryResult; @@ -27,23 +36,231 @@ import android.telephony.ims.RcsThreadQueryResult; * {@hide} */ interface IRcs { + ///////////////////////// // RcsMessageStore APIs + ///////////////////////// RcsThreadQueryResult getRcsThreads(in RcsThreadQueryParameters queryParameters); RcsThreadQueryResult getRcsThreadsWithToken( - in RcsThreadQueryContinuationToken continuationToken); + in RcsQueryContinuationToken continuationToken); - void deleteThread(int threadId); + RcsParticipantQueryResult getParticipants(in RcsParticipantQueryParameters queryParameters); - Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant); + RcsParticipantQueryResult getParticipantsWithToken( + in RcsQueryContinuationToken continuationToken); + RcsMessageQueryResult getMessages(in RcsMessageQueryParameters queryParameters); + + RcsMessageQueryResult getMessagesWithToken( + in RcsQueryContinuationToken continuationToken); + + RcsEventQueryResult getEvents(in RcsEventQueryParameters queryParameters); + + RcsEventQueryResult getEventsWithToken( + in RcsQueryContinuationToken continuationToken); + + // returns true if the thread was successfully deleted + boolean deleteThread(int threadId, int threadType); + + // Creates an Rcs1To1Thread and returns its row ID + int createRcs1To1Thread(int participantId); + + // Creates an RcsGroupThread and returns its row ID + int createGroupThread(in int[] participantIds, String groupName, in Uri groupIcon); + + ///////////////////////// // RcsThread APIs - int getMessageCount(int rcsThreadId); + ///////////////////////// + + // Creates a new RcsIncomingMessage on the given thread and returns its row ID + int addIncomingMessage(int rcsThreadId, + in RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters); + + // Creates a new RcsOutgoingMessage on the given thread and returns its row ID + int addOutgoingMessage(int rcsThreadId, + in RcsMessageCreationParameters rcsMessageCreationParameters); + + // TODO: modify RcsProvider URI's to allow deleting a message without specifying its thread + void deleteMessage(int rcsMessageId, boolean isIncoming, int rcsThreadId, boolean isGroup); + + RcsMessageSnippet getMessageSnippet(int rcsThreadId); + + ///////////////////////// + // Rcs1To1Thread APIs + ///////////////////////// + void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId); + + long get1To1ThreadFallbackThreadId(int rcsThreadId); + + int get1To1ThreadOtherParticipantId(int rcsThreadId); + + ///////////////////////// + // RcsGroupThread APIs + ///////////////////////// + void setGroupThreadName(int rcsThreadId, String groupName); + + String getGroupThreadName(int rcsThreadId); + + void setGroupThreadIcon(int rcsThreadId, in Uri groupIcon); + + Uri getGroupThreadIcon(int rcsThreadId); + + void setGroupThreadOwner(int rcsThreadId, int participantId); + + int getGroupThreadOwner(int rcsThreadId); + + void setGroupThreadConferenceUri(int rcsThreadId, in Uri conferenceUri); + + Uri getGroupThreadConferenceUri(int rcsThreadId); + void addParticipantToGroupThread(int rcsThreadId, int participantId); + + void removeParticipantFromGroupThread(int rcsThreadId, int participantId); + + ///////////////////////// // RcsParticipant APIs - RcsParticipant createRcsParticipant(String canonicalAddress); + ///////////////////////// + + // Creates a new RcsParticipant and returns its rowId + int createRcsParticipant(String canonicalAddress, String alias); + + String getRcsParticipantCanonicalAddress(int participantId); + + String getRcsParticipantAlias(int participantId); + + void setRcsParticipantAlias(int id, String alias); + + String getRcsParticipantContactId(int participantId); + + void setRcsParticipantContactId(int participantId, String contactId); + + ///////////////////////// + // RcsMessage APIs + ///////////////////////// + void setMessageSubId(int messageId, boolean isIncoming, int subId); + + int getMessageSubId(int messageId, boolean isIncoming); + + void setMessageStatus(int messageId, boolean isIncoming, int status); + + int getMessageStatus(int messageId, boolean isIncoming); + + void setMessageOriginationTimestamp(int messageId, boolean isIncoming, long originationTimestamp); + + long getMessageOriginationTimestamp(int messageId, boolean isIncoming); + + void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId); + + String getGlobalMessageIdForMessage(int messageId, boolean isIncoming); + + void setMessageArrivalTimestamp(int messageId, boolean isIncoming, long arrivalTimestamp); + + long getMessageArrivalTimestamp(int messageId, boolean isIncoming); + + void setMessageSeenTimestamp(int messageId, boolean isIncoming, long seenTimestamp); + + long getMessageSeenTimestamp(int messageId, boolean isIncoming); + + void setTextForMessage(int messageId, boolean isIncoming, String text); + + String getTextForMessage(int messageId, boolean isIncoming); + + void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude); + + double getLatitudeForMessage(int messageId, boolean isIncoming); + + void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude); + + double getLongitudeForMessage(int messageId, boolean isIncoming); + + // Returns the ID's of the file transfers attached to the given message + int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming); + + int getSenderParticipant(int messageId); + + ///////////////////////// + // RcsOutgoingMessageDelivery APIs + ///////////////////////// + + // Returns the participant ID's that this message is intended to be delivered to + int[] getMessageRecipients(int messageId); + + long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId); + + void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, long deliveredTimestamp); + + long getOutgoingDeliverySeenTimestamp(int messageId, int participantId); + + void setOutgoingDeliverySeenTimestamp(int messageId, int participantId, long seenTimestamp); + + int getOutgoingDeliveryStatus(int messageId, int participantId); + + void setOutgoingDeliveryStatus(int messageId, int participantId, int status); + + ///////////////////////// + // RcsFileTransferPart APIs + ///////////////////////// + + // Performs the initial write to storage and returns the row ID. + int storeFileTransfer(int messageId, boolean isIncoming, + in RcsFileTransferCreationParameters fileTransferCreationParameters); + + void deleteFileTransfer(int partId); + + void setFileTransferSessionId(int partId, String sessionId); + + String getFileTransferSessionId(int partId); + + void setFileTransferContentUri(int partId, in Uri contentUri); + + Uri getFileTransferContentUri(int partId); + + void setFileTransferContentType(int partId, String contentType); + + String getFileTransferContentType(int partId); + + void setFileTransferFileSize(int partId, long fileSize); + + long getFileTransferFileSize(int partId); + + void setFileTransferTransferOffset(int partId, long transferOffset); + + long getFileTransferTransferOffset(int partId); + + void setFileTransferStatus(int partId, int transferStatus); + + int getFileTransferStatus(int partId); + + void setFileTransferWidth(int partId, int width); + + int getFileTransferWidth(int partId); + + void setFileTransferHeight(int partId, int height); + + int getFileTransferHeight(int partId); + + void setFileTransferLength(int partId, long length); + + long getFileTransferLength(int partId); + + void setFileTransferPreviewUri(int partId, in Uri uri); + + Uri getFileTransferPreviewUri(int partId); + + void setFileTransferPreviewType(int partId, String type); + + String getFileTransferPreviewType(int partId); + + ///////////////////////// + // RcsEvent APIs + ///////////////////////// + int createGroupThreadNameChangedEvent(long timestamp, int threadId, int originationParticipantId, String newName); + + int createGroupThreadIconChangedEvent(long timestamp, int threadId, int originationParticipantId, in Uri newIcon); + + int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId, int originationParticipantId, int participantId); - void updateRcsParticipantCanonicalAddress(int id, String canonicalAddress); + int createGroupThreadParticipantLeftEvent(long timestamp, int threadId, int originationParticipantId, int participantId); - void updateRcsParticipantAlias(int id, String alias); + int createParticipantAliasChangedEvent(long timestamp, int participantId, String newAlias); }
\ No newline at end of file diff --git a/telephony/java/com/android/ims/RcsTypeIdPair.java b/telephony/java/com/android/ims/RcsTypeIdPair.java new file mode 100644 index 000000000000..a5177354002e --- /dev/null +++ b/telephony/java/com/android/ims/RcsTypeIdPair.java @@ -0,0 +1,79 @@ +/* + * 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.ims; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A utility class to pass RCS IDs and types in RPC calls + * + * @hide + */ +public class RcsTypeIdPair implements Parcelable { + private int mType; + private int mId; + + public RcsTypeIdPair(int type, int id) { + mType = type; + mId = id; + } + + public int getType() { + return mType; + } + + public void setType(int type) { + mType = type; + } + + public int getId() { + return mId; + } + + public void setId(int id) { + mId = id; + } + + public RcsTypeIdPair(Parcel in) { + mType = in.readInt(); + mId = in.readInt(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeInt(mId); + } + + public static final Creator<RcsTypeIdPair> CREATOR = + new Creator<RcsTypeIdPair>() { + @Override + public RcsTypeIdPair createFromParcel(Parcel in) { + return new RcsTypeIdPair(in); + } + + @Override + public RcsTypeIdPair[] newArray(int size) { + return new RcsTypeIdPair[size]; + } + }; +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java new file mode 100644 index 000000000000..915a260f5c79 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java @@ -0,0 +1,55 @@ +/* + * 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.net.Uri; +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadIconChangedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadIconChangedEventTest { + + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + Uri newIconUri = Uri.parse("content://new_icon"); + + RcsGroupThreadIconChangedEvent iconChangedEvent = + new RcsGroupThreadIconChangedEvent(1234567890, rcsGroupThread, rcsParticipant, + newIconUri); + + Parcel parcel = Parcel.obtain(); + iconChangedEvent.writeToParcel(parcel, iconChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + iconChangedEvent = RcsGroupThreadIconChangedEvent.CREATOR.createFromParcel(parcel); + + assertThat(iconChangedEvent.getNewIcon()).isEqualTo(newIconUri); + assertThat(iconChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(iconChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(iconChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java new file mode 100644 index 000000000000..1384c016daa8 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java @@ -0,0 +1,55 @@ +/* + * 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadNameChangedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadNameChangedEventTest { + @Test + public void testCanUnparcel() { + String newName = "new name"; + + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadNameChangedEvent nameChangedEvent = + new RcsGroupThreadNameChangedEvent(1234567890, rcsGroupThread, rcsParticipant, + newName); + + Parcel parcel = Parcel.obtain(); + nameChangedEvent.writeToParcel(parcel, nameChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + nameChangedEvent = RcsGroupThreadNameChangedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(nameChangedEvent.getNewName()).isEqualTo(newName); + assertThat(nameChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(nameChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(nameChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java new file mode 100644 index 000000000000..d0af7db90627 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadParticipantJoinedEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadParticipantJoinedEventTest { + + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadParticipantJoinedEvent participantJoinedEvent = + new RcsGroupThreadParticipantJoinedEvent(1234567890, rcsGroupThread, rcsParticipant, + rcsParticipant); + + Parcel parcel = Parcel.obtain(); + participantJoinedEvent.writeToParcel(parcel, participantJoinedEvent.describeContents()); + + parcel.setDataPosition(0); + + participantJoinedEvent = RcsGroupThreadParticipantJoinedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(participantJoinedEvent.getJoinedParticipant().getId()).isEqualTo(2); + assertThat(participantJoinedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(participantJoinedEvent.getOriginatingParticipant().getId()).isEqualTo(2); + assertThat(participantJoinedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java new file mode 100644 index 000000000000..7ba5fa653258 --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java @@ -0,0 +1,53 @@ +/* + * 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsGroupThread; +import android.telephony.ims.RcsGroupThreadParticipantLeftEvent; +import android.telephony.ims.RcsParticipant; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsGroupThreadParticipantLeftEventTest { + @Test + public void testCanUnparcel() { + RcsGroupThread rcsGroupThread = new RcsGroupThread(1); + RcsParticipant rcsParticipant = new RcsParticipant(2); + + RcsGroupThreadParticipantLeftEvent participantLeftEvent = + new RcsGroupThreadParticipantLeftEvent(1234567890, rcsGroupThread, rcsParticipant, + rcsParticipant); + + Parcel parcel = Parcel.obtain(); + participantLeftEvent.writeToParcel(parcel, participantLeftEvent.describeContents()); + + parcel.setDataPosition(0); + + // create from parcel + parcel.setDataPosition(0); + participantLeftEvent = RcsGroupThreadParticipantLeftEvent.CREATOR.createFromParcel( + parcel); + assertThat(participantLeftEvent.getRcsGroupThread().getThreadId()).isEqualTo(1); + assertThat(participantLeftEvent.getLeavingParticipantId().getId()).isEqualTo(2); + assertThat(participantLeftEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java deleted file mode 100644 index 44277edcdb8c..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import android.support.test.runner.AndroidJUnit4; -import android.telephony.ims.RcsMessageStore; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsMessageStoreTest { - //TODO(sahinc): Add meaningful tests once we have more of the implementation in place - @Test - public void testDeleteThreadDoesntCrash() { - RcsMessageStore mRcsMessageStore = new RcsMessageStore(); - mRcsMessageStore.deleteThread(0); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java new file mode 100644 index 000000000000..3e2bbbf8256c --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java @@ -0,0 +1,57 @@ +/* + * 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.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsParticipant; +import android.telephony.ims.RcsParticipantAliasChangedEvent; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsParticipantAliasChangedEventTest { + private static final String OLD_ALIAS = "old alias"; + private static final String NEW_ALIAS = "new alias"; + private RcsParticipant mParticipant; + + @Before + public void setUp() { + mParticipant = new RcsParticipant(3); + } + + @Test + public void testCanUnparcel() { + RcsParticipantAliasChangedEvent aliasChangedEvent = + new RcsParticipantAliasChangedEvent(1234567890, mParticipant, NEW_ALIAS); + + Parcel parcel = Parcel.obtain(); + aliasChangedEvent.writeToParcel(parcel, aliasChangedEvent.describeContents()); + + parcel.setDataPosition(0); + + aliasChangedEvent = RcsParticipantAliasChangedEvent.CREATOR.createFromParcel( + parcel); + + assertThat(aliasChangedEvent.getParticipantId().getId()).isEqualTo(3); + assertThat(aliasChangedEvent.getNewAlias()).isEqualTo(NEW_ALIAS); + assertThat(aliasChangedEvent.getTimestamp()).isEqualTo(1234567890); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java new file mode 100644 index 000000000000..b4bcb5d12e0d --- /dev/null +++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.tests.ims; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.Parcel; +import android.support.test.runner.AndroidJUnit4; +import android.telephony.ims.RcsParticipantQueryParameters; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RcsParticipantQueryParametersTest { + + @Test + public void testCanUnparcel() { + RcsParticipantQueryParameters rcsParticipantQueryParameters = + new RcsParticipantQueryParameters.Builder() + .setAliasLike("%alias_") + .setCanonicalAddressLike("_canonical%") + .setSortProperty(RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS) + .setSortDirection(true) + .setResultLimit(432) + .build(); + + + Parcel parcel = Parcel.obtain(); + rcsParticipantQueryParameters.writeToParcel(parcel, + rcsParticipantQueryParameters.describeContents()); + + parcel.setDataPosition(0); + rcsParticipantQueryParameters = RcsParticipantQueryParameters.CREATOR.createFromParcel( + parcel); + + assertThat(rcsParticipantQueryParameters.getAliasLike()).isEqualTo("%alias_"); + assertThat(rcsParticipantQueryParameters.getCanonicalAddressLike()).contains("_canonical%"); + assertThat(rcsParticipantQueryParameters.getLimit()).isEqualTo(432); + assertThat(rcsParticipantQueryParameters.getSortingProperty()).isEqualTo( + RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS); + assertThat(rcsParticipantQueryParameters.getSortDirection()).isTrue(); + } +} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java deleted file mode 100644 index c402dbffc84b..000000000000 --- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.tests.ims; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.Bundle; -import android.support.test.runner.AndroidJUnit4; -import android.telephony.ims.RcsParticipant; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class RcsParticipantTest { - private static final int ID = 123; - private static final String ALIAS = "alias"; - private static final String CANONICAL_ADDRESS = "+1234567890"; - - @Test - public void testCanUnparcel() { - RcsParticipant rcsParticipant = new RcsParticipant(ID, CANONICAL_ADDRESS); - rcsParticipant.setAlias(ALIAS); - - Bundle bundle = new Bundle(); - bundle.putParcelable("Some key", rcsParticipant); - rcsParticipant = bundle.getParcelable("Some key"); - - assertThat(rcsParticipant.getId()).isEqualTo(ID); - assertThat(rcsParticipant.getAlias()).isEqualTo(ALIAS); - assertThat(rcsParticipant.getCanonicalAddress()).isEqualTo(CANONICAL_ADDRESS); - } -} diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java index a890a389bdfc..0a70eeccb0fa 100644 --- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java +++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java @@ -15,39 +15,44 @@ */ package com.android.tests.ims; +import static android.telephony.ims.RcsThreadQueryParameters.SORT_BY_TIMESTAMP; +import static android.telephony.ims.RcsThreadQueryParameters.THREAD_TYPE_GROUP; + import static com.google.common.truth.Truth.assertThat; -import android.os.Bundle; +import android.os.Parcel; import android.support.test.runner.AndroidJUnit4; import android.telephony.ims.RcsParticipant; import android.telephony.ims.RcsThreadQueryParameters; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; @RunWith(AndroidJUnit4.class) public class RcsThreadQueryParametersTest { - private RcsThreadQueryParameters mRcsThreadQueryParameters; - @Mock RcsParticipant mMockParticipant; @Test - public void testUnparceling() { - String key = "some key"; - mRcsThreadQueryParameters = RcsThreadQueryParameters.builder() - .isGroupThread(true) - .withParticipant(mMockParticipant) - .limitResultsTo(50) - .sort(true) + public void testCanUnparcel() { + RcsParticipant rcsParticipant = new RcsParticipant(1); + RcsThreadQueryParameters rcsThreadQueryParameters = new RcsThreadQueryParameters.Builder() + .setThreadType(THREAD_TYPE_GROUP) + .setParticipant(rcsParticipant) + .setResultLimit(50) + .setSortProperty(SORT_BY_TIMESTAMP) + .setSortDirection(true) .build(); - Bundle bundle = new Bundle(); - bundle.putParcelable(key, mRcsThreadQueryParameters); - mRcsThreadQueryParameters = bundle.getParcelable(key); + Parcel parcel = Parcel.obtain(); + rcsThreadQueryParameters.writeToParcel(parcel, rcsThreadQueryParameters.describeContents()); + + parcel.setDataPosition(0); + rcsThreadQueryParameters = RcsThreadQueryParameters.CREATOR.createFromParcel(parcel); - assertThat(mRcsThreadQueryParameters.isGroupThread()).isTrue(); - assertThat(mRcsThreadQueryParameters.getRcsParticipants()).contains(mMockParticipant); - assertThat(mRcsThreadQueryParameters.getLimit()).isEqualTo(50); - assertThat(mRcsThreadQueryParameters.isAscending()).isTrue(); + assertThat(rcsThreadQueryParameters.getThreadType()).isEqualTo(THREAD_TYPE_GROUP); + assertThat(rcsThreadQueryParameters.getRcsParticipantsIds()) + .contains(rcsParticipant.getId()); + assertThat(rcsThreadQueryParameters.getLimit()).isEqualTo(50); + assertThat(rcsThreadQueryParameters.getSortingProperty()).isEqualTo(SORT_BY_TIMESTAMP); + assertThat(rcsThreadQueryParameters.getSortDirection()).isTrue(); } } |