diff options
194 files changed, 6214 insertions, 4286 deletions
diff --git a/Android.bp b/Android.bp index 1f82b01fa6ad..21b84040ac0c 100644 --- a/Android.bp +++ b/Android.bp @@ -577,6 +577,7 @@ java_library { "telephony/java/com/android/internal/telephony/euicc/ISetNicknameCallback.aidl", "telephony/java/com/android/internal/telephony/euicc/ISwitchToProfileCallback.aidl", "wifi/java/android/net/wifi/ISoftApCallback.aidl", + "wifi/java/android/net/wifi/ITrafficStateCallback.aidl", "wifi/java/android/net/wifi/IWifiManager.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareDiscoverySessionCallback.aidl", "wifi/java/android/net/wifi/aware/IWifiAwareEventCallback.aidl", diff --git a/api/current.txt b/api/current.txt index abf1224473a7..7f4fae542037 100644 --- a/api/current.txt +++ b/api/current.txt @@ -272,6 +272,7 @@ package android { field public static final int allowBackup = 16843392; // 0x1010280 field public static final int allowClearUserData = 16842757; // 0x1010005 field public static final int allowEmbedded = 16843765; // 0x10103f5 + field public static final int allowForceDark = 16844171; // 0x101058b field public static final int allowParallelSyncs = 16843570; // 0x1010332 field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 @@ -1294,6 +1295,7 @@ package android { field public static final int summaryColumn = 16843426; // 0x10102a2 field public static final int summaryOff = 16843248; // 0x10101f0 field public static final int summaryOn = 16843247; // 0x10101ef + field public static final int supportsAmbientMode = 16844172; // 0x101058c field public static final int supportsAssist = 16844016; // 0x10104f0 field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1 field public static final int supportsLocalInteraction = 16844047; // 0x101050f @@ -2818,6 +2820,7 @@ package android.accessibilityservice { field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice"; field public static final int SHOW_MODE_AUTO = 0; // 0x0 field public static final int SHOW_MODE_HIDDEN = 1; // 0x1 + field public static final int SHOW_MODE_WITH_HARD_KEYBOARD = 2; // 0x2 } public static abstract class AccessibilityService.GestureResultCallback { @@ -6279,6 +6282,7 @@ package android.app { method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager); method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager); + method public boolean supportsAmbientMode(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR; } @@ -12673,12 +12677,14 @@ package android.database.sqlite { method public static void appendColumns(java.lang.StringBuilder, java.lang.String[]); method public void appendWhere(java.lang.CharSequence); method public void appendWhereEscapeString(java.lang.String); + method public void appendWhereStandalone(java.lang.CharSequence); method public java.lang.String buildQuery(java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public deprecated java.lang.String buildQuery(java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public static java.lang.String buildQueryString(boolean, java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public java.lang.String buildUnionQuery(java.lang.String[], java.lang.String, java.lang.String); method public java.lang.String buildUnionSubQuery(java.lang.String, java.lang.String[], java.util.Set<java.lang.String>, int, java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public deprecated java.lang.String buildUnionSubQuery(java.lang.String, java.lang.String[], java.util.Set<java.lang.String>, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.lang.String); + method public int delete(android.database.sqlite.SQLiteDatabase, java.lang.String, java.lang.String[]); method public java.lang.String getTables(); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String); method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, java.lang.String, java.lang.String, java.lang.String); @@ -12688,6 +12694,7 @@ package android.database.sqlite { method public void setProjectionMap(java.util.Map<java.lang.String, java.lang.String>); method public void setStrict(boolean); method public void setTables(java.lang.String); + method public int update(android.database.sqlite.SQLiteDatabase, android.content.ContentValues, java.lang.String, java.lang.String[]); } public class SQLiteReadOnlyDatabaseException extends android.database.sqlite.SQLiteException { @@ -15173,6 +15180,37 @@ package android.graphics.drawable.shapes { package android.graphics.fonts { + public class Font { + method public android.graphics.fonts.FontVariationAxis[] getAxes(); + method public int getTtcIndex(); + method public int getWeight(); + method public boolean isItalic(); + field public static final int FONT_WEIGHT_BLACK = 900; // 0x384 + field public static final int FONT_WEIGHT_BOLD = 700; // 0x2bc + field public static final int FONT_WEIGHT_EXTRA_BOLD = 800; // 0x320 + field public static final int FONT_WEIGHT_EXTRA_LIGHT = 200; // 0xc8 + field public static final int FONT_WEIGHT_LIGHT = 300; // 0x12c + field public static final int FONT_WEIGHT_MEDIUM = 500; // 0x1f4 + field public static final int FONT_WEIGHT_NORMAL = 400; // 0x190 + field public static final int FONT_WEIGHT_SEMI_BOLD = 600; // 0x258 + field public static final int FONT_WEIGHT_THIN = 100; // 0x64 + } + + public static class Font.Builder { + ctor public Font.Builder(java.nio.ByteBuffer); + ctor public Font.Builder(java.io.File) throws java.io.IOException; + ctor public Font.Builder(java.io.FileDescriptor) throws java.io.IOException; + ctor public Font.Builder(java.io.FileDescriptor, long, long) throws java.io.IOException; + ctor public Font.Builder(android.content.res.AssetManager, java.lang.String) throws java.io.IOException; + ctor public Font.Builder(android.content.res.Resources, int) throws java.io.IOException; + method public android.graphics.fonts.Font build(); + method public android.graphics.fonts.Font.Builder setFontVariationSettings(java.lang.String); + method public android.graphics.fonts.Font.Builder setFontVariationSettings(android.graphics.fonts.FontVariationAxis[]); + method public android.graphics.fonts.Font.Builder setItalic(boolean); + method public android.graphics.fonts.Font.Builder setTtcIndex(int); + method public android.graphics.fonts.Font.Builder setWeight(int); + } + public final class FontVariationAxis { ctor public FontVariationAxis(java.lang.String, float); method public static android.graphics.fonts.FontVariationAxis[] fromFontVariationSettings(java.lang.String); @@ -32169,6 +32207,7 @@ package android.os { public class Binder implements android.os.IBinder { ctor public Binder(); + ctor public Binder(java.lang.String); method public void attachInterface(android.os.IInterface, java.lang.String); method public static final long clearCallingIdentity(); method public void dump(java.io.FileDescriptor, java.lang.String[]); @@ -39815,9 +39854,11 @@ package android.service.wallpaper { method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); method public android.view.SurfaceHolder getSurfaceHolder(); + method public boolean isInAmbientMode(); method public boolean isPreview(); method public boolean isVisible(); method public void notifyColorsChanged(); + method public void onAmbientModeChanged(boolean, boolean); method public void onApplyWindowInsets(android.view.WindowInsets); method public android.os.Bundle onCommand(java.lang.String, int, int, int, android.os.Bundle, boolean); method public android.app.WallpaperColors onComputeColors(); @@ -48819,6 +48860,7 @@ package android.view { method public void dispatchOnGlobalLayout(); method public boolean dispatchOnPreDraw(); method public boolean isAlive(); + method public void registerFrameCommitCallback(java.lang.Runnable); method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void removeOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); method public void removeOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener); @@ -48828,6 +48870,7 @@ package android.view { method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); method public void removeOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener); method public void removeOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener); + method public boolean unregisterFrameCommitCallback(java.lang.Runnable); } public static abstract interface ViewTreeObserver.OnDrawListener { diff --git a/api/test-current.txt b/api/test-current.txt index 5bd6dc8773a5..462d22e2e9b3 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1467,6 +1467,11 @@ package android.view { method public static int getLongPressTooltipHideTimeout(); } + public final class ViewTreeObserver { + method public void registerFrameCommitCallback(java.lang.Runnable); + method public boolean unregisterFrameCommitCallback(java.lang.Runnable); + } + public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000 field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40 diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp index 14af5b96ffac..b5660995fa36 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -25,7 +25,6 @@ cc_library_host_shared { ], shared_libs: [ - "libmetricprotos", "libplatformprotos", ], @@ -38,7 +37,6 @@ cc_library_host_shared { }, export_shared_lib_headers: [ - "libmetricprotos", "libplatformprotos", ] diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk index 49ea6d56d4fe..61c185f7686d 100644 --- a/cmds/statsd/Android.mk +++ b/cmds/statsd/Android.mk @@ -232,7 +232,6 @@ LOCAL_SRC_FILES := \ LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ libgmock \ - libmetricprotos \ libplatformprotos LOCAL_PROTOC_OPTIMIZE_TYPE := full @@ -253,7 +252,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := statsdprotolite LOCAL_SRC_FILES := \ - src/metrics_constants/metrics_constants.proto \ src/stats_log.proto \ src/statsd_config.proto \ src/atoms.proto @@ -317,7 +315,6 @@ LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes) LOCAL_STATIC_LIBRARIES := \ $(statsd_common_static_libraries) \ - libmetricprotos \ libplatformprotos LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index bf033a7f3826..203a8e9b3b0b 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -29,7 +29,6 @@ import "frameworks/base/core/proto/android/server/enums.proto"; import "frameworks/base/core/proto/android/telecomm/enums.proto"; import "frameworks/base/core/proto/android/telephony/enums.proto"; import "frameworks/base/core/proto/android/view/enums.proto"; -import "frameworks/base/proto/src/metrics_constants.proto"; /** * The master atom class. This message defines all of the available @@ -2160,11 +2159,11 @@ message BinderCallsExceptions { /** * An atom for generic metrics logging. Available from Android Q. - * One has to add an enum to frameworks/base/proto/src/metrics_constants.proto - * to extend another metric. */ message GenericAtom { - // Type of event. Previously it only indicated visual elements but now it - // is expanded to describe any type of event. - optional com_android_internal_logging.MetricsEvent.View view = 1; + // The uid of the application that sent this custom atom. + optional int32 uid = 1 [(is_uid) = true]; + + // An event_id indicates the type of event. + optional int32 event_id = 2; } diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h index caac677ee215..22cb2f5c2175 100644 --- a/cmds/statsd/src/external/StatsPuller.h +++ b/cmds/statsd/src/external/StatsPuller.h @@ -37,6 +37,8 @@ public: virtual ~StatsPuller() {} + // Pulls the data. The returned data will have elapsedTimeNs set as timeNs + // and will have wallClockTimeNs set as current wall clock time. bool Pull(const int64_t timeNs, std::vector<std::shared_ptr<LogEvent>>* data); // Clear cache immediately diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h index 45efc4a8bea0..bbf5d9dc69db 100644 --- a/cmds/statsd/src/external/StatsPullerManager.h +++ b/cmds/statsd/src/external/StatsPullerManager.h @@ -53,9 +53,12 @@ public: virtual ~StatsPullerManager() { } + // Registers a receiver for tagId. It will be pulled on the nextPullTimeNs + // and then every intervalNs thereafter. virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, int64_t nextPullTimeNs, int64_t intervalNs); + // Stop listening on a tagId. virtual void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver); // Verify if we know how to pull for this matcher @@ -63,11 +66,16 @@ public: void OnAlarmFired(const int64_t timeNs); + // Use respective puller to pull the data. The returned data will have + // elapsedTimeNs set as timeNs and will have wallClockTimeNs set as current + // wall clock time. virtual bool Pull(const int tagId, const int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data); + // Clear pull data cache immediately. int ForceClearPullerCache(); + // Clear pull data cache if it is beyond respective cool down time. int ClearPullerCacheIfNecessary(int64_t timestampNs); void SetStatsCompanionService(sp<IStatsCompanionService> statsCompanionService); diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index a894782db9f7..bd94800a327d 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -66,8 +66,8 @@ const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric, const int conditionIndex, const sp<ConditionWizard>& wizard, - const int64_t startTimeNs) - : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) { + const int64_t timeBaseNs, const int64_t startTimeNs) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard) { if (metric.has_bucket()) { mBucketSizeNs = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; @@ -100,6 +100,10 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = (metric.links().size() > 0) || (mDimensionsInCondition.size() > 0); + flushIfNeededLocked(startTimeNs); + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs); } diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index 520d5de2b33f..39d4ae2f36a5 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -42,7 +42,7 @@ class CountMetricProducer : public MetricProducer { public: CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex, const sp<ConditionWizard>& wizard, - const int64_t startTimeNs); + const int64_t timeBaseNs, const int64_t startTimeNs); virtual ~CountMetricProducer(); @@ -98,6 +98,7 @@ private: FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced); FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade); FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket); + FRIEND_TEST(CountMetricProducerTest, TestFirstBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index a19eb0b66bc7..9d9e5be9e165 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -68,8 +68,8 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat const bool nesting, const sp<ConditionWizard>& wizard, const FieldMatcher& internalDimensions, - const int64_t startTimeNs) - : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard), + const int64_t timeBaseNs, const int64_t startTimeNs) + : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard), mAggregationType(metric.aggregation_type()), mStartIndex(startIndex), mStopIndex(stopIndex), @@ -128,6 +128,9 @@ DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const Durat mMetric2ConditionLinks.begin()->conditionFields); } } + flushIfNeededLocked(startTimeNs); + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; VLOG("metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs); } diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index c496b12df935..12addb8727f1 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -42,7 +42,7 @@ public: const int conditionIndex, const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex, const bool nesting, const sp<ConditionWizard>& wizard, - const FieldMatcher& internalDimensions, const int64_t startTimeNs); + const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs); virtual ~DurationMetricProducer(); @@ -141,6 +141,7 @@ private: FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade); FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket); FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates); + FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index a77941017c2d..fbe0b2193556 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -76,6 +76,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard), mPullerManager(pullerManager), mPullTagId(pullTagId), + mIsPulled(pullTagId != -1), mMinBucketSizeNs(metric.min_bucket_size_nanos()), mDimensionSoftLimit(StatsdStats::kAtomDimensionKeySizeLimitMap.find(pullTagId) != StatsdStats::kAtomDimensionKeySizeLimitMap.end() @@ -125,11 +126,17 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric flushIfNeededLocked(startTimeNs); // Kicks off the puller immediately. - if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(), mBucketSizeNs); } + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + if (mIsPulled) { + pullLocked(startTimeNs); + } + VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs, mConditionSliced); @@ -137,7 +144,7 @@ GaugeMetricProducer::GaugeMetricProducer(const ConfigKey& key, const GaugeMetric GaugeMetricProducer::~GaugeMetricProducer() { VLOG("~GaugeMetricProducer() called"); - if (mPullTagId != -1 && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { + if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) { mPullerManager->UnRegisterReceiver(mPullTagId, this); } } @@ -323,13 +330,11 @@ void GaugeMetricProducer::pullLocked(const int64_t timestampNs) { if (!triggerPuller) { return; } - vector<std::shared_ptr<LogEvent>> allData; if (!mPullerManager->Pull(mPullTagId, timestampNs, &allData)) { - ALOGE("Gauge Stats puller failed for tag: %d", mPullTagId); + ALOGE("Gauge Stats puller failed for tag: %d at %lld", mPullTagId, (long long)timestampNs); return; } - for (const auto& data : allData) { onMatchedLogEventLocked(0, *data); } @@ -340,8 +345,7 @@ void GaugeMetricProducer::onConditionChangedLocked(const bool conditionMet, VLOG("GaugeMetric %lld onConditionChanged", (long long)mMetricId); flushIfNeededLocked(eventTimeNs); mCondition = conditionMet; - - if (mPullTagId != -1) { + if (mIsPulled) { pullLocked(eventTimeNs); } // else: Push mode. No need to proactively pull the gauge data. } @@ -354,7 +358,7 @@ void GaugeMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition // If the condition is sliced, mCondition is true if any of the dimensions is true. And we will // pull for every dimension. mCondition = overallCondition; - if (mPullTagId != -1) { + if (mIsPulled) { pullLocked(eventTimeNs); } // else: Push mode. No need to proactively pull the gauge data. } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index 6984aa2d145d..cc65440c9572 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -77,7 +77,7 @@ public: } flushCurrentBucketLocked(eventTimeNs); mCurrentBucketStartTimeNs = eventTimeNs; - if (mPullTagId != -1) { + if (mIsPulled) { pullLocked(eventTimeNs); } }; @@ -121,6 +121,9 @@ private: // tagId for pulled data. -1 if this is not pulled const int mPullTagId; + // if this is pulled metric + const bool mIsPulled; + // Save the past buckets and we can clear when the StatsLogReport is dumped. std::unordered_map<MetricDimensionKey, std::vector<GaugeBucket>> mPastBuckets; @@ -159,12 +162,13 @@ private: const size_t mGaugeAtomsPerDimensionLimit; - FRIEND_TEST(GaugeMetricProducerTest, TestWithCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestWithSlicedCondition); - FRIEND_TEST(GaugeMetricProducerTest, TestNoCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition); FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade); FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade); - FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); + FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection); + FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index c6f7bb42e5d5..16447e850cf5 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -64,8 +64,8 @@ const int FIELD_ID_BUCKET_INFO = 3; const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4; const int FIELD_ID_DIMENSION_LEAF_IN_CONDITION = 5; // for ValueBucketInfo -const int FIELD_ID_VALUE_LONG = 3; -const int FIELD_ID_VALUE_DOUBLE = 7; +const int FIELD_ID_VALUE_LONG = 7; +const int FIELD_ID_VALUE_DOUBLE = 8; const int FIELD_ID_BUCKET_NUM = 4; const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5; const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; @@ -74,7 +74,7 @@ const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6; ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric& metric, const int conditionIndex, const sp<ConditionWizard>& wizard, const int pullTagId, - const int64_t timeBaseNs, const int64_t startTimestampNs, + const int64_t timeBaseNs, const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager) : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard), mPullerManager(pullerManager), @@ -127,13 +127,20 @@ ValueMetricProducer::ValueMetricProducer(const ConfigKey& key, const ValueMetric mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what()) || HasPositionALL(metric.dimensions_in_condition()); - flushIfNeededLocked(startTimestampNs); + flushIfNeededLocked(startTimeNs); // Kicks off the puller immediately. if (mIsPulled) { - mPullerManager->RegisterReceiver(mPullTagId, this, - mCurrentBucketStartTimeNs + mBucketSizeNs, mBucketSizeNs); + mPullerManager->RegisterReceiver(mPullTagId, this, getCurrentBucketEndTimeNs(), + mBucketSizeNs); } + // TODO: Only do this for partial buckets like first bucket. All other buckets should use + // flushIfNeeded to adjust start and end to bucket boundaries. + // Adjust start for partial bucket + mCurrentBucketStartTimeNs = startTimeNs; + if (mIsPulled) { + pullLocked(startTimeNs); + } VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs); } @@ -280,16 +287,19 @@ void ValueMetricProducer::onConditionChangedLocked(const bool condition, flushIfNeededLocked(eventTimeNs); if (mIsPulled) { - vector<shared_ptr<LogEvent>> allData; - if (mPullerManager->Pull(mPullTagId, eventTimeNs, &allData)) { - if (allData.size() == 0) { - return; - } - for (const auto& data : allData) { - onMatchedLogEventLocked(0, *data); - } + pullLocked(eventTimeNs); + } +} + +void ValueMetricProducer::pullLocked(const int64_t timestampNs) { + vector<std::shared_ptr<LogEvent>> allData; + if (mPullerManager->Pull(mPullTagId, timestampNs, &allData)) { + if (allData.size() == 0) { + return; + } + for (const auto& data : allData) { + onMatchedLogEventLocked(0, *data); } - return; } } @@ -306,12 +316,14 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven int64_t eventTime = mTimeBaseNs + ((realEventTime - mTimeBaseNs) / mBucketSizeNs) * mBucketSizeNs; + // close the end of the bucket mCondition = false; for (const auto& data : allData) { data->setElapsedTimestampNs(eventTime - 1); onMatchedLogEventLocked(0, *data); } + // start a new bucket mCondition = true; for (const auto& data : allData) { data->setElapsedTimestampNs(eventTime); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 188e3de6a289..b2f0b6ff4d78 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -55,7 +55,7 @@ public: const int64_t version) override { std::lock_guard<std::mutex> lock(mMutex); - if (mPullTagId != -1 && (mCondition == true || mConditionTrackerIndex < 0) ) { + if (mIsPulled && (mCondition == true || mConditionTrackerIndex < 0)) { vector<shared_ptr<LogEvent>> allData; mPullerManager->Pull(mPullTagId, eventTimeNs, &allData); if (allData.size() == 0) { @@ -159,6 +159,8 @@ private: // Util function to check whether the specified dimension hits the guardrail. bool hitGuardRailLocked(const MetricDimensionKey& newKey); + void pullLocked(const int64_t timestampNs); + static const size_t kBucketSize = sizeof(ValueBucket{}); const size_t mDimensionSoftLimit; @@ -171,7 +173,7 @@ private: const Type mValueType; - FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents); + FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsNoCondition); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset); FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset); FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition); @@ -190,6 +192,7 @@ private: FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum); FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSumSliced); + FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index e03edb3000ca..ff48d0239059 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -312,7 +312,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } sp<MetricProducer> countProducer = - new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs); + new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs); allMetricProducers.push_back(countProducer); } @@ -382,7 +382,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t sp<MetricProducer> durationMetric = new DurationMetricProducer( key, metric, conditionIndex, trackerIndices[0], trackerIndices[1], - trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs); + trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, currentTimeNs); allMetricProducers.push_back(durationMetric); } diff --git a/cmds/statsd/src/metrics_constants/metrics_constants.proto b/cmds/statsd/src/metrics_constants/metrics_constants.proto deleted file mode 120000 index 8366db767fe8..000000000000 --- a/cmds/statsd/src/metrics_constants/metrics_constants.proto +++ /dev/null @@ -1 +0,0 @@ -../../../../proto/src/metrics_constants.proto
\ No newline at end of file diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index cfd62690dd1c..db7e680f1d37 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -106,10 +106,12 @@ message ValueBucketInfo { optional int64 end_bucket_elapsed_nanos = 2; + optional int64 value = 3 [deprecated = true]; + oneof values { - int64 value_long = 3; + int64 value_long = 7; - double value_double = 7; + double value_double = 8; } optional int64 bucket_num = 4; diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp index 9a8919e98f6d..67c704eb87fd 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -37,6 +37,19 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); +TEST(CountMetricProducerTest, TestFirstBucket) { + CountMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + + CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + 5, 600 * NS_PER_SEC + NS_PER_SEC/2); + EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, countProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs()); +} + TEST(CountMetricProducerTest, TestNonDimensionalEvents) { int64_t bucketStartTimeNs = 10000000000; int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL; @@ -56,8 +69,7 @@ TEST(CountMetricProducerTest, TestNonDimensionalEvents) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + bucketStartTimeNs, bucketStartTimeNs); // 2 events in bucket 1. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); @@ -119,8 +131,7 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); - CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs); countProducer.onConditionChanged(true, bucketStartTimeNs); countProducer.onMatchedLogEvent(1 /*matcher index*/, event1); @@ -181,8 +192,7 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) { EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); CountMetricProducer countProducer(kConfigKey, metric, 1 /*condition tracker index*/, wizard, - bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + bucketStartTimeNs, bucketStartTimeNs); countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); countProducer.flushIfNeededLocked(bucketStartTimeNs + 1); @@ -221,8 +231,7 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) { event1.init(); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, - bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); @@ -280,8 +289,7 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) { event1.init(); sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /* no condition */, wizard, - bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + bucketStartTimeNs, bucketStartTimeNs); // Bucket is flushed yet. countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); @@ -337,8 +345,7 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, - bucketStartTimeNs); - countProducer.setBucketSize(60 * NS_PER_SEC); + bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor); diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp index 7ef8c5bd6a1b..b54096441d3f 100644 --- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp @@ -39,6 +39,23 @@ namespace statsd { const ConfigKey kConfigKey(0, 12345); +TEST(DurationMetricTrackerTest, TestFirstBucket) { + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + DurationMetric metric; + metric.set_id(1); + metric.set_bucket(ONE_MINUTE); + metric.set_aggregation_type(DurationMetric_AggregationType_SUM); + + FieldMatcher dimensions; + DurationMetricProducer durationProducer( + kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, 5, 600 * NS_PER_SEC + NS_PER_SEC/2); + + EXPECT_EQ(600500000000, durationProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, durationProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, durationProducer.getCurrentBucketEndTimeNs()); +} + TEST(DurationMetricTrackerTest, TestNoCondition) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); int64_t bucketStartTimeNs = 10000000000; @@ -58,8 +75,7 @@ TEST(DurationMetricTrackerTest, TestNoCondition) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /*no condition*/, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); durationProducer.onMatchedLogEvent(1 /* start index*/, event1); durationProducer.onMatchedLogEvent(2 /* stop index*/, event2); @@ -100,8 +116,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedCondition) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, 0 /* condition index */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); EXPECT_FALSE(durationProducer.mCondition); EXPECT_FALSE(durationProducer.isConditionSliced()); @@ -151,8 +166,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -206,8 +220,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -261,8 +274,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor); EXPECT_TRUE(anomalyTracker != nullptr); @@ -300,8 +312,7 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); LogEvent start_event(tagId, startTimeNs); start_event.init(); @@ -348,8 +359,7 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) { FieldMatcher dimensions; DurationMetricProducer durationProducer( kConfigKey, metric, -1 /* no condition */, 1 /* start index */, 2 /* stop index */, - 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs); - durationProducer.setBucketSize(60 * NS_PER_SEC); + 3 /* stop_all index */, false /*nesting*/, wizard, dimensions, bucketStartTimeNs, bucketStartTimeNs); LogEvent start_event(tagId, startTimeNs); start_event.init(); diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp index 19c9f775465b..2fda858db7d7 100644 --- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp @@ -47,7 +47,33 @@ const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs; const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs; const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; -TEST(GaugeMetricProducerTest, TestNoCondition) { +/* + * Tests that the first bucket works correctly + */ +TEST(GaugeMetricProducerTest, TestFirstBucket) { + GaugeMetric metric; + metric.set_id(metricId); + metric.set_bucket(ONE_MINUTE); + metric.mutable_gauge_fields_filter()->set_include_all(false); + auto gaugeFieldMatcher = metric.mutable_gauge_fields_filter()->mutable_fields(); + gaugeFieldMatcher->set_field(tagId); + gaugeFieldMatcher->add_child()->set_field(1); + gaugeFieldMatcher->add_child()->set_field(3); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + // statsd started long ago. + // The metric starts in the middle of the bucket + GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + -1, 5, 600 * NS_PER_SEC + NS_PER_SEC/2, pullerManager); + + EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, gaugeProducer.getCurrentBucketEndTimeNs()); +} + +TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) { GaugeMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -62,6 +88,16 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(3); + event->init(); + data->push_back(event); + return true; + })); GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); @@ -83,7 +119,9 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { EXPECT_EQ(10, it->mValue.int_value); it++; EXPECT_EQ(11, it->mValue.int_value); - EXPECT_EQ(0UL, gaugeProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); + EXPECT_EQ(3, gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms + .front().mFields->begin()->mValue.int_value); allData.clear(); std::shared_ptr<LogEvent> event2 = std::make_shared<LogEvent>(tagId, bucket3StartTimeNs + 10); @@ -102,7 +140,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { EXPECT_EQ(25, it->mValue.int_value); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(10L, it->mValue.int_value); @@ -114,7 +152,7 @@ TEST(GaugeMetricProducerTest, TestNoCondition) { EXPECT_EQ(0UL, gaugeProducer.mCurrentSlicedBucket->size()); // One dimension. EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, gaugeProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(3UL, gaugeProducer.mPastBuckets.begin()->second.size()); it = gaugeProducer.mPastBuckets.begin()->second.back().mGaugeAtoms.front().mFields->begin(); EXPECT_EQ(INT, it->mValue.getType()); EXPECT_EQ(24L, it->mValue.int_value); @@ -210,6 +248,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); @@ -263,7 +302,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) { ->mValue.int_value); } -TEST(GaugeMetricProducerTest, TestWithCondition) { +TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition) { GaugeMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -333,7 +372,7 @@ TEST(GaugeMetricProducerTest, TestWithCondition) { ->mValue.int_value); } -TEST(GaugeMetricProducerTest, TestWithSlicedCondition) { +TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition) { const int conditionTag = 65; GaugeMetric metric; metric.set_id(1111111); @@ -409,13 +448,14 @@ TEST(GaugeMetricProducerTest, TestWithSlicedCondition) { EXPECT_EQ(1UL, gaugeProducer.mPastBuckets.size()); } -TEST(GaugeMetricProducerTest, TestAnomalyDetection) { +TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection) { sp<AlarmMonitor> alarmMonitor; sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)).WillOnce(Return(false)); GaugeMetric metric; metric.set_id(metricId); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 3559a7c2dd60..57aab971eaaa 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -50,9 +50,32 @@ const int64_t bucket6StartTimeNs = bucketStartTimeNs + 5 * bucketSizeNs; const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC; /* + * Tests that the first bucket works correctly + */ +TEST(ValueMetricProducerTest, TestFirstBucket) { + 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); + + sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + // statsd started long ago. + // The metric starts in the middle of the bucket + ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, + -1, 5, 600 * NS_PER_SEC + NS_PER_SEC/2, pullerManager); + + EXPECT_EQ(600500000000, valueProducer.mCurrentBucketStartTimeNs); + EXPECT_EQ(10, valueProducer.mCurrentBucketNum); + EXPECT_EQ(660000000005, valueProducer.getCurrentBucketEndTimeNs()); +} + +/* * Tests pulled atoms with no conditions */ -TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { +TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) { ValueMetric metric; metric.set_id(metricId); metric.set_bucket(ONE_MINUTE); @@ -63,10 +86,20 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(3); + event->init(); + data->push_back(event); + return true; + })); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -85,7 +118,8 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { EXPECT_EQ(true, curInterval.startUpdated); EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(11, curInterval.start.long_value); - EXPECT_EQ(0UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); + EXPECT_EQ(8, valueProducer.mPastBuckets.begin()->second.back().mValueLong); allData.clear(); event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1); @@ -99,9 +133,9 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; // tartUpdated:false sum:12 EXPECT_EQ(true, curInterval.startUpdated); - EXPECT_EQ(0, curInterval.value.long_value); + EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); EXPECT_EQ(12, valueProducer.mPastBuckets.begin()->second.back().mValueLong); allData.clear(); @@ -115,9 +149,9 @@ TEST(ValueMetricProducerTest, TestNonDimensionalEvents) { curInterval = valueProducer.mCurrentSlicedBucket.begin()->second; // startUpdated:false sum:12 EXPECT_EQ(true, curInterval.startUpdated); - EXPECT_EQ(0, curInterval.value.long_value); + EXPECT_EQ(false, curInterval.hasValue); EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); - EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size()); + EXPECT_EQ(3UL, valueProducer.mPastBuckets.begin()->second.size()); EXPECT_EQ(13, valueProducer.mPastBuckets.begin()->second.back().mValueLong); } @@ -136,10 +170,10 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)).WillOnce(Return(false)); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -205,10 +239,10 @@ TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)).WillOnce(Return(false)); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -275,6 +309,17 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + // should not take effect + .WillOnce(Invoke([](int tagId, int64_t timeNs, + vector<std::shared_ptr<LogEvent>>* data) { + data->clear(); + shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); + event->write(tagId); + event->write(3); + event->init(); + data->push_back(event); + return true; + })) .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); @@ -298,7 +343,6 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); // has one slice @@ -349,7 +393,6 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -392,6 +435,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); @@ -404,7 +448,6 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) { })); ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; allData.clear(); @@ -447,6 +490,7 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { data->clear(); @@ -469,7 +513,6 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) { })); ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 1); valueProducer.onConditionChanged(false, bucket2StartTimeNs-100); @@ -496,7 +539,6 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -538,7 +580,6 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -612,7 +653,6 @@ TEST(ValueMetricProducerTest, TestAnomalyDetection) { ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, -1 /*not pulled*/, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor); @@ -687,10 +727,10 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _, _)).WillOnce(Return()); EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return()); + EXPECT_CALL(*pullerManager, Pull(tagId, _, _)).WillOnce(Return(false)); ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); vector<shared_ptr<LogEvent>> allData; // pull 1 @@ -770,6 +810,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) // condition becomes true .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { @@ -795,7 +836,6 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); // has one slice @@ -849,6 +889,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) // condition becomes true .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { @@ -885,7 +926,6 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); // has one slice @@ -947,6 +987,7 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition3) { EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return()); EXPECT_CALL(*pullerManager, Pull(tagId, _, _)) + .WillOnce(Return(false)) // condition becomes true .WillOnce(Invoke([](int tagId, int64_t timeNs, vector<std::shared_ptr<LogEvent>>* data) { @@ -972,7 +1013,6 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition3) { ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, tagId, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onConditionChanged(true, bucketStartTimeNs + 8); // has one slice @@ -1022,7 +1062,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -1065,7 +1104,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -1108,7 +1146,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateAvg) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -1154,7 +1191,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) { ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10); event1->write(1); @@ -1218,14 +1254,12 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSumSliced) { sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>(); EXPECT_CALL(*wizard, query(_, key1, _, _, _, _)).WillOnce(Return(ConditionState::kFalse)); - EXPECT_CALL(*wizard, query(_, key2, _, _, _, _)).WillOnce(Return(ConditionState::kTrue)); sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, -1, bucketStartTimeNs, bucketStartTimeNs, pullerManager); - valueProducer.setBucketSize(60 * NS_PER_SEC); valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1); diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 829a94446937..300a530970c9 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -31,7 +31,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.provider.Settings; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; @@ -398,13 +397,49 @@ public abstract class AccessibilityService extends Service { @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "SHOW_MODE_" }, value = { SHOW_MODE_AUTO, - SHOW_MODE_HIDDEN + SHOW_MODE_HIDDEN, + SHOW_MODE_WITH_HARD_KEYBOARD }) public @interface SoftKeyboardShowMode {} + /** + * Allow the system to control when the soft keyboard is shown. + * @see SoftKeyboardController + */ public static final int SHOW_MODE_AUTO = 0; + + /** + * Never show the soft keyboard. + * @see SoftKeyboardController + */ public static final int SHOW_MODE_HIDDEN = 1; + /** + * Allow the soft keyboard to be shown, even if a hard keyboard is connected + * @see SoftKeyboardController + */ + public static final int SHOW_MODE_WITH_HARD_KEYBOARD = 2; + + /** + * Mask used to cover the show modes supported in public API + * @hide + */ + public static final int SHOW_MODE_MASK = 0x03; + + /** + * Bit used to hold the old value of the hard IME setting to restore when a service is shut + * down. + * @hide + */ + public static final int SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE = 0x20000000; + + /** + * Bit for show mode setting to indicate that the user has overridden the hard keyboard + * behavior. + * @hide + */ + public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000; + private int mConnectionId = AccessibilityInteractionClient.NO_ID; private AccessibilityServiceInfo mInfo; @@ -1147,7 +1182,27 @@ public abstract class AccessibilityService extends Service { } /** - * Used to control and query the soft keyboard show mode. + * Used to control, query, and listen for changes to the soft keyboard show mode. + * <p> + * Accessibility services may request to override the decisions normally made about whether or + * not the soft keyboard is shown. + * <p> + * If multiple services make conflicting requests, the last request is honored. A service may + * register a listener to find out if the mode has changed under it. + * <p> + * If the user takes action to override the behavior behavior requested by an accessibility + * service, the user's request takes precendence, the show mode will be reset to + * {@link AccessibilityService#SHOW_MODE_AUTO}, and services will no longer be able to control + * that aspect of the soft keyboard's behavior. + * <p> + * Note: Because soft keyboards are independent apps, the framework does not have total control + * over their behavior. They may choose to show themselves, or not, without regard to requests + * made here. So the framework will make a best effort to deliver the behavior requested, but + * cannot guarantee success. + * + * @see AccessibilityService#SHOW_MODE_AUTO + * @see AccessibilityService#SHOW_MODE_HIDDEN + * @see AccessibilityService#SHOW_MODE_WITH_HARD_KEYBOARD */ public static final class SoftKeyboardController { private final AccessibilityService mService; @@ -1217,7 +1272,8 @@ public abstract class AccessibilityService extends Service { * @param listener the listener to remove, must be non-null * @return {@code true} if the listener was removed, {@code false} otherwise */ - public boolean removeOnShowModeChangedListener(@NonNull OnShowModeChangedListener listener) { + public boolean removeOnShowModeChangedListener( + @NonNull OnShowModeChangedListener listener) { if (mListeners == null) { return false; } @@ -1289,32 +1345,32 @@ public abstract class AccessibilityService extends Service { } /** - * Returns the show mode of the soft keyboard. The default show mode is - * {@code SHOW_MODE_AUTO}, where the soft keyboard is shown when a text input field is - * focused. An AccessibilityService can also request the show mode - * {@code SHOW_MODE_HIDDEN}, where the soft keyboard is never shown. + * Returns the show mode of the soft keyboard. * * @return the current soft keyboard show mode + * + * @see AccessibilityService#SHOW_MODE_AUTO + * @see AccessibilityService#SHOW_MODE_HIDDEN + * @see AccessibilityService#SHOW_MODE_WITH_HARD_KEYBOARD */ @SoftKeyboardShowMode public int getShowMode() { - try { - return Settings.Secure.getInt(mService.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); - } catch (Settings.SettingNotFoundException e) { - Log.v(LOG_TAG, "Failed to obtain the soft keyboard mode", e); - // The settings hasn't been changed yet, so it's value is null. Return the default. - return 0; - } + final IAccessibilityServiceConnection connection = + AccessibilityInteractionClient.getInstance().getConnection( + mService.mConnectionId); + if (connection != null) { + try { + return connection.getSoftKeyboardShowMode(); + } catch (RemoteException re) { + Log.w(LOG_TAG, "Failed to set soft keyboard behavior", re); + re.rethrowFromSystemServer(); + } + } + return SHOW_MODE_AUTO; } /** - * Sets the soft keyboard show mode. The default show mode is - * {@code SHOW_MODE_AUTO}, where the soft keyboard is shown when a text input field is - * focused. An AccessibilityService can also request the show mode - * {@code SHOW_MODE_HIDDEN}, where the soft keyboard is never shown. The - * The lastto this method will be honored, regardless of any previous calls (including those - * made by other AccessibilityServices). + * Sets the soft keyboard show mode. * <p> * <strong>Note:</strong> If the service is not yet connected (e.g. * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the @@ -1322,6 +1378,10 @@ public abstract class AccessibilityService extends Service { * * @param showMode the new show mode for the soft keyboard * @return {@code true} on success + * + * @see AccessibilityService#SHOW_MODE_AUTO + * @see AccessibilityService#SHOW_MODE_HIDDEN + * @see AccessibilityService#SHOW_MODE_WITH_HARD_KEYBOARD */ public boolean setShowMode(@SoftKeyboardShowMode int showMode) { final IAccessibilityServiceConnection connection = diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 037aeb058f15..276131f9d0d6 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -87,6 +87,8 @@ interface IAccessibilityServiceConnection { boolean setSoftKeyboardShowMode(int showMode); + int getSoftKeyboardShowMode(); + void setSoftKeyboardCallbackEnabled(boolean enabled); boolean isAccessibilityButtonAvailable(); diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index c8959788cf50..2baae92bc6a8 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -228,4 +228,7 @@ public abstract class ActivityManagerInternal { public abstract boolean isCurrentProfile(int userId); public abstract boolean hasStartedUserState(int userId); public abstract void finishUserSwitch(Object uss); + + /** Schedule the execution of all pending app GCs. */ + public abstract void scheduleAppGcs(); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 151e9a5a4908..2a3fc04b2451 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -259,6 +259,7 @@ public final class ActivityThread extends ClientTransactionHandler { final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); + /** The activities to be truly destroyed (not include relaunch). */ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed = Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>()); // List of new activities (via ActivityRecord.nextIdle) that should @@ -3078,6 +3079,10 @@ public final class ActivityThread extends ClientTransactionHandler { } private void reportSizeConfigurations(ActivityClientRecord r) { + if (mActivitiesToBeDestroyed.containsKey(r.token)) { + // Size configurations of a destroyed activity is meaningless. + return; + } Configuration[] configurations = r.activity.getResources().getSizeConfigurations(); if (configurations == null) { return; @@ -3826,6 +3831,13 @@ public final class ActivityThread extends ClientTransactionHandler { // We didn't actually resume the activity, so skipping any follow-up actions. return; } + if (mActivitiesToBeDestroyed.containsKey(token)) { + // Although the activity is resumed, it is going to be destroyed. So the following + // UI operations are unnecessary and also prevents exception because its token may + // be gone that window manager cannot recognize it. All necessary cleanup actions + // performed below will be done while handling destruction. + return; + } final Activity a = r.activity; diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index e6452619b508..8874554f928c 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -29,6 +29,7 @@ import android.os.SystemProperties; import android.provider.Settings; import android.util.Printer; import android.util.Slog; + import com.android.internal.util.FastPrintWriter; import java.io.PrintWriter; @@ -333,6 +334,12 @@ public class ApplicationErrorReport implements Parcelable { public String stackTrace; /** + * Crash tag for some context. + * @hide + */ + public String crashTag; + + /** * Create an uninitialized instance of CrashInfo. */ public CrashInfo() { @@ -416,6 +423,7 @@ public class ApplicationErrorReport implements Parcelable { throwMethodName = in.readString(); throwLineNumber = in.readInt(); stackTrace = in.readString(); + crashTag = in.readString(); } /** @@ -430,6 +438,7 @@ public class ApplicationErrorReport implements Parcelable { dest.writeString(throwMethodName); dest.writeInt(throwLineNumber); dest.writeString(stackTrace); + dest.writeString(crashTag); int total = dest.dataPosition()-start; if (Binder.CHECK_PARCEL_SIZE && total > 20*1024) { Slog.d("Error", "ERR: exClass=" + exceptionClassName); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 285f83b9457b..16360b3bf5a8 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -325,7 +325,6 @@ interface IActivityManager { */ void requestWifiBugReport(in String shareTitle, in String shareDescription); - long inputDispatchingTimedOut(int pid, boolean aboveSystem, in String reason); void clearPendingBackup(); Intent getIntentForIntentSender(in IIntentSender sender); // This is not public because you need to be very careful in how you diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 35a17892eec3..9873a8152b3f 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -315,12 +315,13 @@ public final class WallpaperInfo implements Parcelable { } /** - * Returns whether a wallpaper was optimized or not for ambient mode. + * Returns whether a wallpaper was optimized or not for ambient mode and can be drawn in there. * - * @return {@code true} if wallpaper can draw in ambient mode. - * @hide + * @see WallpaperService.Engine#onAmbientModeChanged(boolean, boolean) + * @see WallpaperService.Engine#isInAmbientMode() + * @return {@code true} if wallpaper can draw when in ambient mode. */ - public boolean getSupportsAmbientMode() { + public boolean supportsAmbientMode() { return mSupportsAmbientMode; } diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 3d019f07cb84..7a8ab60c502d 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -16,6 +16,7 @@ package android.database; +import android.annotation.Nullable; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; @@ -34,6 +35,8 @@ import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Log; +import com.android.internal.util.ArrayUtils; + import java.io.FileNotFoundException; import java.io.PrintStream; import java.text.Collator; @@ -216,6 +219,92 @@ public class DatabaseUtils { } /** + * Bind the given selection with the given selection arguments. + * <p> + * Internally assumes that '?' is only ever used for arguments, and doesn't + * appear as a literal or escaped value. + * <p> + * This method is typically useful for trusted code that needs to cook up a + * fully-bound selection. + * + * @hide + */ + public static @Nullable String bindSelection(@Nullable String selection, + @Nullable Object... selectionArgs) { + if (selection == null) return null; + // If no arguments provided, so we can't bind anything + if (ArrayUtils.isEmpty(selectionArgs)) return selection; + // If no bindings requested, so we can shortcut + if (selection.indexOf('?') == -1) return selection; + + // Track the chars immediately before and after each bind request, to + // decide if it needs additional whitespace added + char before = ' '; + char after = ' '; + + int argIndex = 0; + final int len = selection.length(); + final StringBuilder res = new StringBuilder(len); + for (int i = 0; i < len; ) { + char c = selection.charAt(i++); + if (c == '?') { + // Assume this bind request is guarded until we find a specific + // trailing character below + after = ' '; + + // Sniff forward to see if the selection is requesting a + // specific argument index + int start = i; + for (; i < len; i++) { + c = selection.charAt(i); + if (c < '0' || c > '9') { + after = c; + break; + } + } + if (start != i) { + argIndex = Integer.parseInt(selection.substring(start, i)) - 1; + } + + // Manually bind the argument into the selection, adding + // whitespace when needed for clarity + final Object arg = selectionArgs[argIndex++]; + if (before != ' ' && before != '=') res.append(' '); + switch (DatabaseUtils.getTypeOfObject(arg)) { + case Cursor.FIELD_TYPE_NULL: + res.append("NULL"); + break; + case Cursor.FIELD_TYPE_INTEGER: + res.append(((Number) arg).longValue()); + break; + case Cursor.FIELD_TYPE_FLOAT: + res.append(((Number) arg).doubleValue()); + break; + case Cursor.FIELD_TYPE_BLOB: + throw new IllegalArgumentException("Blobs not supported"); + case Cursor.FIELD_TYPE_STRING: + default: + if (arg instanceof Boolean) { + // Provide compatibility with legacy applications which may pass + // Boolean values in bind args. + res.append(((Boolean) arg).booleanValue() ? 1 : 0); + } else { + res.append('\''); + res.append(arg.toString()); + res.append('\''); + } + break; + } + if (after != ' ') res.append(' '); + } else { + res.append(c); + before = c; + } + } + return res.toString(); + } + + /** * Returns data type of the given object's value. *<p> * Returned values are diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 101fb821f4ce..df6447a7e51e 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -34,10 +34,10 @@ import dalvik.system.CloseGuard; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.Map; - /** * Represents a SQLite database connection. * Each connection wraps an instance of a native <code>sqlite3</code> object. @@ -90,6 +90,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static final String TAG = "SQLiteConnection"; private static final boolean DEBUG = false; + public static volatile boolean sLocalDebug = false; + private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @@ -991,6 +993,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } private void bindArguments(PreparedStatement statement, Object[] bindArgs) { + if (sLocalDebug) { + Log.v(TAG, statement.mSql + " with args " + Arrays.toString(bindArgs)); + } + final int count = bindArgs != null ? bindArgs.length : 0; if (count != statement.mNumParameters) { throw new SQLiteBindOrColumnIndexOutOfRangeException( diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index 2d45b14146fc..06560f2e4887 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -29,7 +29,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; -import com.android.internal.util.ArrayUtils; +import libcore.util.EmptyArray; import java.util.Arrays; import java.util.Iterator; @@ -43,8 +43,7 @@ import java.util.regex.Pattern; * This is a convenience class that helps build SQL queries to be sent to * {@link SQLiteDatabase} objects. */ -public class SQLiteQueryBuilder -{ +public class SQLiteQueryBuilder { private static final String TAG = "SQLiteQueryBuilder"; private static final Pattern sLimitPattern = Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); @@ -100,7 +99,7 @@ public class SQLiteQueryBuilder * * @param inWhere the chunk of text to append to the WHERE clause. */ - public void appendWhere(CharSequence inWhere) { + public void appendWhere(@NonNull CharSequence inWhere) { if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } @@ -117,7 +116,7 @@ public class SQLiteQueryBuilder * @param inWhere the chunk of text to append to the WHERE clause. it will be escaped * to avoid SQL injection attacks */ - public void appendWhereEscapeString(String inWhere) { + public void appendWhereEscapeString(@NonNull String inWhere) { if (mWhereClause == null) { mWhereClause = new StringBuilder(inWhere.length() + 16); } @@ -125,6 +124,27 @@ public class SQLiteQueryBuilder } /** + * Add a standalone chunk to the {@code WHERE} clause of this query. + * <p> + * This method differs from {@link #appendWhere(CharSequence)} in that it + * automatically appends {@code AND} to any existing {@code WHERE} clause + * already under construction before appending the given standalone + * expression wrapped in parentheses. + * + * @param inWhere the standalone expression to append to the {@code WHERE} + * clause. It will be wrapped in parentheses when it's appended. + */ + public void appendWhereStandalone(@NonNull CharSequence inWhere) { + if (mWhereClause == null) { + mWhereClause = new StringBuilder(inWhere.length() + 16); + } + if (mWhereClause.length() > 0) { + mWhereClause.append(" AND "); + } + mWhereClause.append('(').append(inWhere).append(')'); + } + + /** * Sets the projection map for the query. The projection map maps * from column names that the caller passes into query to database * column names. This is useful for renaming columns as well as @@ -436,7 +456,6 @@ public class SQLiteQueryBuilder * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows updated - * @hide */ public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { @@ -471,14 +490,19 @@ public class SQLiteQueryBuilder sql = unwrappedSql; } + if (selectionArgs == null) { + selectionArgs = EmptyArray.STRING; + } final ArrayMap<String, Object> rawValues = values.getValues(); - final String[] updateArgs = new String[rawValues.size()]; - for (int i = 0; i < updateArgs.length; i++) { - final Object arg = rawValues.valueAt(i); - updateArgs[i] = (arg != null) ? arg.toString() : null; + final int valuesLength = rawValues.size(); + final Object[] sqlArgs = new Object[valuesLength + selectionArgs.length]; + for (int i = 0; i < sqlArgs.length; i++) { + if (i < valuesLength) { + sqlArgs[i] = rawValues.valueAt(i); + } else { + sqlArgs[i] = selectionArgs[i - valuesLength]; + } } - - final String[] sqlArgs = ArrayUtils.concat(String.class, updateArgs, selectionArgs); if (Log.isLoggable(TAG, Log.DEBUG)) { if (Build.IS_DEBUGGABLE) { Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); @@ -502,7 +526,6 @@ public class SQLiteQueryBuilder * that they appear in the selection. The values will be bound * as Strings. * @return the number of rows deleted - * @hide */ public int delete(@NonNull SQLiteDatabase db, @Nullable String selection, @Nullable String[] selectionArgs) { diff --git a/core/java/android/database/sqlite/SQLiteStatementBuilder.java b/core/java/android/database/sqlite/SQLiteStatementBuilder.java deleted file mode 100644 index e2efb2f8c39b..000000000000 --- a/core/java/android/database/sqlite/SQLiteStatementBuilder.java +++ /dev/null @@ -1,1036 +0,0 @@ -/* - * Copyright (C) 2006 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.database.sqlite; - -import static android.content.ContentResolver.QUERY_ARG_SQL_GROUP_BY; -import static android.content.ContentResolver.QUERY_ARG_SQL_HAVING; -import static android.content.ContentResolver.QUERY_ARG_SQL_LIMIT; -import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION; -import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS; -import static android.content.ContentResolver.QUERY_ARG_SQL_SORT_ORDER; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.DatabaseUtils; -import android.os.Build; -import android.os.Bundle; -import android.os.CancellationSignal; -import android.os.OperationCanceledException; -import android.provider.BaseColumns; -import android.text.TextUtils; -import android.util.ArrayMap; -import android.util.Log; - -import com.android.internal.util.ArrayUtils; - -import dalvik.system.VMRuntime; - -import libcore.util.EmptyArray; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * This is a convenience class that helps build SQL queries to be sent to - * {@link SQLiteDatabase} objects. - * @hide - */ -public class SQLiteStatementBuilder { - private static final String TAG = "SQLiteStatementBuilder"; - private static final Pattern sLimitPattern = - Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?"); - - private Map<String, String> mProjectionMap = null; - private String mTables = ""; - private StringBuilder mWhereClause = null; // lazily created - private String[] mWhereArgs = EmptyArray.STRING; - private boolean mDistinct; - private SQLiteDatabase.CursorFactory mFactory; - private boolean mStrict; - - public SQLiteStatementBuilder() { - mDistinct = false; - mFactory = null; - } - - /** - * Mark the query as DISTINCT. - * - * @param distinct if true the query is DISTINCT, otherwise it isn't - */ - public void setDistinct(boolean distinct) { - mDistinct = distinct; - } - - /** - * Returns the list of tables being queried - * - * @return the list of tables being queried - */ - public String getTables() { - return mTables; - } - - /** - * Sets the list of tables to query. Multiple tables can be specified to perform a join. - * For example: - * setTables("foo, bar") - * setTables("foo LEFT OUTER JOIN bar ON (foo.id = bar.foo_id)") - * - * @param inTables the list of tables to query on - */ - public void setTables(String inTables) { - mTables = inTables; - } - - /** {@hide} */ - public @Nullable String getWhere() { - return (mWhereClause != null) ? mWhereClause.toString() : null; - } - - /** {@hide} */ - public String[] getWhereArgs() { - return mWhereArgs; - } - - /** - * Append a chunk to the {@code WHERE} clause of the query. All chunks - * appended are surrounded by parenthesis and {@code AND}ed with the - * selection passed to {@link #query}. The final {@code WHERE} clause looks - * like: - * - * <pre> - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * </pre> - * - * @param inWhere the chunk of text to append to the {@code WHERE} clause. - */ - public void appendWhere(@NonNull CharSequence inWhere) { - appendWhere(inWhere, EmptyArray.STRING); - } - - /** - * Append a chunk to the {@code WHERE} clause of the query. All chunks - * appended are surrounded by parenthesis and {@code AND}ed with the - * selection passed to {@link #query}. The final {@code WHERE} clause looks - * like: - * - * <pre> - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * </pre> - * - * @param inWhere the chunk of text to append to the {@code WHERE} clause. - * @param inWhereArgs list of arguments to be bound to any '?' occurrences - * in the where clause. - */ - public void appendWhere(@NonNull CharSequence inWhere, String... inWhereArgs) { - if (mWhereClause == null) { - mWhereClause = new StringBuilder(inWhere.length() + 16); - } - mWhereClause.append(inWhere); - mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs); - } - - /** - * Append a standalone expression to the {@code WHERE} clause of this query. - * <p> - * This method differs from {@link #appendWhere(CharSequence)} in that it - * automatically appends {@code AND} to any existing {@code WHERE} clause - * already under construction before appending the given standalone - * expression. - * - * @param inWhere the standalone expression to append to the {@code WHERE} - * clause. It will be wrapped in parentheses when it's appended. - */ - public void appendWhereExpression(@NonNull CharSequence inWhere) { - appendWhereExpression(inWhere, EmptyArray.STRING); - } - - /** - * Append a standalone expression to the {@code WHERE} clause of this query. - * <p> - * This method differs from {@link #appendWhere(CharSequence)} in that it - * automatically appends {@code AND} to any existing {@code WHERE} clause - * already under construction before appending the given standalone - * expression. - * - * @param inWhere the standalone expression to append to the {@code WHERE} - * clause. It will be wrapped in parentheses when it's appended. - * @param inWhereArgs list of arguments to be bound to any '?' occurrences - * in the standalone expression. - */ - public void appendWhereExpression(@NonNull CharSequence inWhere, String... inWhereArgs) { - if (mWhereClause == null) { - mWhereClause = new StringBuilder(inWhere.length() + 16); - } - if (mWhereClause.length() > 0) { - mWhereClause.append(" AND "); - } - mWhereClause.append('(').append(inWhere).append(')'); - mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs); - } - - /** - * Append a chunk to the {@code WHERE} clause of the query. All chunks - * appended are surrounded by parenthesis and {@code AND}ed with the - * selection passed to {@link #query}. The final {@code WHERE} clause looks - * like this: - * - * <pre> - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * </pre> - * - * @param inWhere the chunk of text to append to the {@code WHERE} clause. - * It will be escaped to avoid SQL injection attacks. - */ - public void appendWhereEscapeString(@NonNull String inWhere) { - appendWhereEscapeString(inWhere, EmptyArray.STRING); - } - - /** - * Append a chunk to the {@code WHERE} clause of the query. All chunks - * appended are surrounded by parenthesis and {@code AND}ed with the - * selection passed to {@link #query}. The final {@code WHERE} clause looks - * like this: - * - * <pre> - * WHERE (<append chunk 1><append chunk2>) AND (<query() selection parameter>) - * </pre> - * - * @param inWhere the chunk of text to append to the {@code WHERE} clause. - * It will be escaped to avoid SQL injection attacks. - * @param inWhereArgs list of arguments to be bound to any '?' occurrences - * in the where clause. - */ - public void appendWhereEscapeString(@NonNull String inWhere, String... inWhereArgs) { - if (mWhereClause == null) { - mWhereClause = new StringBuilder(inWhere.length() + 16); - } - DatabaseUtils.appendEscapedSQLString(mWhereClause, inWhere); - mWhereArgs = ArrayUtils.concat(String.class, mWhereArgs, inWhereArgs); - } - - /** - * Sets the projection map for the query. The projection map maps - * from column names that the caller passes into query to database - * column names. This is useful for renaming columns as well as - * disambiguating column names when doing joins. For example you - * could map "name" to "people.name". If a projection map is set - * it must contain all column names the user may request, even if - * the key and value are the same. - * - * @param columnMap maps from the user column names to the database column names - */ - public void setProjectionMap(Map<String, String> columnMap) { - mProjectionMap = columnMap; - } - - /** - * Sets the cursor factory to be used for the query. You can use - * one factory for all queries on a database but it is normally - * easier to specify the factory when doing this query. - * - * @param factory the factory to use. - */ - public void setCursorFactory(SQLiteDatabase.CursorFactory factory) { - mFactory = factory; - } - - /** - * When set, the selection is verified against malicious arguments. - * When using this class to create a statement using - * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, - * non-numeric limits will raise an exception. If a projection map is specified, fields - * not in that map will be ignored. - * If this class is used to execute the statement directly using - * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)} - * or - * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)}, - * additionally also parenthesis escaping selection are caught. - * - * To summarize: To get maximum protection against malicious third party apps (for example - * content provider consumers), make sure to do the following: - * <ul> - * <li>Set this value to true</li> - * <li>Use a projection map</li> - * <li>Use one of the query overloads instead of getting the statement as a sql string</li> - * </ul> - * By default, this value is false. - */ - public void setStrict(boolean strict) { - mStrict = strict; - } - - /** - * Build an SQL query string from the given clauses. - * - * @param distinct true if you want each row to be unique, false otherwise. - * @param tables The table names to compile the query against. - * @param columns A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param where A filter declaring which rows to return, formatted as an SQL - * WHERE clause (excluding the WHERE itself). Passing null will - * return all rows for the given URL. - * @param groupBy A filter declaring how to group rows, formatted as an SQL - * GROUP BY clause (excluding the GROUP BY itself). Passing null - * will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in the cursor, - * if row grouping is being used, formatted as an SQL HAVING - * clause (excluding the HAVING itself). Passing null will cause - * all row groups to be included, and is required when row - * grouping is not being used. - * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause - * (excluding the ORDER BY itself). Passing null will use the - * default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return the SQL query string - */ - public static String buildQueryString( - boolean distinct, String tables, String[] columns, String where, - String groupBy, String having, String orderBy, String limit) { - if (TextUtils.isEmpty(groupBy) && !TextUtils.isEmpty(having)) { - throw new IllegalArgumentException( - "HAVING clauses are only permitted when using a groupBy clause"); - } - if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) { - throw new IllegalArgumentException("invalid LIMIT clauses:" + limit); - } - - StringBuilder query = new StringBuilder(120); - - query.append("SELECT "); - if (distinct) { - query.append("DISTINCT "); - } - if (columns != null && columns.length != 0) { - appendColumns(query, columns); - } else { - query.append("* "); - } - query.append("FROM "); - query.append(tables); - appendClause(query, " WHERE ", where); - appendClause(query, " GROUP BY ", groupBy); - appendClause(query, " HAVING ", having); - appendClause(query, " ORDER BY ", orderBy); - appendClause(query, " LIMIT ", limit); - - return query.toString(); - } - - private static void appendClause(StringBuilder s, String name, String clause) { - if (!TextUtils.isEmpty(clause)) { - s.append(name); - s.append(clause); - } - } - - /** - * Add the names that are non-null in columns to s, separating - * them with commas. - */ - public static void appendColumns(StringBuilder s, String[] columns) { - int n = columns.length; - - for (int i = 0; i < n; i++) { - String column = columns[i]; - - if (column != null) { - if (i > 0) { - s.append(", "); - } - s.append(column); - } - } - s.append(' '); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projection A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public @Nullable Cursor query(@NonNull SQLiteDatabase db, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String groupBy, - @Nullable String having, - @Nullable String sortOrder) { - return query(db, projection, selection, selectionArgs, groupBy, having, sortOrder, - null /* limit */, null /* cancellationSignal */); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projection A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public @Nullable Cursor query(@NonNull SQLiteDatabase db, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String groupBy, - @Nullable String having, - @Nullable String sortOrder, - @Nullable String limit) { - return query(db, projection, selection, selectionArgs, - groupBy, having, sortOrder, limit, null); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projection A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public @Nullable Cursor query(@NonNull SQLiteDatabase db, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String sortOrder, - @Nullable CancellationSignal cancellationSignal) { - return query(db, projection, selection, selectionArgs, null, null, sortOrder, null, - cancellationSignal); - } - - /** - * Perform a query by combining all current settings and the - * information passed into this method. - * - * @param db the database to query on - * @param projection A list of which columns to return. Passing - * null will return all columns, which is discouraged to prevent - * reading data from storage that isn't going to be used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY - * itself). Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @param cancellationSignal A signal to cancel the operation in progress, or null if none. - * If the operation is canceled, then {@link OperationCanceledException} will be thrown - * when the query is executed. - * @return a cursor over the result set - * @see android.content.ContentResolver#query(android.net.Uri, String[], - * String, String[], String) - */ - public @Nullable Cursor query(@NonNull SQLiteDatabase db, - @Nullable String[] projection, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String groupBy, - @Nullable String having, - @Nullable String sortOrder, - @Nullable String limit, - @Nullable CancellationSignal cancellationSignal) { - final Bundle queryArgs = new Bundle(); - maybePutString(queryArgs, QUERY_ARG_SQL_SELECTION, selection); - maybePutStringArray(queryArgs, QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs); - maybePutString(queryArgs, QUERY_ARG_SQL_GROUP_BY, groupBy); - maybePutString(queryArgs, QUERY_ARG_SQL_HAVING, having); - maybePutString(queryArgs, QUERY_ARG_SQL_SORT_ORDER, sortOrder); - maybePutString(queryArgs, QUERY_ARG_SQL_LIMIT, limit); - return query(db, projection, queryArgs, cancellationSignal); - } - - /** - * Perform a query by combining all current settings and the information - * passed into this method. - * - * @param db the database to query on - * @param projection A list of which columns to return. Passing null will - * return all columns, which is discouraged to prevent reading - * data from storage that isn't going to be used. - * @param queryArgs A collection of arguments for the query, defined using - * keys such as {@link ContentResolver#QUERY_ARG_SQL_SELECTION} - * and {@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}. - * @param cancellationSignal A signal to cancel the operation in progress, - * or null if none. If the operation is canceled, then - * {@link OperationCanceledException} will be thrown when the - * query is executed. - * @return a cursor over the result set - */ - public Cursor query(@NonNull SQLiteDatabase db, - @Nullable String[] projection, - @Nullable Bundle queryArgs, - @Nullable CancellationSignal cancellationSignal) { - Objects.requireNonNull(db, "No database defined"); - - if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) { - Objects.requireNonNull(mTables, "No tables defined"); - } else if (mTables == null) { - return null; - } - - if (queryArgs == null) { - queryArgs = Bundle.EMPTY; - } - - // Final SQL that we will execute - final String sql; - - final String unwrappedSql = buildQuery(projection, - queryArgs.getString(QUERY_ARG_SQL_SELECTION), - queryArgs.getString(QUERY_ARG_SQL_GROUP_BY), - queryArgs.getString(QUERY_ARG_SQL_HAVING), - queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER), - queryArgs.getString(QUERY_ARG_SQL_LIMIT)); - - if (mStrict) { - // Validate the user-supplied selection to detect syntactic anomalies - // in the selection string that could indicate a SQL injection attempt. - // The idea is to ensure that the selection clause is a valid SQL expression - // by compiling it twice: once wrapped in parentheses and once as - // originally specified. An attacker cannot create an expression that - // would escape the SQL expression while maintaining balanced parentheses - // in both the wrapped and original forms. - - // NOTE: The ordering of the below operations is important; we must - // execute the wrapped query to ensure the untrusted clause has been - // fully isolated. - - // TODO: decode SORT ORDER and LIMIT clauses, since they can contain - // "expr" inside that need to be validated - - final String wrappedSql = buildQuery(projection, - wrap(queryArgs.getString(QUERY_ARG_SQL_SELECTION)), - queryArgs.getString(QUERY_ARG_SQL_GROUP_BY), - queryArgs.getString(QUERY_ARG_SQL_HAVING), - queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER), - queryArgs.getString(QUERY_ARG_SQL_LIMIT)); - - // Validate the unwrapped query - db.validateSql(unwrappedSql, cancellationSignal); - - // Execute wrapped query for extra protection - sql = wrappedSql; - } else { - // Execute unwrapped query - sql = unwrappedSql; - } - - final String[] sqlArgs = ArrayUtils.concat(String.class, - queryArgs.getStringArray(QUERY_ARG_SQL_SELECTION_ARGS), mWhereArgs); - - if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); - } - - return db.rawQueryWithFactory( - mFactory, sql, sqlArgs, - SQLiteDatabase.findEditTable(mTables), - cancellationSignal); // will throw if query is invalid - } - - /** - * Perform an update by combining all current settings and the - * information passed into this method. - * - * @param db the database to update on - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @return the number of rows updated - */ - public int update(@NonNull SQLiteDatabase db, @NonNull ContentValues values, - @Nullable String selection, @Nullable String[] selectionArgs) { - Objects.requireNonNull(mTables, "No tables defined"); - Objects.requireNonNull(db, "No database defined"); - Objects.requireNonNull(values, "No values defined"); - - if (mStrict) { - // Validate the user-supplied selection to detect syntactic anomalies - // in the selection string that could indicate a SQL injection attempt. - // The idea is to ensure that the selection clause is a valid SQL expression - // by compiling it twice: once wrapped in parentheses and once as - // originally specified. An attacker cannot create an expression that - // would escape the SQL expression while maintaining balanced parentheses - // in both the wrapped and original forms. - final String sql = buildUpdate(values, wrap(selection)); - db.validateSql(sql, null); // will throw if query is invalid - } - - final ArrayMap<String, Object> rawValues = values.getValues(); - final String[] updateArgs = new String[rawValues.size()]; - for (int i = 0; i < updateArgs.length; i++) { - final Object arg = rawValues.valueAt(i); - updateArgs[i] = (arg != null) ? arg.toString() : null; - } - - final String sql = buildUpdate(values, selection); - final String[] sqlArgs = ArrayUtils.concat(String.class, updateArgs, - ArrayUtils.concat(String.class, selectionArgs, mWhereArgs)); - - if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); - } - - return db.executeSql(sql, sqlArgs); - } - - /** - * Perform a delete by combining all current settings and the - * information passed into this method. - * - * @param db the database to delete on - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given URL. - * @param selectionArgs You may include ?s in selection, which - * will be replaced by the values from selectionArgs, in order - * that they appear in the selection. The values will be bound - * as Strings. - * @return the number of rows deleted - */ - public int delete(@NonNull SQLiteDatabase db, @Nullable String selection, - @Nullable String[] selectionArgs) { - Objects.requireNonNull(mTables, "No tables defined"); - Objects.requireNonNull(db, "No database defined"); - - if (mStrict) { - // Validate the user-supplied selection to detect syntactic anomalies - // in the selection string that could indicate a SQL injection attempt. - // The idea is to ensure that the selection clause is a valid SQL expression - // by compiling it twice: once wrapped in parentheses and once as - // originally specified. An attacker cannot create an expression that - // would escape the SQL expression while maintaining balanced parentheses - // in both the wrapped and original forms. - final String sql = buildDelete(wrap(selection)); - db.validateSql(sql, null); // will throw if query is invalid - } - - final String sql = buildDelete(selection); - final String[] sqlArgs = ArrayUtils.concat(String.class, selectionArgs, mWhereArgs); - - if (Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs)); - } - - return db.executeSql(sql, sqlArgs); - } - - /** - * Construct a SELECT statement suitable for use in a group of - * SELECT statements that will be joined through UNION operators - * in buildUnionQuery. - * - * @param projectionIn A list of which columns to return. Passing - * null will return all columns, which is discouraged to - * prevent reading data from storage that isn't going to be - * used. - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given - * URL. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY itself). - * Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing null - * will use the default sort order, which may be unordered. - * @param limit Limits the number of rows returned by the query, - * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return the resulting SQL SELECT statement - */ - public String buildQuery( - String[] projectionIn, String selection, String groupBy, - String having, String sortOrder, String limit) { - String[] projection = computeProjection(projectionIn); - String where = computeWhere(selection); - - return buildQueryString( - mDistinct, mTables, projection, where, - groupBy, having, sortOrder, limit); - } - - /** - * @deprecated This method's signature is misleading since no SQL parameter - * substitution is carried out. The selection arguments parameter does not get - * used at all. To avoid confusion, call - * {@link #buildQuery(String[], String, String, String, String, String)} instead. - */ - @Deprecated - public String buildQuery( - String[] projectionIn, String selection, String[] selectionArgs, - String groupBy, String having, String sortOrder, String limit) { - return buildQuery(projectionIn, selection, groupBy, having, sortOrder, limit); - } - - /** {@hide} */ - public String buildUpdate(ContentValues values, String selection) { - if (values == null || values.isEmpty()) { - throw new IllegalArgumentException("Empty values"); - } - - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(mTables); - sql.append(" SET "); - - final ArrayMap<String, Object> rawValues = values.getValues(); - for (int i = 0; i < rawValues.size(); i++) { - if (i > 0) { - sql.append(','); - } - sql.append(rawValues.keyAt(i)); - sql.append("=?"); - } - - final String where = computeWhere(selection); - appendClause(sql, " WHERE ", where); - return sql.toString(); - } - - /** {@hide} */ - public String buildDelete(String selection) { - StringBuilder sql = new StringBuilder(120); - sql.append("DELETE FROM "); - sql.append(mTables); - - final String where = computeWhere(selection); - appendClause(sql, " WHERE ", where); - return sql.toString(); - } - - /** - * Construct a SELECT statement suitable for use in a group of - * SELECT statements that will be joined through UNION operators - * in buildUnionQuery. - * - * @param typeDiscriminatorColumn the name of the result column - * whose cells will contain the name of the table from which - * each row was drawn. - * @param unionColumns the names of the columns to appear in the - * result. This may include columns that do not appear in the - * table this SELECT is querying (i.e. mTables), but that do - * appear in one of the other tables in the UNION query that we - * are constructing. - * @param columnsPresentInTable a Set of the names of the columns - * that appear in this table (i.e. in the table whose name is - * mTables). Since columns in unionColumns include columns that - * appear only in other tables, we use this array to distinguish - * which ones actually are present. Other columns will have - * NULL values for results from this subquery. - * @param computedColumnsOffset all columns in unionColumns before - * this index are included under the assumption that they're - * computed and therefore won't appear in columnsPresentInTable, - * e.g. "date * 1000 as normalized_date" - * @param typeDiscriminatorValue the value used for the - * type-discriminator column in this subquery - * @param selection A filter declaring which rows to return, - * formatted as an SQL WHERE clause (excluding the WHERE - * itself). Passing null will return all rows for the given - * URL. - * @param groupBy A filter declaring how to group rows, formatted - * as an SQL GROUP BY clause (excluding the GROUP BY itself). - * Passing null will cause the rows to not be grouped. - * @param having A filter declare which row groups to include in - * the cursor, if row grouping is being used, formatted as an - * SQL HAVING clause (excluding the HAVING itself). Passing - * null will cause all row groups to be included, and is - * required when row grouping is not being used. - * @return the resulting SQL SELECT statement - */ - public String buildUnionSubQuery( - String typeDiscriminatorColumn, - String[] unionColumns, - Set<String> columnsPresentInTable, - int computedColumnsOffset, - String typeDiscriminatorValue, - String selection, - String groupBy, - String having) { - int unionColumnsCount = unionColumns.length; - String[] projectionIn = new String[unionColumnsCount]; - - for (int i = 0; i < unionColumnsCount; i++) { - String unionColumn = unionColumns[i]; - - if (unionColumn.equals(typeDiscriminatorColumn)) { - projectionIn[i] = "'" + typeDiscriminatorValue + "' AS " - + typeDiscriminatorColumn; - } else if (i <= computedColumnsOffset - || columnsPresentInTable.contains(unionColumn)) { - projectionIn[i] = unionColumn; - } else { - projectionIn[i] = "NULL AS " + unionColumn; - } - } - return buildQuery( - projectionIn, selection, groupBy, having, - null /* sortOrder */, - null /* limit */); - } - - /** - * @deprecated This method's signature is misleading since no SQL parameter - * substitution is carried out. The selection arguments parameter does not get - * used at all. To avoid confusion, call - * {@link #buildUnionSubQuery} - * instead. - */ - @Deprecated - public String buildUnionSubQuery( - String typeDiscriminatorColumn, - String[] unionColumns, - Set<String> columnsPresentInTable, - int computedColumnsOffset, - String typeDiscriminatorValue, - String selection, - String[] selectionArgs, - String groupBy, - String having) { - return buildUnionSubQuery( - typeDiscriminatorColumn, unionColumns, columnsPresentInTable, - computedColumnsOffset, typeDiscriminatorValue, selection, - groupBy, having); - } - - /** - * Given a set of subqueries, all of which are SELECT statements, - * construct a query that returns the union of what those - * subqueries return. - * @param subQueries an array of SQL SELECT statements, all of - * which must have the same columns as the same positions in - * their results - * @param sortOrder How to order the rows, formatted as an SQL - * ORDER BY clause (excluding the ORDER BY itself). Passing - * null will use the default sort order, which may be unordered. - * @param limit The limit clause, which applies to the entire union result set - * - * @return the resulting SQL SELECT statement - */ - public String buildUnionQuery(String[] subQueries, String sortOrder, String limit) { - StringBuilder query = new StringBuilder(128); - int subQueryCount = subQueries.length; - String unionOperator = mDistinct ? " UNION " : " UNION ALL "; - - for (int i = 0; i < subQueryCount; i++) { - if (i > 0) { - query.append(unionOperator); - } - query.append(subQueries[i]); - } - appendClause(query, " ORDER BY ", sortOrder); - appendClause(query, " LIMIT ", limit); - return query.toString(); - } - - private @Nullable String[] computeProjection(@Nullable String[] projectionIn) { - if (projectionIn != null && projectionIn.length > 0) { - if (mProjectionMap != null) { - String[] projection = new String[projectionIn.length]; - int length = projectionIn.length; - - for (int i = 0; i < length; i++) { - String userColumn = projectionIn[i]; - String column = mProjectionMap.get(userColumn); - - if (column != null) { - projection[i] = column; - continue; - } - - if (!mStrict && - ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { - /* A column alias already exist */ - projection[i] = userColumn; - continue; - } - - throw new IllegalArgumentException("Invalid column " - + projectionIn[i] + " from tables " + mTables); - } - return projection; - } else { - return projectionIn; - } - } else if (mProjectionMap != null) { - // Return all columns in projection map. - Set<Entry<String, String>> entrySet = mProjectionMap.entrySet(); - String[] projection = new String[entrySet.size()]; - Iterator<Entry<String, String>> entryIter = entrySet.iterator(); - int i = 0; - - while (entryIter.hasNext()) { - Entry<String, String> entry = entryIter.next(); - - // Don't include the _count column when people ask for no projection. - if (entry.getKey().equals(BaseColumns._COUNT)) { - continue; - } - projection[i++] = entry.getValue(); - } - return projection; - } - return null; - } - - private @NonNull String computeWhere(@Nullable String selection) { - final boolean hasUser = selection != null && selection.length() > 0; - final boolean hasInternal = mWhereClause != null && mWhereClause.length() > 0; - - if (hasUser || hasInternal) { - final StringBuilder where = new StringBuilder(); - if (hasUser) { - where.append('(').append(selection).append(')'); - } - if (hasUser && hasInternal) { - where.append(" AND "); - } - if (hasInternal) { - where.append('(').append(mWhereClause.toString()).append(')'); - } - return where.toString(); - } else { - return null; - } - } - - /** - * Wrap given argument in parenthesis, unless it's {@code null} or - * {@code ()}, in which case return it verbatim. - */ - private @Nullable String wrap(@Nullable String arg) { - if (arg == null) { - return null; - } else if (arg.equals("")) { - return arg; - } else { - return "(" + arg + ")"; - } - } - - private static void maybePutString(@NonNull Bundle bundle, @NonNull String key, - @Nullable String value) { - if (value != null) { - bundle.putString(key, value); - } - } - - private static void maybePutStringArray(@NonNull Bundle bundle, @NonNull String key, - @Nullable String[] value) { - if (value != null) { - bundle.putStringArray(key, value); - } - } -} diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index 662d13023cd1..b9d900730b69 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -406,9 +406,28 @@ public class Binder implements IBinder { public static final native void blockUntilThreadAvailable(); /** - * Default constructor initializes the object. + * Default constructor just initializes the object. + * + * If you're creating a Binder token (a Binder object without an attached interface), + * you should use {@link #Binder(String)} instead. */ public Binder() { + this(null); + } + + /** + * Constructor for creating a raw Binder object (token) along with a descriptor. + * + * The descriptor of binder objects usually specifies the interface they are implementing. + * In case of binder tokens, no interface is implemented, and the descriptor can be used + * as a sort of tag to help identify the binder token. This will help identify remote + * references to these objects more easily when debugging. + * + * @param descriptor Used to identify the creator of this token, for example the class name. + * Instead of creating multiple tokens with the same descriptor, consider adding a suffix to + * help identify them. + */ + public Binder(@Nullable String descriptor) { mObject = getNativeBBinderHolder(); NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mObject); @@ -420,6 +439,7 @@ public class Binder implements IBinder { klass.getCanonicalName()); } } + mDescriptor = descriptor; } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 7ce7c9237815..7caf0b103534 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -492,10 +492,11 @@ public class Process { String instructionSet, String appDataDir, String invokeWith, + String packageName, String[] zygoteArgs) { return zygoteProcess.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, packageName, zygoteArgs); } /** @hide */ @@ -509,10 +510,11 @@ public class Process { String instructionSet, String appDataDir, String invokeWith, + String packageName, String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, - abi, instructionSet, appDataDir, invokeWith, zygoteArgs); + abi, instructionSet, appDataDir, invokeWith, packageName, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 021e72f7a082..067e8493717b 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -227,12 +227,13 @@ public class ZygoteProcess { String instructionSet, String appDataDir, String invokeWith, + String packageName, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */, - zygoteArgs); + packageName, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -366,6 +367,7 @@ public class ZygoteProcess { String appDataDir, String invokeWith, boolean startChildZygote, + String packageName, String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<String>(); @@ -426,6 +428,10 @@ public class ZygoteProcess { argsForZygote.add("--start-child-zygote"); } + if (packageName != null) { + argsForZygote.add("--package-name=" + packageName); + } + argsForZygote.add(processClass); if (extraArgs != null) { @@ -733,7 +739,7 @@ public class ZygoteProcess { result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, - true /* startChildZygote */, extraArgs); + true /* startChildZygote */, null /* packageName */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java index 0ed252667533..2a094bb2ed1c 100644 --- a/core/java/android/preference/SeekBarVolumizer.java +++ b/core/java/android/preference/SeekBarVolumizer.java @@ -277,7 +277,7 @@ public class SeekBarVolumizer implements OnSeekBarChangeListener, Handler.Callba mHandler.sendEmptyMessage(MSG_INIT_SAMPLE); mVolumeObserver = new Observer(mHandler); mContext.getContentResolver().registerContentObserver( - System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), + System.getUriFor(System.VOLUME_SETTINGS_INT[mStreamType]), false, mVolumeObserver); mReceiver.setListening(true); } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 4b45e32d2dc5..9c3a40995c77 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -16,8 +16,11 @@ package android.provider; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.ClipData; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.ContentUris; @@ -25,22 +28,23 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.UriPermission; +import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.DatabaseUtils; -import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.media.MiniThumbFile; -import android.media.ThumbnailUtils; +import android.graphics.Point; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Environment; -import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.service.media.CameraPrewarmService; +import android.util.ArrayMap; import android.util.Log; +import com.android.internal.annotations.GuardedBy; + import libcore.io.IoUtils; import java.io.File; @@ -49,7 +53,6 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Arrays; import java.util.List; /** @@ -611,178 +614,79 @@ public final class MediaStore { } } + /** @hide */ + public static class ThumbnailConstants { + public static final int MINI_KIND = 1; + public static final int FULL_SCREEN_KIND = 2; + public static final int MICRO_KIND = 3; + + public static final Point MINI_SIZE = new Point(512, 384); + public static final Point MICRO_SIZE = new Point(96, 96); + } + /** * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended * to be accessed elsewhere. */ private static class InternalThumbnails implements BaseColumns { - private static final int MINI_KIND = 1; - private static final int FULL_SCREEN_KIND = 2; - private static final int MICRO_KIND = 3; - private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA}; - static final int DEFAULT_GROUP_ID = 0; - private static final Object sThumbBufLock = new Object(); - private static byte[] sThumbBuf; - - private static Bitmap getMiniThumbFromFile( - Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { - Bitmap bitmap = null; - Uri thumbUri = null; - try { - long thumbId = c.getLong(0); - String filePath = c.getString(1); - thumbUri = ContentUris.withAppendedId(baseUri, thumbId); - ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r"); - bitmap = BitmapFactory.decodeFileDescriptor( - pfdInput.getFileDescriptor(), null, options); - pfdInput.close(); - } catch (FileNotFoundException ex) { - Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); - } catch (IOException ex) { - Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex); - } catch (OutOfMemoryError ex) { - Log.e(TAG, "failed to allocate memory for thumbnail " - + thumbUri + "; " + ex); - } - return bitmap; - } - /** - * This method cancels the thumbnail request so clients waiting for getThumbnail will be - * interrupted and return immediately. Only the original process which made the getThumbnail - * requests can cancel their own requests. - * - * @param cr ContentResolver - * @param origId original image or video id. use -1 to cancel all requests. - * @param groupId the same groupId used in getThumbnail - * @param baseUri the base URI of requested thumbnails + * Currently outstanding thumbnail requests that can be cancelled. */ - static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri, - long groupId) { - Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1") - .appendQueryParameter("orig_id", String.valueOf(origId)) - .appendQueryParameter("group_id", String.valueOf(groupId)).build(); - Cursor c = null; - try { - c = cr.query(cancelUri, PROJECTION, null, null, null); - } - finally { - if (c != null) c.close(); - } - } + @GuardedBy("sPending") + private static ArrayMap<Uri, CancellationSignal> sPending = new ArrayMap<>(); /** - * This method ensure thumbnails associated with origId are generated and decode the byte - * stream from database (MICRO_KIND) or file (MINI_KIND). - * - * Special optimization has been done to avoid further IPC communication for MICRO_KIND - * thumbnails. + * Make a blocking request to obtain the given thumbnail, generating it + * if needed. * - * @param cr ContentResolver - * @param origId original image or video id - * @param kind could be MINI_KIND or MICRO_KIND - * @param options this is only used for MINI_KIND when decoding the Bitmap - * @param baseUri the base URI of requested thumbnails - * @param groupId the id of group to which this request belongs - * @return Bitmap bitmap of specified thumbnail kind + * @see #cancelThumbnail(ContentResolver, Uri) */ - static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind, - BitmapFactory.Options options, Uri baseUri, boolean isVideo) { - Bitmap bitmap = null; - // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo); - // If the magic is non-zero, we simply return thumbnail if it does exist. - // querying MediaProvider and simply return thumbnail. - MiniThumbFile thumbFile = MiniThumbFile.instance( - isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI); - Cursor c = null; - try { - long magic = thumbFile.getMagic(origId); - if (magic != 0) { - if (kind == MICRO_KIND) { - synchronized (sThumbBufLock) { - if (sThumbBuf == null) { - sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; - } - if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { - bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); - if (bitmap == null) { - Log.w(TAG, "couldn't decode byte array."); - } - } - } - return bitmap; - } else if (kind == MINI_KIND) { - String column = isVideo ? "video_id=" : "image_id="; - c = cr.query(baseUri, PROJECTION, column + origId, null, null); - if (c != null && c.moveToFirst()) { - bitmap = getMiniThumbFromFile(c, baseUri, cr, options); - if (bitmap != null) { - return bitmap; - } - } - } + static @Nullable Bitmap getThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri, + int kind, @Nullable BitmapFactory.Options opts) { + final Bundle openOpts = new Bundle(); + if (kind == ThumbnailConstants.MICRO_KIND) { + openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MICRO_SIZE); + } else if (kind == ThumbnailConstants.MINI_KIND) { + openOpts.putParcelable(ContentResolver.EXTRA_SIZE, ThumbnailConstants.MINI_SIZE); + } else { + throw new IllegalArgumentException("Unsupported kind: " + kind); + } + + CancellationSignal signal = null; + synchronized (sPending) { + signal = sPending.get(uri); + if (signal == null) { + signal = new CancellationSignal(); + sPending.put(uri, signal); } + } - Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1") - .appendQueryParameter("orig_id", String.valueOf(origId)) - .appendQueryParameter("group_id", String.valueOf(groupId)).build(); - if (c != null) c.close(); - c = cr.query(blockingUri, PROJECTION, null, null, null); - // This happens when original image/video doesn't exist. - if (c == null) return null; - - // Assuming thumbnail has been generated, at least original image exists. - if (kind == MICRO_KIND) { - synchronized (sThumbBufLock) { - if (sThumbBuf == null) { - sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; - } - Arrays.fill(sThumbBuf, (byte)0); - if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { - bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); - if (bitmap == null) { - Log.w(TAG, "couldn't decode byte array."); - } - } - } - } else if (kind == MINI_KIND) { - if (c.moveToFirst()) { - bitmap = getMiniThumbFromFile(c, baseUri, cr, options); - } - } else { - throw new IllegalArgumentException("Unsupported kind: " + kind); + try (AssetFileDescriptor afd = cr.openTypedAssetFileDescriptor(uri, + "image/*", openOpts, signal)) { + return BitmapFactory.decodeFileDescriptor(afd.getFileDescriptor(), null, opts); + } catch (IOException e) { + Log.w(TAG, "Failed to obtain thumbnail for " + uri, e); + return null; + } finally { + synchronized (sPending) { + sPending.remove(uri); } + } + } - // We probably run out of space, so create the thumbnail in memory. - if (bitmap == null) { - Log.v(TAG, "Create the thumbnail in memory: origId=" + origId - + ", kind=" + kind + ", isVideo="+isVideo); - Uri uri = Uri.parse( - baseUri.buildUpon().appendPath(String.valueOf(origId)) - .toString().replaceFirst("thumbnails", "media")); - if (c != null) c.close(); - c = cr.query(uri, PROJECTION, null, null, null); - if (c == null || !c.moveToFirst()) { - return null; - } - String filePath = c.getString(1); - if (filePath != null) { - if (isVideo) { - bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind); - } else { - bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind); - } - } + /** + * This method cancels the thumbnail request so clients waiting for + * {@link #getThumbnail} will be interrupted and return immediately. + * Only the original process which made the request can cancel their own + * requests. + */ + static void cancelThumbnail(@NonNull ContentResolver cr, @NonNull Uri uri) { + synchronized (sPending) { + final CancellationSignal signal = sPending.get(uri); + if (signal != null) { + signal.cancel(); } - } catch (SQLiteException ex) { - Log.w(TAG, ex); - } finally { - if (c != null) c.close(); - // To avoid file descriptor leak in application process. - thumbFile.deactivate(); - thumbFile = null; } - return bitmap; } } @@ -916,48 +820,6 @@ public final class MediaStore { } } - private static final Bitmap StoreThumbnail( - ContentResolver cr, - Bitmap source, - long id, - float width, float height, - int kind) { - // create the matrix to scale it - Matrix matrix = new Matrix(); - - float scaleX = width / source.getWidth(); - float scaleY = height / source.getHeight(); - - matrix.setScale(scaleX, scaleY); - - Bitmap thumb = Bitmap.createBitmap(source, 0, 0, - source.getWidth(), - source.getHeight(), matrix, - true); - - ContentValues values = new ContentValues(4); - values.put(Images.Thumbnails.KIND, kind); - values.put(Images.Thumbnails.IMAGE_ID, (int)id); - values.put(Images.Thumbnails.HEIGHT, thumb.getHeight()); - values.put(Images.Thumbnails.WIDTH, thumb.getWidth()); - - Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values); - - try { - OutputStream thumbOut = cr.openOutputStream(url); - - thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut); - thumbOut.close(); - return thumb; - } - catch (FileNotFoundException ex) { - return null; - } - catch (IOException ex) { - return null; - } - } - /** * Insert an image and create a thumbnail for it. * @@ -990,12 +852,9 @@ public final class MediaStore { } long id = ContentUris.parseId(url); - // Wait until MINI_KIND thumbnail is generated. - Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id, - Images.Thumbnails.MINI_KIND, null); - // This is for backward compatibility. - Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F, - Images.Thumbnails.MICRO_KIND); + // Block until we've generated common thumbnails + Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MINI_KIND, null); + Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MICRO_KIND, null); } else { Log.e(TAG, "Failed to create thumbnail, removing original"); cr.delete(url, null, null); @@ -1085,8 +944,9 @@ public final class MediaStore { * @param origId original image id */ public static void cancelThumbnailRequest(ContentResolver cr, long origId) { - InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, - InternalThumbnails.DEFAULT_GROUP_ID); + final Uri uri = ContentUris.withAppendedId( + Images.Media.EXTERNAL_CONTENT_URI, origId); + InternalThumbnails.cancelThumbnail(cr, uri); } /** @@ -1102,9 +962,9 @@ public final class MediaStore { */ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options) { - return InternalThumbnails.getThumbnail(cr, origId, - InternalThumbnails.DEFAULT_GROUP_ID, kind, options, - EXTERNAL_CONTENT_URI, false); + final Uri uri = ContentUris.withAppendedId( + Images.Media.EXTERNAL_CONTENT_URI, origId); + return InternalThumbnails.getThumbnail(cr, uri, kind, options); } /** @@ -1117,7 +977,7 @@ public final class MediaStore { * @param groupId the same groupId used in getThumbnail. */ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { - InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId); + cancelThumbnailRequest(cr, origId); } /** @@ -1134,8 +994,7 @@ public final class MediaStore { */ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options) { - return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options, - EXTERNAL_CONTENT_URI, false); + return getThumbnail(cr, origId, kind, options); } /** @@ -1193,9 +1052,9 @@ public final class MediaStore { */ public static final String KIND = "kind"; - public static final int MINI_KIND = 1; - public static final int FULL_SCREEN_KIND = 2; - public static final int MICRO_KIND = 3; + public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; + public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; + public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; /** * The blob raw data of thumbnail * <P>Type: DATA STREAM</P> @@ -2155,8 +2014,9 @@ public final class MediaStore { * @param origId original video id */ public static void cancelThumbnailRequest(ContentResolver cr, long origId) { - InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, - InternalThumbnails.DEFAULT_GROUP_ID); + final Uri uri = ContentUris.withAppendedId( + Video.Media.EXTERNAL_CONTENT_URI, origId); + InternalThumbnails.cancelThumbnail(cr, uri); } /** @@ -2172,9 +2032,9 @@ public final class MediaStore { */ public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options) { - return InternalThumbnails.getThumbnail(cr, origId, - InternalThumbnails.DEFAULT_GROUP_ID, kind, options, - EXTERNAL_CONTENT_URI, true); + final Uri uri = ContentUris.withAppendedId( + Video.Media.EXTERNAL_CONTENT_URI, origId); + return InternalThumbnails.getThumbnail(cr, uri, kind, options); } /** @@ -2191,8 +2051,7 @@ public final class MediaStore { */ public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options) { - return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options, - EXTERNAL_CONTENT_URI, true); + return getThumbnail(cr, origId, kind, options); } /** @@ -2205,7 +2064,7 @@ public final class MediaStore { * @param groupId the same groupId used in getThumbnail. */ public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) { - InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId); + cancelThumbnailRequest(cr, origId); } /** @@ -2263,9 +2122,9 @@ public final class MediaStore { */ public static final String KIND = "kind"; - public static final int MINI_KIND = 1; - public static final int FULL_SCREEN_KIND = 2; - public static final int MICRO_KIND = 3; + public static final int MINI_KIND = ThumbnailConstants.MINI_KIND; + public static final int FULL_SCREEN_KIND = ThumbnailConstants.FULL_SCREEN_KIND; + public static final int MICRO_KIND = ThumbnailConstants.MICRO_KIND; /** * The width of the thumbnal diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 848138714e98..ebd90bdc2414 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12067,6 +12067,33 @@ public final class Settings { "autofill_compat_mode_allowed_packages"; /** + * Level of autofill logging. + * + * <p>Valid values are + * {@link android.view.autofill.AutofillManager#NO_LOGGING}, + * {@link android.view.autofill.AutofillManager#FLAG_ADD_CLIENT_DEBUG}, or + * {@link android.view.autofill.AutofillManager#FLAG_ADD_CLIENT_VERBOSE}. + * + * @hide + */ + public static final String AUTOFILL_LOGGING_LEVEL = "autofill_logging_level"; + + /** + * Maximum number of partitions that can be allowed in an autofill session. + * + * @hide + */ + public static final String AUTOFILL_MAX_PARTITIONS_SIZE = "autofill_max_partitions_size"; + + /** + * Maximum number of visible datasets in the Autofill dataset picker UI, or {@code 0} to use + * the default value from resources. + * + * @hide + */ + public static final String AUTOFILL_MAX_VISIBLE_DATASETS = "autofill_max_visible_datasets"; + + /** * Exemptions to the hidden API blacklist. * * @hide diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index c48b6d12e948..29f73b55bb40 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -21,6 +21,7 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.app.WallpaperColors; +import android.app.WallpaperInfo; import android.app.WallpaperManager; import android.content.Context; import android.content.Intent; @@ -436,8 +437,7 @@ public abstract class WallpaperService extends Service { /** * Returns true if this engine is running in ambient mode -- that is, - * it is being shown in low power mode, in always on display. - * @hide + * it is being shown in low power mode, on always on display. */ public boolean isInAmbientMode() { return mIsInAmbientMode; @@ -563,10 +563,12 @@ public abstract class WallpaperService extends Service { * Called when the device enters or exits ambient mode. * * @param inAmbientMode {@code true} if in ambient mode. - * @param animated {@code true} if you'll have te opportunity of animating your transition - * {@code false} when the screen will blank and the wallpaper should be - * set to ambient mode immediately. - * @hide + * @param animated {@code true} if you'll have the opportunity of animating your transition + * {@code false} when the wallpaper should present its ambient version + * immediately. + * + * @see #isInAmbientMode() + * @see WallpaperInfo#supportsAmbientMode() */ public void onAmbientModeChanged(boolean inAmbientMode, boolean animated) { } diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index c2c3182c2abd..9bf8cd20f441 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -30,10 +30,6 @@ import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Pools.SynchronizedPool; -import dalvik.annotation.optimization.CriticalNative; - -import libcore.util.NativeAllocationRegistry; - import java.util.Arrays; /** @@ -62,9 +58,6 @@ import java.util.Arrays; public class MeasuredParagraph { private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; - private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); - private MeasuredParagraph() {} // Use build static functions instead. private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1); @@ -128,24 +121,7 @@ public class MeasuredParagraph { private @Nullable IntArray mFontMetrics = new IntArray(4 * 4); // The native MeasuredParagraph. - // See getNativePtr comments. - // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead. - private /* Maybe Zero */ long mNativePtr = 0; - private @Nullable Runnable mNativeObjectCleaner; - - // Associate the native object to this Java object. - private void bindNativeObject(/* Non Zero*/ long nativePtr) { - mNativePtr = nativePtr; - mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr); - } - - // Decouple the native object from this Java object and release the native object. - private void unbindNativeObject() { - if (mNativePtr != 0) { - mNativeObjectCleaner.run(); - mNativePtr = 0; - } - } + private @Nullable NativeMeasuredParagraph mNativeMeasuredParagraph; // Following two objects are for avoiding object allocation. private @NonNull TextPaint mCachedPaint = new TextPaint(); @@ -173,7 +149,7 @@ public class MeasuredParagraph { mWidths.clear(); mFontMetrics.clear(); mSpanEndCache.clear(); - unbindNativeObject(); + mNativeMeasuredParagraph = null; } /** @@ -267,10 +243,10 @@ public class MeasuredParagraph { * Returns the native ptr of the MeasuredParagraph. * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. - * Returns 0 in other cases. + * Returns null in other cases. */ - public /* Maybe Zero */ long getNativePtr() { - return mNativePtr; + public NativeMeasuredParagraph getNativeMeasuredParagraph() { + return mNativeMeasuredParagraph; } /** @@ -283,7 +259,7 @@ public class MeasuredParagraph { * @param end the exclusive end offset of the target region in the text */ public float getWidth(int start, int end) { - if (mNativePtr == 0) { + if (mNativeMeasuredParagraph == null) { // We have result in Java. final float[] widths = mWidths.getRawArray(); float r = 0.0f; @@ -293,7 +269,7 @@ public class MeasuredParagraph { return r; } else { // We have result in native. - return nGetWidth(mNativePtr, start, end); + return mNativeMeasuredParagraph.getWidth(start, end); } } @@ -305,7 +281,16 @@ public class MeasuredParagraph { */ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Rect bounds) { - nGetBounds(mNativePtr, mCopiedBuffer, start, end, bounds); + mNativeMeasuredParagraph.getBounds(mCopiedBuffer, start, end, bounds); + } + + /** + * Returns a width of the character at the offset. + * + * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + */ + public float getCharWidthAt(@IntRange(from = 0) int offset) { + return mNativeMeasuredParagraph.getCharWidthAt(offset); } /** @@ -364,7 +349,7 @@ public class MeasuredParagraph { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan( - paint, null /* spans */, start, end, 0 /* native static layout ptr */); + paint, null /* spans */, start, end, null /* native builder ptr */); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. int spanEnd; @@ -374,7 +359,7 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); mt.applyMetricsAffectingSpan( - paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */); + paint, spans, spanStart, spanEnd, null /* native builder ptr */); } } return mt; @@ -406,25 +391,16 @@ public class MeasuredParagraph { @Nullable MeasuredParagraph recycle) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); + final NativeMeasuredParagraph.Builder builder = new NativeMeasuredParagraph.Builder(); if (mt.mTextLength == 0) { // Need to build empty native measured text for StaticLayout. // TODO: Stop creating empty measured text for empty lines. - long nativeBuilderPtr = nInitBuilder(); - try { - mt.bindNativeObject( - nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, - computeHyphenation, computeLayout)); - } finally { - nFreeBuilder(nativeBuilderPtr); - } - return mt; - } - - long nativeBuilderPtr = nInitBuilder(); - try { + mt.mNativeMeasuredParagraph = builder.build(mt.mCopiedBuffer, computeHyphenation, + computeLayout); + } else { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr); + mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, builder); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply @@ -437,15 +413,12 @@ public class MeasuredParagraph { MetricAffectingSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, - nativeBuilderPtr); + mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd, builder); mt.mSpanEndCache.append(spanEnd); } } - mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer, - computeHyphenation, computeLayout)); - } finally { - nFreeBuilder(nativeBuilderPtr); + mt.mNativeMeasuredParagraph = builder.build(mt.mCopiedBuffer, computeHyphenation, + computeLayout); } return mt; @@ -517,13 +490,13 @@ public class MeasuredParagraph { private void applyReplacementRun(@NonNull ReplacementSpan replacement, @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { + @Nullable NativeMeasuredParagraph.Builder builder) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? final float width = replacement.getSize( mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm); - if (nativeBuilderPtr == 0) { + if (builder == null) { // Assigns all width to the first character. This is the same behavior as minikin. mWidths.set(start, width); if (end > start + 1) { @@ -531,24 +504,22 @@ public class MeasuredParagraph { } mWholeWidth += width; } else { - nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - width); + builder.addReplacementRun(mCachedPaint, start, end, width); } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer - /* Maybe Zero */ long nativeBuilderPtr) { + @Nullable NativeMeasuredParagraph.Builder builder) { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. - if (nativeBuilderPtr == 0) { + if (builder == null) { mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */, mWidths.getRawArray(), start); } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end, - false /* isRtl */); + builder.addStyleRun(mCachedPaint, start, end, false /* isRtl */); } } else { // If there is multiple bidi levels, split into individual bidi level and apply style. @@ -558,14 +529,13 @@ public class MeasuredParagraph { for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) { if (levelEnd == end || mLevels.get(levelEnd) != level) { // transition point final boolean isRtl = (level & 0x1) != 0; - if (nativeBuilderPtr == 0) { + if (builder == null) { final int levelLength = levelEnd - levelStart; mWholeWidth += mCachedPaint.getTextRunAdvances( mCopiedBuffer, levelStart, levelLength, levelStart, levelLength, isRtl, mWidths.getRawArray(), levelStart); } else { - nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart, - levelEnd, isRtl); + builder.addStyleRun(mCachedPaint, levelStart, levelEnd, isRtl); } if (levelEnd == end) { break; @@ -582,12 +552,12 @@ public class MeasuredParagraph { @Nullable MetricAffectingSpan[] spans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer - /* Maybe Zero */ long nativeBuilderPtr) { + @Nullable NativeMeasuredParagraph.Builder builder) { mCachedPaint.set(paint); // XXX paint should not have a baseline shift, but... mCachedPaint.baselineShift = 0; - final boolean needFontMetrics = nativeBuilderPtr != 0; + final boolean needFontMetrics = builder != null; if (needFontMetrics && mCachedFm == null) { mCachedFm = new Paint.FontMetricsInt(); @@ -610,15 +580,14 @@ public class MeasuredParagraph { final int startInCopiedBuffer = start - mTextStart; final int endInCopiedBuffer = end - mTextStart; - if (nativeBuilderPtr != 0) { + if (builder != null) { mCachedPaint.getFontMetricsInt(mCachedFm); } if (replacement != null) { - applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, - nativeBuilderPtr); + applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, builder); } else { - applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr); + applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, builder); } if (needFontMetrics) { @@ -689,59 +658,6 @@ public class MeasuredParagraph { * This only works if the MeasuredParagraph is computed with buildForStaticLayout. */ public @IntRange(from = 0) int getMemoryUsage() { - return nGetMemoryUsage(mNativePtr); + return mNativeMeasuredParagraph.getMemoryUsage(); } - - private static native /* Non Zero */ long nInitBuilder(); - - /** - * Apply style to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param isRtl True if the text is RTL. - */ - private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - boolean isRtl); - - /** - * Apply ReplacementRun to make native measured text. - * - * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. - * @param paintPtr The native paint pointer to be applied. - * @param start The start offset in the copied buffer. - * @param end The end offset in the copied buffer. - * @param width The width of the replacement. - */ - private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, - /* Non Zero */ long paintPtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end, - @FloatRange(from = 0) float width); - - private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr, - @NonNull char[] text, - boolean computeHyphenation, - boolean computeLayout); - - private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); - - @CriticalNative - private static native float nGetWidth(/* Non Zero */ long nativePtr, - @IntRange(from = 0) int start, - @IntRange(from = 0) int end); - - @CriticalNative - private static native /* Non Zero */ long nGetReleaseFunc(); - - @CriticalNative - private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); - - private static native void nGetBounds(long nativePtr, char[] buf, int start, int end, - Rect rect); } diff --git a/core/java/android/text/NativeLineBreaker.java b/core/java/android/text/NativeLineBreaker.java new file mode 100644 index 000000000000..a31b3361d970 --- /dev/null +++ b/core/java/android/text/NativeLineBreaker.java @@ -0,0 +1,155 @@ +/* + * 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.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; + +import dalvik.annotation.optimization.CriticalNative; +import dalvik.annotation.optimization.FastNative; + +import libcore.util.NativeAllocationRegistry; + +/** + * A native implementation of the line breaker. + * TODO: Consider to make this class public. + * @hide + */ +public class NativeLineBreaker { + + /** + * A result object of a line breaking + */ + public static class LineBreaks { + public int breakCount; + private static final int INITIAL_SIZE = 16; + public int[] breaks = new int[INITIAL_SIZE]; + public float[] widths = new float[INITIAL_SIZE]; + public float[] ascents = new float[INITIAL_SIZE]; + public float[] descents = new float[INITIAL_SIZE]; + public int[] flags = new int[INITIAL_SIZE]; + // breaks, widths, and flags should all have the same length + } + + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + NativeLineBreaker.class.getClassLoader(), nGetReleaseFunc(), 64); + + private final long mNativePtr; + + /** + * A constructor of NativeLineBreaker + */ + public NativeLineBreaker(@Layout.BreakStrategy int breakStrategy, + @Layout.HyphenationFrequency int hyphenationFrequency, + boolean justify, @Nullable int[] indents) { + mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify, indents); + sRegistry.registerNativeAllocation(this, mNativePtr); + } + + /** + * Break text into lines + * + * @param chars an array of characters + * @param measuredPara a result of the text measurement + * @param length a length of the target text from the begining + * @param firstWidth a width of the first width of the line in this paragraph + * @param firstWidthLineCount a number of lines that has the length of the firstWidth + * @param restWidth a width of the rest of the lines. + * @param variableTabStops an array of tab stop widths + * @param defaultTabStop a width of the tab stop + * @param indentsOffset an offset of the indents to be used. + * @param out output buffer + * @return a number of the lines + */ + @NonNull public int computeLineBreaks( + @NonNull char[] chars, + @NonNull NativeMeasuredParagraph measuredPara, + @IntRange(from = 0) int length, + @FloatRange(from = 0.0f) float firstWidth, + @IntRange(from = 0) int firstWidthLineCount, + @FloatRange(from = 0.0f) float restWidth, + @Nullable int[] variableTabStops, + int defaultTabStop, + @IntRange(from = 0) int indentsOffset, + @NonNull LineBreaks out) { + return nComputeLineBreaks( + mNativePtr, + + // Inputs + chars, + measuredPara.getNativePtr(), + length, + firstWidth, + firstWidthLineCount, + restWidth, + variableTabStops, + defaultTabStop, + indentsOffset, + + // Outputs + out, + out.breaks.length, + out.breaks, + out.widths, + out.ascents, + out.descents, + out.flags); + + } + + @FastNative + private static native long nInit( + @Layout.BreakStrategy int breakStrategy, + @Layout.HyphenationFrequency int hyphenationFrequency, + boolean isJustified, + @Nullable int[] indents); + + @CriticalNative + private static native long nGetReleaseFunc(); + + // populates LineBreaks and returns the number of breaks found + // + // the arrays inside the LineBreaks objects are passed in as well + // to reduce the number of JNI calls in the common case where the + // arrays do not have to be resized + // The individual character widths will be returned in charWidths. The length of + // charWidths must be at least the length of the text. + private static native int nComputeLineBreaks( + /* non zero */ long nativePtr, + + // Inputs + @NonNull char[] text, + /* Non Zero */ long measuredTextPtr, + @IntRange(from = 0) int length, + @FloatRange(from = 0.0f) float firstWidth, + @IntRange(from = 0) int firstWidthLineCount, + @FloatRange(from = 0.0f) float restWidth, + @Nullable int[] variableTabStops, + int defaultTabStop, + @IntRange(from = 0) int indentsOffset, + + // Outputs + @NonNull LineBreaks recycle, + @IntRange(from = 0) int recycleLength, + @NonNull int[] recycleBreaks, + @NonNull float[] recycleWidths, + @NonNull float[] recycleAscents, + @NonNull float[] recycleDescents, + @NonNull int[] recycleFlags); +} diff --git a/core/java/android/text/NativeMeasuredParagraph.java b/core/java/android/text/NativeMeasuredParagraph.java new file mode 100644 index 000000000000..d03674f86109 --- /dev/null +++ b/core/java/android/text/NativeMeasuredParagraph.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2010 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.text; + +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.graphics.Paint; +import android.graphics.Rect; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +/** + * A native implementation of measured paragraph. + * TODO: Consider to make this class public. + * @hide + */ +public class NativeMeasuredParagraph { + private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( + NativeMeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024); + + private long mNativePtr; + + // Use builder instead. + private NativeMeasuredParagraph(long ptr) { + mNativePtr = ptr; + } + + /** + * Returns a width of the given region + */ + public float getWidth(int start, int end) { + return nGetWidth(mNativePtr, start, end); + } + + /** + * Returns a memory usage of the native object. + */ + public int getMemoryUsage() { + return nGetMemoryUsage(mNativePtr); + } + + /** + * Fills the boundary box of the given region + */ + public void getBounds(char[] buf, int start, int end, Rect rect) { + nGetBounds(mNativePtr, buf, start, end, rect); + } + + /** + * Returns the width of the character at the given offset + */ + public float getCharWidthAt(int offset) { + return nGetCharWidthAt(mNativePtr, offset); + } + + /** + * Returns a native pointer of the underlying native object. + */ + public long getNativePtr() { + return mNativePtr; + } + + @CriticalNative + private static native float nGetWidth(/* Non Zero */ long nativePtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end); + + @CriticalNative + private static native /* Non Zero */ long nGetReleaseFunc(); + + @CriticalNative + private static native int nGetMemoryUsage(/* Non Zero */ long nativePtr); + + private static native void nGetBounds(long nativePtr, char[] buf, int start, int end, + Rect rect); + + @CriticalNative + private static native float nGetCharWidthAt(long nativePtr, int offset); + + /** + * A builder for the NativeMeasuredParagraph + */ + public static class Builder { + private final long mNativePtr; + + public Builder() { + mNativePtr = nInitBuilder(); + } + + /** + * Apply styles to given range + */ + public void addStyleRun(@NonNull Paint paint, int start, int end, boolean isRtl) { + nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl); + } + + /** + * Tells native that the given range is replaced with the object of given width. + */ + public void addReplacementRun(@NonNull Paint paint, int start, int end, float width) { + nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width); + } + + /** + * Build the NativeMeasuredParagraph + */ + public NativeMeasuredParagraph build(char[] text, boolean computeHyphenation, + boolean computeLayout) { + try { + long ptr = nBuildNativeMeasuredParagraph(mNativePtr, text, computeHyphenation, + computeLayout); + NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr); + sRegistry.registerNativeAllocation(res, ptr); + return res; + } finally { + nFreeBuilder(mNativePtr); + } + } + + private static native /* Non Zero */ long nInitBuilder(); + + /** + * Apply style to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param isRtl True if the text is RTL. + */ + private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + boolean isRtl); + /** + * Apply ReplacementRun to make native measured text. + * + * @param nativeBuilderPtr The native MeasuredParagraph builder pointer. + * @param paintPtr The native paint pointer to be applied. + * @param start The start offset in the copied buffer. + * @param end The end offset in the copied buffer. + * @param width The width of the replacement. + */ + private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr, + /* Non Zero */ long paintPtr, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @FloatRange(from = 0) float width); + + private static native long nBuildNativeMeasuredParagraph( + /* Non Zero */ long nativeBuilderPtr, + @NonNull char[] text, + boolean computeHyphenation, + boolean computeLayout); + + private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr); + } +} diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index 027ead30c9ae..b7ea0122cb06 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -518,6 +518,21 @@ public class PrecomputedText implements Spannable { } /** + * Returns a width of a character at offset + * + * @param offset an offset of the text. + * @return a width of the character. + * @hide + */ + public float getCharWidthAt(@IntRange(from = 0) int offset) { + Preconditions.checkArgument(0 <= offset && offset < mText.length(), "invalid offset"); + final int paraIndex = findParaIndex(offset); + final int paraStart = getParagraphStart(paraIndex); + final int paraEnd = getParagraphEnd(paraIndex); + return getMeasuredParagraph(paraIndex).getCharWidthAt(offset - paraStart); + } + + /** * Returns the size of native PrecomputedText memory usage. * * Note that this is not guaranteed to be accurate. Must be used only for testing purposes. diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 4b78aa2332d5..6dad23815bef 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -21,7 +21,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Paint; -import android.text.AutoGrowArray.FloatArray; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; @@ -32,9 +31,6 @@ import android.util.Pools.SynchronizedPool; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; -import dalvik.annotation.optimization.CriticalNative; -import dalvik.annotation.optimization.FastNative; - import java.util.Arrays; /** @@ -57,7 +53,7 @@ public class StaticLayout extends Layout { * * - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in * native. - * - Run nComputeLineBreaks() to obtain line breaks for the paragraph. + * - Run NativeLineBreaker.computeLineBreaks() to obtain line breaks for the paragraph. * * After all paragraphs, call finish() to release expensive buffers. */ @@ -586,8 +582,7 @@ public class StaticLayout extends Layout { float ellipsizedWidth = b.mEllipsizedWidth; TextUtils.TruncateAt ellipsize = b.mEllipsize; final boolean addLastLineSpacing = b.mAddLastLineLineSpacing; - LineBreaks lineBreaks = new LineBreaks(); // TODO: move to builder to avoid allocation costs - FloatArray widths = new FloatArray(); + NativeLineBreaker.LineBreaks lineBreaks = new NativeLineBreaker.LineBreaks(); mLineCount = 0; mEllipsized = false; @@ -615,7 +610,7 @@ public class StaticLayout extends Layout { indents = null; } - final long nativePtr = nInit( + final NativeLineBreaker lineBreaker = new NativeLineBreaker( b.mBreakStrategy, b.mHyphenationFrequency, // TODO: Support more justification mode, e.g. letter spacing, stretching. b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE, @@ -639,243 +634,219 @@ public class StaticLayout extends Layout { bufEnd, false /* computeLayout */); } - try { - for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) { - final int paraStart = paraIndex == 0 - ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd; - final int paraEnd = paragraphInfo[paraIndex].paragraphEnd; - - int firstWidthLineCount = 1; - int firstWidth = outerWidth; - int restWidth = outerWidth; - - LineHeightSpan[] chooseHt = null; - - if (spanned != null) { - LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, - LeadingMarginSpan.class); - for (int i = 0; i < sp.length; i++) { - LeadingMarginSpan lms = sp[i]; - firstWidth -= sp[i].getLeadingMargin(true); - restWidth -= sp[i].getLeadingMargin(false); - - // LeadingMarginSpan2 is odd. The count affects all - // leading margin spans, not just this particular one - if (lms instanceof LeadingMarginSpan2) { - LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; - firstWidthLineCount = Math.max(firstWidthLineCount, - lms2.getLeadingMarginLineCount()); - } + for (int paraIndex = 0; paraIndex < paragraphInfo.length; paraIndex++) { + final int paraStart = paraIndex == 0 + ? bufStart : paragraphInfo[paraIndex - 1].paragraphEnd; + final int paraEnd = paragraphInfo[paraIndex].paragraphEnd; + + int firstWidthLineCount = 1; + int firstWidth = outerWidth; + int restWidth = outerWidth; + + LineHeightSpan[] chooseHt = null; + if (spanned != null) { + LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd, + LeadingMarginSpan.class); + for (int i = 0; i < sp.length; i++) { + LeadingMarginSpan lms = sp[i]; + firstWidth -= sp[i].getLeadingMargin(true); + restWidth -= sp[i].getLeadingMargin(false); + + // LeadingMarginSpan2 is odd. The count affects all + // leading margin spans, not just this particular one + if (lms instanceof LeadingMarginSpan2) { + LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms; + firstWidthLineCount = Math.max(firstWidthLineCount, + lms2.getLeadingMarginLineCount()); } + } - chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); + chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class); - if (chooseHt.length == 0) { - chooseHt = null; // So that out() would not assume it has any contents - } else { - if (chooseHtv == null || chooseHtv.length < chooseHt.length) { - chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); - } + if (chooseHt.length == 0) { + chooseHt = null; // So that out() would not assume it has any contents + } else { + if (chooseHtv == null || chooseHtv.length < chooseHt.length) { + chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length); + } - for (int i = 0; i < chooseHt.length; i++) { - int o = spanned.getSpanStart(chooseHt[i]); + for (int i = 0; i < chooseHt.length; i++) { + int o = spanned.getSpanStart(chooseHt[i]); - if (o < paraStart) { - // starts in this layout, before the - // current paragraph + if (o < paraStart) { + // starts in this layout, before the + // current paragraph - chooseHtv[i] = getLineTop(getLineForOffset(o)); - } else { - // starts in this paragraph + chooseHtv[i] = getLineTop(getLineForOffset(o)); + } else { + // starts in this paragraph - chooseHtv[i] = v; - } + chooseHtv[i] = v; } } } - - // tab stop locations - int[] variableTabStops = null; - if (spanned != null) { - TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, - paraEnd, TabStopSpan.class); - if (spans.length > 0) { - int[] stops = new int[spans.length]; - for (int i = 0; i < spans.length; i++) { - stops[i] = spans[i].getTabStop(); - } - Arrays.sort(stops, 0, stops.length); - variableTabStops = stops; + } + // tab stop locations + int[] variableTabStops = null; + if (spanned != null) { + TabStopSpan[] spans = getParagraphSpans(spanned, paraStart, + paraEnd, TabStopSpan.class); + if (spans.length > 0) { + int[] stops = new int[spans.length]; + for (int i = 0; i < spans.length; i++) { + stops[i] = spans[i].getTabStop(); } + Arrays.sort(stops, 0, stops.length); + variableTabStops = stops; } + } - final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured; - final char[] chs = measuredPara.getChars(); - final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); - final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); - // TODO: Stop keeping duplicated width copy in native and Java. - widths.resize(chs.length); - - // measurement has to be done before performing line breaking - // but we don't want to recompute fontmetrics or span ranges the - // second time, so we cache those and then use those stored values - - int breakCount = nComputeLineBreaks( - nativePtr, - - // Inputs - chs, - measuredPara.getNativePtr(), - paraEnd - paraStart, - firstWidth, - firstWidthLineCount, - restWidth, - variableTabStops, - TAB_INCREMENT, - mLineCount, - - // Outputs - lineBreaks, - lineBreaks.breaks.length, - lineBreaks.breaks, - lineBreaks.widths, - lineBreaks.ascents, - lineBreaks.descents, - lineBreaks.flags, - widths.getRawArray()); - - final int[] breaks = lineBreaks.breaks; - final float[] lineWidths = lineBreaks.widths; - final float[] ascents = lineBreaks.ascents; - final float[] descents = lineBreaks.descents; - final int[] flags = lineBreaks.flags; - - final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; - final boolean ellipsisMayBeApplied = ellipsize != null - && (ellipsize == TextUtils.TruncateAt.END - || (mMaximumVisibleLineCount == 1 - && ellipsize != TextUtils.TruncateAt.MARQUEE)); - if (0 < remainingLineCount && remainingLineCount < breakCount - && ellipsisMayBeApplied) { - // Calculate width and flag. - float width = 0; - int flag = 0; // XXX May need to also have starting hyphen edit - for (int i = remainingLineCount - 1; i < breakCount; i++) { - if (i == breakCount - 1) { - width += lineWidths[i]; - } else { - for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { - width += widths.get(j); - } + final MeasuredParagraph measuredPara = paragraphInfo[paraIndex].measured; + final char[] chs = measuredPara.getChars(); + final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray(); + final int[] fmCache = measuredPara.getFontMetrics().getRawArray(); + int breakCount = lineBreaker.computeLineBreaks( + measuredPara.getChars(), + measuredPara.getNativeMeasuredParagraph(), + paraEnd - paraStart, + firstWidth, + firstWidthLineCount, + restWidth, + variableTabStops, + TAB_INCREMENT, + mLineCount, + lineBreaks); + + final int[] breaks = lineBreaks.breaks; + final float[] lineWidths = lineBreaks.widths; + final float[] ascents = lineBreaks.ascents; + final float[] descents = lineBreaks.descents; + final int[] flags = lineBreaks.flags; + + final int remainingLineCount = mMaximumVisibleLineCount - mLineCount; + final boolean ellipsisMayBeApplied = ellipsize != null + && (ellipsize == TextUtils.TruncateAt.END + || (mMaximumVisibleLineCount == 1 + && ellipsize != TextUtils.TruncateAt.MARQUEE)); + if (0 < remainingLineCount && remainingLineCount < breakCount + && ellipsisMayBeApplied) { + // Calculate width + float width = 0; + int flag = 0; // XXX May need to also have starting hyphen edit + for (int i = remainingLineCount - 1; i < breakCount; i++) { + if (i == breakCount - 1) { + width += lineWidths[i]; + } else { + for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) { + width += measuredPara.getCharWidthAt(j - paraStart); } - flag |= flags[i] & TAB_MASK; } - // Treat the last line and overflowed lines as a single line. - breaks[remainingLineCount - 1] = breaks[breakCount - 1]; - lineWidths[remainingLineCount - 1] = width; - flags[remainingLineCount - 1] = flag; + flag |= flags[i] & TAB_MASK; + } + // Treat the last line and overflowed lines as a single line. + breaks[remainingLineCount - 1] = breaks[breakCount - 1]; + lineWidths[remainingLineCount - 1] = width; + flags[remainingLineCount - 1] = flag; + + breakCount = remainingLineCount; + } + + // here is the offset of the starting character of the line we are currently + // measuring + int here = paraStart; + + int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; + int fmCacheIndex = 0; + int spanEndCacheIndex = 0; + int breakIndex = 0; + for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { + // retrieve end of span + spanEnd = spanEndCache[spanEndCacheIndex++]; + + // retrieve cached metrics, order matches above + fm.top = fmCache[fmCacheIndex * 4 + 0]; + fm.bottom = fmCache[fmCacheIndex * 4 + 1]; + fm.ascent = fmCache[fmCacheIndex * 4 + 2]; + fm.descent = fmCache[fmCacheIndex * 4 + 3]; + fmCacheIndex++; + + if (fm.top < fmTop) { + fmTop = fm.top; + } + if (fm.ascent < fmAscent) { + fmAscent = fm.ascent; + } + if (fm.descent > fmDescent) { + fmDescent = fm.descent; + } + if (fm.bottom > fmBottom) { + fmBottom = fm.bottom; + } - breakCount = remainingLineCount; + // skip breaks ending before current span range + while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { + breakIndex++; } - // here is the offset of the starting character of the line we are currently - // measuring - int here = paraStart; - - int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0; - int fmCacheIndex = 0; - int spanEndCacheIndex = 0; - int breakIndex = 0; - for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) { - // retrieve end of span - spanEnd = spanEndCache[spanEndCacheIndex++]; - - // retrieve cached metrics, order matches above - fm.top = fmCache[fmCacheIndex * 4 + 0]; - fm.bottom = fmCache[fmCacheIndex * 4 + 1]; - fm.ascent = fmCache[fmCacheIndex * 4 + 2]; - fm.descent = fmCache[fmCacheIndex * 4 + 3]; - fmCacheIndex++; - - if (fm.top < fmTop) { + while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { + int endPos = paraStart + breaks[breakIndex]; + + boolean moreChars = (endPos < bufEnd); + + final int ascent = fallbackLineSpacing + ? Math.min(fmAscent, Math.round(ascents[breakIndex])) + : fmAscent; + final int descent = fallbackLineSpacing + ? Math.max(fmDescent, Math.round(descents[breakIndex])) + : fmDescent; + + v = out(source, here, endPos, + ascent, descent, fmTop, fmBottom, + v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, + flags[breakIndex], needMultiply, measuredPara, bufEnd, + includepad, trackpad, addLastLineSpacing, chs, + paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], + paint, moreChars); + + if (endPos < spanEnd) { + // preserve metrics for current span fmTop = fm.top; - } - if (fm.ascent < fmAscent) { + fmBottom = fm.bottom; fmAscent = fm.ascent; - } - if (fm.descent > fmDescent) { fmDescent = fm.descent; + } else { + fmTop = fmBottom = fmAscent = fmDescent = 0; } - if (fm.bottom > fmBottom) { - fmBottom = fm.bottom; - } - - // skip breaks ending before current span range - while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) { - breakIndex++; - } - - while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) { - int endPos = paraStart + breaks[breakIndex]; - - boolean moreChars = (endPos < bufEnd); - - final int ascent = fallbackLineSpacing - ? Math.min(fmAscent, Math.round(ascents[breakIndex])) - : fmAscent; - final int descent = fallbackLineSpacing - ? Math.max(fmDescent, Math.round(descents[breakIndex])) - : fmDescent; - v = out(source, here, endPos, - ascent, descent, fmTop, fmBottom, - v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, - flags[breakIndex], needMultiply, measuredPara, bufEnd, - includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(), - paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex], - paint, moreChars); - - if (endPos < spanEnd) { - // preserve metrics for current span - fmTop = fm.top; - fmBottom = fm.bottom; - fmAscent = fm.ascent; - fmDescent = fm.descent; - } else { - fmTop = fmBottom = fmAscent = fmDescent = 0; - } - here = endPos; - breakIndex++; + here = endPos; + breakIndex++; - if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { - return; - } + if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) { + return; } } - - if (paraEnd == bufEnd) { - break; - } } - if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) - && mLineCount < mMaximumVisibleLineCount) { - final MeasuredParagraph measuredPara = - MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); - paint.getFontMetricsInt(fm); - v = out(source, - bufEnd, bufEnd, fm.ascent, fm.descent, - fm.top, fm.bottom, - v, - spacingmult, spacingadd, null, - null, fm, 0, - needMultiply, measuredPara, bufEnd, - includepad, trackpad, addLastLineSpacing, null, - null, bufStart, ellipsize, - ellipsizedWidth, 0, paint, false); + if (paraEnd == bufEnd) { + break; } - } finally { - nFinish(nativePtr); + } + + if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) + && mLineCount < mMaximumVisibleLineCount) { + final MeasuredParagraph measuredPara = + MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null); + paint.getFontMetricsInt(fm); + v = out(source, + bufEnd, bufEnd, fm.ascent, fm.descent, + fm.top, fm.bottom, + v, + spacingmult, spacingadd, null, + null, fm, 0, + needMultiply, measuredPara, bufEnd, + includepad, trackpad, addLastLineSpacing, null, + bufStart, ellipsize, + ellipsizedWidth, 0, paint, false); } } @@ -884,7 +855,7 @@ public class StaticLayout extends Layout { final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured, final int bufEnd, final boolean includePad, final boolean trackPad, - final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, + final boolean addLastLineLineSpacing, final char[] chs, final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, final float textWidth, final TextPaint paint, final boolean moreChars) { final int j = mLineCount; @@ -942,7 +913,7 @@ public class StaticLayout extends Layout { (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) && ellipsize == TextUtils.TruncateAt.END); if (doEllipsis) { - calculateEllipsis(start, end, widths, widthStart, + calculateEllipsis(start, end, measured, widthStart, ellipsisWidth, ellipsize, j, textWidth, paint, forceEllipsis); } @@ -1026,7 +997,7 @@ public class StaticLayout extends Layout { } private void calculateEllipsis(int lineStart, int lineEnd, - float[] widths, int widthStart, + MeasuredParagraph measured, int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth, TextPaint paint, boolean forceEllipsis) { @@ -1050,9 +1021,10 @@ public class StaticLayout extends Layout { int i; for (i = len; i > 0; i--) { - float w = widths[i - 1 + lineStart - widthStart]; + float w = measured.getCharWidthAt(i - 1 + lineStart - widthStart); if (w + sum + ellipsisWidth > avail) { - while (i < len && widths[i + lineStart - widthStart] == 0.0f) { + while (i < len + && measured.getCharWidthAt(i + lineStart - widthStart) == 0.0f) { i++; } break; @@ -1074,7 +1046,7 @@ public class StaticLayout extends Layout { int i; for (i = 0; i < len; i++) { - float w = widths[i + lineStart - widthStart]; + float w = measured.getCharWidthAt(i + lineStart - widthStart); if (w + sum + ellipsisWidth > avail) { break; @@ -1097,10 +1069,12 @@ public class StaticLayout extends Layout { float ravail = (avail - ellipsisWidth) / 2; for (right = len; right > 0; right--) { - float w = widths[right - 1 + lineStart - widthStart]; + float w = measured.getCharWidthAt(right - 1 + lineStart - widthStart); if (w + rsum > ravail) { - while (right < len && widths[right + lineStart - widthStart] == 0.0f) { + while (right < len + && measured.getCharWidthAt(right + lineStart - widthStart) + == 0.0f) { right++; } break; @@ -1110,7 +1084,7 @@ public class StaticLayout extends Layout { float lavail = avail - ellipsisWidth - rsum; for (left = 0; left < right; left++) { - float w = widths[left + lineStart - widthStart]; + float w = measured.getCharWidthAt(left + lineStart - widthStart); if (w + lsum > lavail) { break; @@ -1306,47 +1280,6 @@ public class StaticLayout extends Layout { ? mMaxLineHeight : super.getHeight(); } - @FastNative - private static native long nInit( - @BreakStrategy int breakStrategy, - @HyphenationFrequency int hyphenationFrequency, - boolean isJustified, - @Nullable int[] indents); - - @CriticalNative - private static native void nFinish(long nativePtr); - - // populates LineBreaks and returns the number of breaks found - // - // the arrays inside the LineBreaks objects are passed in as well - // to reduce the number of JNI calls in the common case where the - // arrays do not have to be resized - // The individual character widths will be returned in charWidths. The length of charWidths must - // be at least the length of the text. - private static native int nComputeLineBreaks( - /* non zero */ long nativePtr, - - // Inputs - @NonNull char[] text, - /* Non Zero */ long measuredTextPtr, - @IntRange(from = 0) int length, - @FloatRange(from = 0.0f) float firstWidth, - @IntRange(from = 0) int firstWidthLineCount, - @FloatRange(from = 0.0f) float restWidth, - @Nullable int[] variableTabStops, - int defaultTabStop, - @IntRange(from = 0) int indentsOffset, - - // Outputs - @NonNull LineBreaks recycle, - @IntRange(from = 0) int recycleLength, - @NonNull int[] recycleBreaks, - @NonNull float[] recycleWidths, - @NonNull float[] recycleAscents, - @NonNull float[] recycleDescents, - @NonNull int[] recycleFlags, - @NonNull float[] charWidths); - private int mLineCount; private int mTopPadding, mBottomPadding; private int mColumns; @@ -1396,8 +1329,7 @@ public class StaticLayout extends Layout { private static final int DEFAULT_MAX_LINE_HEIGHT = -1; - // This is used to return three arrays from a single JNI call when - // performing line breaking + // Unused, here because of gray list private API accesses. /*package*/ static class LineBreaks { private static final int INITIAL_SIZE = 16; public int[] breaks = new int[INITIAL_SIZE]; diff --git a/core/java/android/view/RecordingCanvas.java b/core/java/android/view/RecordingCanvas.java index 18cc10f5b2f2..74fa9e875076 100644 --- a/core/java/android/view/RecordingCanvas.java +++ b/core/java/android/view/RecordingCanvas.java @@ -515,7 +515,7 @@ public class RecordingCanvas extends Canvas { contextStart - paraStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), - mp.getNativePtr()); + mp.getNativeMeasuredParagraph().getNativePtr()); return; } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 240f3c009841..97a2d795edaa 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3097,13 +3097,27 @@ public final class ViewRootImpl implements ViewParent, Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); boolean usingAsyncReport = false; - if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null - && mAttachInfo.mThreadedRenderer.isEnabled()) { - usingAsyncReport = true; - mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { - // TODO: Use the frame number - pendingDrawFinished(); - }); + if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) { + ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver + .captureFrameCommitCallbacks(); + if (mReportNextDraw) { + usingAsyncReport = true; + mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { + // TODO: Use the frame number + pendingDrawFinished(); + if (commitCallbacks != null) { + for (int i = 0; i < commitCallbacks.size(); i++) { + commitCallbacks.get(i).run(); + } + } + }); + } else if (commitCallbacks != null && commitCallbacks.size() > 0) { + mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> { + for (int i = 0; i < commitCallbacks.size(); i++) { + commitCallbacks.get(i).run(); + } + }); + } } try { diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 0973d0aa9b90..efc18072e547 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -16,6 +16,9 @@ package android.view; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.TestApi; import android.content.Context; import android.graphics.Rect; import android.graphics.Region; @@ -56,6 +59,9 @@ public final class ViewTreeObserver { private ArrayList<OnDrawListener> mOnDrawListeners; private static boolean sIllegalOnDrawModificationIsFatal; + // These listeners are one-shot + private ArrayList<Runnable> mOnFrameCommitListeners; + /** Remains false until #dispatchOnWindowShown() is called. If a listener registers after * that the listener will be immediately called. */ private boolean mWindowShown; @@ -393,6 +399,14 @@ public final class ViewTreeObserver { } } + if (observer.mOnFrameCommitListeners != null) { + if (mOnFrameCommitListeners != null) { + mOnFrameCommitListeners.addAll(observer.captureFrameCommitCallbacks()); + } else { + mOnFrameCommitListeners = observer.captureFrameCommitCallbacks(); + } + } + if (observer.mOnTouchModeChangeListeners != null) { if (mOnTouchModeChangeListeners != null) { mOnTouchModeChangeListeners.addAll(observer.mOnTouchModeChangeListeners); @@ -713,6 +727,49 @@ public final class ViewTreeObserver { } /** + * Adds a frame commit callback. This callback will be invoked when the current rendering + * content has been rendered into a frame and submitted to the swap chain. The frame may + * not currently be visible on the display when this is invoked, but it has been submitted. + * This callback is useful in combination with {@link PixelCopy} to capture the current + * rendered content of the UI reliably. + * + * Note: Only works with hardware rendering. Does nothing otherwise. + * + * @param callback The callback to invoke when the frame is committed. + */ + @TestApi + public void registerFrameCommitCallback(@NonNull Runnable callback) { + checkIsAlive(); + if (mOnFrameCommitListeners == null) { + mOnFrameCommitListeners = new ArrayList<>(); + } + mOnFrameCommitListeners.add(callback); + } + + @Nullable ArrayList<Runnable> captureFrameCommitCallbacks() { + ArrayList<Runnable> ret = mOnFrameCommitListeners; + mOnFrameCommitListeners = null; + return ret; + } + + /** + * Attempts to remove the given callback from the list of pending frame complete callbacks. + * + * @param callback The callback to remove + * @return Whether or not the callback was removed. If this returns true the callback will + * not be invoked. If false is returned then the callback was either never added + * or may already be pending execution and was unable to be removed + */ + @TestApi + public boolean unregisterFrameCommitCallback(@NonNull Runnable callback) { + checkIsAlive(); + if (mOnFrameCommitListeners == null) { + return false; + } + return mOnFrameCommitListeners.remove(callback); + } + + /** * Register a callback to be invoked when a view has been scrolled. * * @param listener The callback to add diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index 0e1e379d610a..b4d9c53aedb8 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -336,7 +336,13 @@ public class AccessibilityCache { AccessibilityNodeInfo clone = AccessibilityNodeInfo.obtain(info); nodes.put(sourceId, clone); if (clone.isAccessibilityFocused()) { + if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID + && mAccessibilityFocus != sourceId) { + refreshCachedNodeLocked(windowId, mAccessibilityFocus); + } mAccessibilityFocus = sourceId; + } else if (mAccessibilityFocus == sourceId) { + mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; } if (clone.isFocused()) { mInputFocus = sourceId; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 41daf9e11370..b5c736480215 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -34,6 +34,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.metrics.LogMaker; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; @@ -202,10 +203,17 @@ public final class AutofillManager { /** @hide */ public static final int ACTION_VIEW_EXITED = 3; /** @hide */ public static final int ACTION_VALUE_CHANGED = 4; - + /** @hide */ public static final int NO_LOGGING = 0; /** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1; /** @hide */ public static final int FLAG_ADD_CLIENT_DEBUG = 0x2; /** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4; + /** @hide */ + public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE + ? AutofillManager.FLAG_ADD_CLIENT_DEBUG + : AutofillManager.NO_LOGGING; + + /** @hide */ + public static final int DEFAULT_MAX_PARTITIONS_SIZE = 10; /** Which bits in an authentication id are used for the dataset id */ private static final int AUTHENTICATION_ID_DATASET_ID_MASK = 0xFFFF; diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java index f686b6645655..bdd7a0900213 100644 --- a/core/java/android/webkit/WebViewClient.java +++ b/core/java/android/webkit/WebViewClient.java @@ -31,18 +31,25 @@ import java.lang.annotation.RetentionPolicy; public class WebViewClient { /** - * Give the host application a chance to take over the control when a new - * url is about to be loaded in the current WebView. If WebViewClient is not - * provided, by default WebView will ask Activity Manager to choose the - * proper handler for the url. If WebViewClient is provided, return {@code true} - * means the host application handles the url, while return {@code false} means the - * current WebView handles the url. - * This method is not called for requests using the POST "method". + * Give the host application a chance to take control when a URL is about to be loaded in the + * current WebView. If a WebViewClient is not provided, by default WebView will ask Activity + * Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning + * {@code true} causes the current WebView to abort loading the URL, while returning + * {@code false} causes the WebView to continue loading the URL as usual. + * + * <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the same + * URL and then return {@code true}. This unnecessarily cancels the current load and starts a + * new load with the same URL. The correct way to continue loading a given URL is to simply + * return {@code false}, without calling {@link WebView#loadUrl(String)}. + * + * <p class="note"><b>Note:</b> This method is not called for POST requests. + * + * <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S) + * schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail. * * @param view The WebView that is initiating the callback. - * @param url The url to be loaded. - * @return {@code true} if the host application wants to leave the current WebView - * and handle the url itself, otherwise return {@code false}. + * @param url The URL to be loaded. + * @return {@code true} to cancel the current load, otherwise return {@code false}. * @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest) * shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead. */ @@ -52,26 +59,25 @@ public class WebViewClient { } /** - * Give the host application a chance to take over the control when a new - * url is about to be loaded in the current WebView. If WebViewClient is not - * provided, by default WebView will ask Activity Manager to choose the - * proper handler for the url. If WebViewClient is provided, return {@code true} - * means the host application handles the url, while return {@code false} means the - * current WebView handles the url. - * - * <p>Notes: - * <ul> - * <li>This method is not called for requests using the POST "method".</li> - * <li>This method is also called for subframes with non-http schemes, thus it is - * strongly disadvised to unconditionally call {@link WebView#loadUrl(String)} - * with the request's url from inside the method and then return {@code true}, - * as this will make WebView to attempt loading a non-http url, and thus fail.</li> - * </ul> + * Give the host application a chance to take control when a URL is about to be loaded in the + * current WebView. If a WebViewClient is not provided, by default WebView will ask Activity + * Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning + * {@code true} causes the current WebView to abort loading the URL, while returning + * {@code false} causes the WebView to continue loading the URL as usual. + * + * <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's + * URL and then return {@code true}. This unnecessarily cancels the current load and starts a + * new load with the same URL. The correct way to continue loading a given URL is to simply + * return {@code false}, without calling {@link WebView#loadUrl(String)}. + * + * <p class="note"><b>Note:</b> This method is not called for POST requests. + * + * <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S) + * schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail. * * @param view The WebView that is initiating the callback. * @param request Object containing the details of the request. - * @return {@code true} if the host application wants to leave the current WebView - * and handle the url itself, otherwise return {@code false}. + * @return {@code true} to cancel the current load, otherwise return {@code false}. */ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return shouldOverrideUrlLoading(view, request.getUrl().toString()); diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java index e5d6556e1218..f63c43f6614f 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -36,7 +36,6 @@ public final class AssociationState { private final ProcessStats.PackageState mPackageState; private final String mProcessName; private final String mName; - private final DurationsTable mDurations; public final class SourceState { final SourceKey mKey; @@ -49,8 +48,10 @@ public final class AssociationState { long mDuration; long mTrackingUptime; int mActiveCount; + int mActiveProcState = ProcessStats.STATE_NOTHING; long mActiveStartUptime; long mActiveDuration; + DurationsTable mDurations; SourceState(SourceKey key) { mKey = key; @@ -77,13 +78,15 @@ public final class AssociationState { mProcState = procState; } if (procState < ProcessStats.STATE_HOME) { + // If the proc state has become better than cached, then we want to + // start tracking it to count when it is actually active. If it drops + // down to cached, we will clean it up when we later evaluate all currently + // tracked associations in ProcessStats.updateTrackingAssociationsLocked(). if (!mInTrackingList) { mInTrackingList = true; mTrackingUptime = now; mProcessStats.mTrackingAssociations.add(this); } - } else { - stopTracking(now); } } @@ -102,6 +105,22 @@ public final class AssociationState { mActiveStartUptime = now; mActiveCount++; } + if (mActiveProcState != mProcState) { + if (mActiveProcState != ProcessStats.STATE_NOTHING) { + // Currently active proc state changed, need to store the duration + // so far and switch tracking to the new proc state. + final long duration = mActiveDuration + now - mActiveStartUptime; + if (duration != 0) { + if (mDurations == null) { + makeDurations(); + } + mDurations.addDuration(mActiveProcState, duration); + mActiveDuration = 0; + } + mActiveStartUptime = now; + } + mActiveProcState = mProcState; + } } else { Slog.wtf(TAG, "startActive while not tracking: " + this); } @@ -112,15 +131,25 @@ public final class AssociationState { if (!mInTrackingList) { Slog.wtf(TAG, "stopActive while not tracking: " + this); } - mActiveDuration += now - mActiveStartUptime; + final long duration = mActiveDuration + now - mActiveStartUptime; + if (mDurations != null) { + mDurations.addDuration(mActiveProcState, duration); + } else { + mActiveDuration = duration; + } mActiveStartUptime = 0; } } + void makeDurations() { + mDurations = new DurationsTable(mProcessStats.mTableData); + } + void stopTracking(long now) { stopActive(now); if (mInTrackingList) { mInTrackingList = false; + mProcState = ProcessStats.STATE_NOTHING; // Do a manual search for where to remove, since these objects will typically // be towards the end of the array. final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations; @@ -207,7 +236,6 @@ public final class AssociationState { mPackageState = packageState; mName = name; mProcessName = processName; - mDurations = new DurationsTable(processStats.mTableData); mProc = proc; } @@ -254,7 +282,6 @@ public final class AssociationState { } public void add(AssociationState other) { - mDurations.addDurations(other.mDurations); for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) { final SourceKey key = other.mSources.keyAt(isrc); final SourceState otherSrc = other.mSources.valueAt(isrc); @@ -266,7 +293,48 @@ public final class AssociationState { mySrc.mCount += otherSrc.mCount; mySrc.mDuration += otherSrc.mDuration; mySrc.mActiveCount += otherSrc.mActiveCount; - mySrc.mActiveDuration += otherSrc.mActiveDuration; + if (otherSrc.mActiveDuration != 0 || otherSrc.mDurations != null) { + // Only need to do anything if the other one has some duration data. + if (mySrc.mDurations != null) { + // If the target already has multiple durations, just add in whatever + // we have in the other. + if (otherSrc.mDurations != null) { + mySrc.mDurations.addDurations(otherSrc.mDurations); + } else { + mySrc.mDurations.addDuration(otherSrc.mActiveProcState, + otherSrc.mActiveDuration); + } + } else if (otherSrc.mDurations != null) { + // The other one has multiple durations, but we don't. Expand to + // multiple durations and copy over. + mySrc.makeDurations(); + mySrc.mDurations.addDurations(otherSrc.mDurations); + if (mySrc.mActiveDuration != 0) { + mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration); + mySrc.mActiveDuration = 0; + mySrc.mActiveProcState = ProcessStats.STATE_NOTHING; + } + } else if (mySrc.mActiveDuration != 0) { + // Both have a single inline duration... we can either add them together, + // or need to expand to multiple durations. + if (mySrc.mActiveProcState == otherSrc.mActiveProcState) { + mySrc.mDuration += otherSrc.mDuration; + } else { + // The two have durations with different proc states, need to turn + // in to multiple durations. + mySrc.makeDurations(); + mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration); + mySrc.mDurations.addDuration(otherSrc.mActiveProcState, + otherSrc.mActiveDuration); + mySrc.mActiveDuration = 0; + mySrc.mActiveProcState = ProcessStats.STATE_NOTHING; + } + } else { + // The other one has a duration, and we know the target doesn't. Copy over. + mySrc.mActiveProcState = otherSrc.mActiveProcState; + mySrc.mActiveDuration = otherSrc.mActiveDuration; + } + } } } @@ -275,7 +343,6 @@ public final class AssociationState { } public void resetSafely(long now) { - mDurations.resetTable(); if (!isInUse()) { mSources.clear(); } else { @@ -293,6 +360,7 @@ public final class AssociationState { src.mActiveCount = 0; } src.mActiveDuration = 0; + src.mDurations = null; } else { mSources.removeAt(isrc); } @@ -301,7 +369,6 @@ public final class AssociationState { } public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) { - mDurations.writeToParcel(out); final int NSRC = mSources.size(); out.writeInt(NSRC); for (int isrc = 0; isrc < NSRC; isrc++) { @@ -312,7 +379,14 @@ public final class AssociationState { out.writeInt(src.mCount); out.writeLong(src.mDuration); out.writeInt(src.mActiveCount); - out.writeLong(src.mActiveDuration); + if (src.mDurations != null) { + out.writeInt(1); + src.mDurations.writeToParcel(out); + } else { + out.writeInt(0); + out.writeInt(src.mActiveProcState); + out.writeLong(src.mActiveDuration); + } } } @@ -321,9 +395,6 @@ public final class AssociationState { * caused it to fail. */ public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) { - if (!mDurations.readFromParcel(in)) { - return "Duration table corrupt"; - } final int NSRC = in.readInt(); if (NSRC < 0 || NSRC > 100000) { return "Association with bad src count: " + NSRC; @@ -336,7 +407,15 @@ public final class AssociationState { src.mCount = in.readInt(); src.mDuration = in.readLong(); src.mActiveCount = in.readInt(); - src.mActiveDuration = in.readLong(); + if (in.readInt() != 0) { + src.makeDurations(); + if (!src.mDurations.readFromParcel(in)) { + return "Duration table corrupt: " + key + " <- " + src; + } + } else { + src.mActiveProcState = in.readInt(); + src.mActiveDuration = in.readLong(); + } mSources.put(key, src); } return null; @@ -351,7 +430,12 @@ public final class AssociationState { src.mStartUptime = nowUptime; } if (src.mActiveStartUptime > 0) { - src.mActiveDuration += nowUptime - src.mActiveStartUptime; + final long duration = src.mActiveDuration + nowUptime - src.mActiveStartUptime; + if (src.mDurations != null) { + src.mDurations.addDuration(src.mActiveProcState, duration); + } else { + src.mActiveDuration = duration; + } src.mActiveStartUptime = nowUptime; } } @@ -359,7 +443,7 @@ public final class AssociationState { } public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix, - long now, long totalTime, boolean dumpSummary, boolean dumpAll) { + long now, long totalTime, boolean dumpDetails, boolean dumpAll) { if (dumpAll) { pw.print(prefix); pw.print("mNumActive="); @@ -376,18 +460,18 @@ public final class AssociationState { UserHandle.formatUid(pw, key.mUid); pw.println(":"); pw.print(prefixInner); - pw.print(" Count "); + pw.print(" Total count "); pw.print(src.mCount); long duration = src.mDuration; if (src.mNesting > 0) { duration += now - src.mStartUptime; } if (dumpAll) { - pw.print(" / Duration "); + pw.print(": Duration "); TimeUtils.formatDuration(duration, pw); pw.print(" / "); } else { - pw.print(" / time "); + pw.print(": time "); } DumpUtils.printPercent(pw, (double)duration/(double)totalTime); if (src.mNesting > 0) { @@ -401,30 +485,120 @@ public final class AssociationState { pw.print(")"); } pw.println(); - if (src.mActiveCount > 0) { + if (src.mActiveCount > 0 || src.mDurations != null || src.mActiveDuration != 0 + || src.mActiveStartUptime != 0) { pw.print(prefixInner); pw.print(" Active count "); pw.print(src.mActiveCount); - duration = src.mActiveDuration; - if (src.mActiveStartUptime > 0) { - duration += now - src.mActiveStartUptime; - } - if (dumpAll) { - pw.print(" / Duration "); - TimeUtils.formatDuration(duration, pw); - pw.print(" / "); + if (dumpDetails) { + if (dumpAll) { + pw.print(src.mDurations != null ? " (multi-field)" : " (inline)"); + } + pw.println(":"); + dumpTime(pw, prefixInner, src, totalTime, now, dumpDetails, dumpAll); } else { - pw.print(" / time "); + pw.print(": "); + dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll); + pw.println(); } - DumpUtils.printPercent(pw, (double)duration/(double)totalTime); - if (src.mActiveStartUptime > 0) { - pw.print(" (running)"); + } + if (dumpAll) { + if (src.mInTrackingList) { + pw.print(prefixInner); + pw.print(" mInTrackingList="); + pw.println(src.mInTrackingList); + } + if (src.mProcState != ProcessStats.STATE_NOTHING) { + pw.print(prefixInner); + pw.print(" mProcState="); + pw.print(DumpUtils.STATE_NAMES[src.mProcState]); + pw.print(" mProcStateSeq="); + pw.println(src.mProcStateSeq); } - pw.println(); } } } + void dumpActiveDurationSummary(PrintWriter pw, final SourceState src, long totalTime, + long now, boolean dumpAll) { + long duration = dumpTime(null, null, src, totalTime, now, false, false); + final boolean isRunning = duration < 0; + if (isRunning) { + duration = -duration; + } + if (dumpAll) { + pw.print("Duration "); + TimeUtils.formatDuration(duration, pw); + pw.print(" / "); + } else { + pw.print("time "); + } + DumpUtils.printPercent(pw, (double) duration / (double) totalTime); + if (src.mActiveStartUptime > 0) { + pw.print(" (running)"); + } + pw.println(); + } + + long dumpTime(PrintWriter pw, String prefix, final SourceState src, long overallTime, long now, + boolean dumpDetails, boolean dumpAll) { + long totalTime = 0; + boolean isRunning = false; + for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) { + long time; + if (src.mDurations != null) { + time = src.mDurations.getValueForId((byte)iprocstate); + } else { + time = src.mActiveProcState == iprocstate ? src.mDuration : 0; + } + final String running; + if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) { + running = " (running)"; + isRunning = true; + time += now - src.mActiveStartUptime; + } else { + running = null; + } + if (time != 0) { + if (pw != null) { + pw.print(prefix); + pw.print(" "); + pw.print(DumpUtils.STATE_LABELS[iprocstate]); + pw.print(": "); + if (dumpAll) { + pw.print("Duration "); + TimeUtils.formatDuration(time, pw); + pw.print(" / "); + } else { + pw.print("time "); + } + DumpUtils.printPercent(pw, (double) time / (double) overallTime); + if (running != null) { + pw.print(running); + } + pw.println(); + } + totalTime += time; + } + } + if (totalTime != 0 && pw != null) { + pw.print(prefix); + pw.print(" "); + pw.print(DumpUtils.STATE_LABEL_TOTAL); + pw.print(": "); + if (dumpAll) { + pw.print("Duration "); + TimeUtils.formatDuration(totalTime, pw); + pw.print(" / "); + } else { + pw.print("time "); + } + DumpUtils.printPercent(pw, (double) totalTime / (double) overallTime); + pw.println(); + } + return isRunning ? -totalTime : totalTime; + } + public void dumpTimesCheckin(PrintWriter pw, String pkgName, int uid, long vers, String associationName, long now) { final int NSRC = mSources.size(); @@ -454,12 +628,30 @@ public final class AssociationState { pw.print(duration); pw.print(","); pw.print(src.mActiveCount); - duration = src.mActiveDuration; - if (src.mActiveStartUptime > 0) { - duration += now - src.mActiveStartUptime; + final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0; + if (src.mDurations != null) { + final int N = src.mDurations.getKeyCount(); + for (int i=0; i<N; i++) { + final int dkey = src.mDurations.getKeyAt(i); + duration = src.mDurations.getValue(dkey); + if (dkey == src.mActiveProcState) { + duration += timeNow; + } + final int procState = SparseMappingTable.getIdFromKey(dkey); + pw.print(","); + DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, procState, 1); + pw.print(':'); + pw.print(duration); + } + } else { + duration = src.mActiveDuration + timeNow; + if (duration != 0) { + pw.print(","); + DumpUtils.printArrayEntry(pw, DumpUtils.STATE_TAGS, src.mActiveProcState, 1); + pw.print(':'); + pw.print(duration); + } } - pw.print(","); - pw.print(duration); pw.println(); } } diff --git a/core/java/com/android/internal/app/procstats/DumpUtils.java b/core/java/com/android/internal/app/procstats/DumpUtils.java index 06b6552ca59f..e6073e58b53c 100644 --- a/core/java/com/android/internal/app/procstats/DumpUtils.java +++ b/core/java/com/android/internal/app/procstats/DumpUtils.java @@ -48,6 +48,9 @@ import java.util.Objects; */ public final class DumpUtils { public static final String[] STATE_NAMES; + public static final String[] STATE_LABELS; + public static final String STATE_LABEL_TOTAL; + public static final String STATE_LABEL_CACHED; public static final String[] STATE_NAMES_CSV; static final String[] STATE_TAGS; static final int[] STATE_PROTO_ENUMS; @@ -55,52 +58,70 @@ public final class DumpUtils { // Make the mapping easy to update. static { STATE_NAMES = new String[STATE_COUNT]; - STATE_NAMES[STATE_PERSISTENT] = "Persist"; - STATE_NAMES[STATE_TOP] = "Top"; - STATE_NAMES[STATE_IMPORTANT_FOREGROUND] = "ImpFg"; - STATE_NAMES[STATE_IMPORTANT_BACKGROUND] = "ImpBg"; - STATE_NAMES[STATE_BACKUP] = "Backup"; - STATE_NAMES[STATE_SERVICE] = "Service"; - STATE_NAMES[STATE_SERVICE_RESTARTING] = "ServRst"; - STATE_NAMES[STATE_RECEIVER] = "Receivr"; - STATE_NAMES[STATE_HEAVY_WEIGHT] = "HeavyWt"; - STATE_NAMES[STATE_HOME] = "Home"; - STATE_NAMES[STATE_LAST_ACTIVITY] = "LastAct"; - STATE_NAMES[STATE_CACHED_ACTIVITY] = "CchAct"; - STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT] = "CchCAct"; - STATE_NAMES[STATE_CACHED_EMPTY] = "CchEmty"; + STATE_NAMES[STATE_PERSISTENT] = "Persist"; + STATE_NAMES[STATE_TOP] = "Top"; + STATE_NAMES[STATE_IMPORTANT_FOREGROUND] = "ImpFg"; + STATE_NAMES[STATE_IMPORTANT_BACKGROUND] = "ImpBg"; + STATE_NAMES[STATE_BACKUP] = "Backup"; + STATE_NAMES[STATE_SERVICE] = "Service"; + STATE_NAMES[STATE_SERVICE_RESTARTING] = "ServRst"; + STATE_NAMES[STATE_RECEIVER] = "Receivr"; + STATE_NAMES[STATE_HEAVY_WEIGHT] = "HeavyWt"; + STATE_NAMES[STATE_HOME] = "Home"; + STATE_NAMES[STATE_LAST_ACTIVITY] = "LastAct"; + STATE_NAMES[STATE_CACHED_ACTIVITY] = "CchAct"; + STATE_NAMES[STATE_CACHED_ACTIVITY_CLIENT] = "CchCAct"; + STATE_NAMES[STATE_CACHED_EMPTY] = "CchEmty"; + + STATE_LABELS = new String[STATE_COUNT]; + STATE_LABELS[STATE_PERSISTENT] = "Persistent"; + STATE_LABELS[STATE_TOP] = " Top"; + STATE_LABELS[STATE_IMPORTANT_FOREGROUND] = " Imp Fg"; + STATE_LABELS[STATE_IMPORTANT_BACKGROUND] = " Imp Bg"; + STATE_LABELS[STATE_BACKUP] = " Backup"; + STATE_LABELS[STATE_SERVICE] = " Service"; + STATE_LABELS[STATE_SERVICE_RESTARTING] = "Service Rs"; + STATE_LABELS[STATE_RECEIVER] = " Receiver"; + STATE_LABELS[STATE_HEAVY_WEIGHT] = " Heavy Wgt"; + STATE_LABELS[STATE_HOME] = " (Home)"; + STATE_LABELS[STATE_LAST_ACTIVITY] = "(Last Act)"; + STATE_LABELS[STATE_CACHED_ACTIVITY] = " (Cch Act)"; + STATE_LABELS[STATE_CACHED_ACTIVITY_CLIENT] = "(Cch CAct)"; + STATE_LABELS[STATE_CACHED_EMPTY] = "(Cch Emty)"; + STATE_LABEL_CACHED = " (Cached)"; + STATE_LABEL_TOTAL = " TOTAL"; STATE_NAMES_CSV = new String[STATE_COUNT]; - STATE_NAMES_CSV[STATE_PERSISTENT] = "pers"; - STATE_NAMES_CSV[STATE_TOP] = "top"; - STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND] = "impfg"; - STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND] = "impbg"; - STATE_NAMES_CSV[STATE_BACKUP] = "backup"; - STATE_NAMES_CSV[STATE_SERVICE] = "service"; - STATE_NAMES_CSV[STATE_SERVICE_RESTARTING] = "service-rs"; - STATE_NAMES_CSV[STATE_RECEIVER] = "receiver"; - STATE_NAMES_CSV[STATE_HEAVY_WEIGHT] = "heavy"; - STATE_NAMES_CSV[STATE_HOME] = "home"; - STATE_NAMES_CSV[STATE_LAST_ACTIVITY] = "lastact"; - STATE_NAMES_CSV[STATE_CACHED_ACTIVITY] = "cch-activity"; - STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT] = "cch-aclient"; - STATE_NAMES_CSV[STATE_CACHED_EMPTY] = "cch-empty"; + STATE_NAMES_CSV[STATE_PERSISTENT] = "pers"; + STATE_NAMES_CSV[STATE_TOP] = "top"; + STATE_NAMES_CSV[STATE_IMPORTANT_FOREGROUND] = "impfg"; + STATE_NAMES_CSV[STATE_IMPORTANT_BACKGROUND] = "impbg"; + STATE_NAMES_CSV[STATE_BACKUP] = "backup"; + STATE_NAMES_CSV[STATE_SERVICE] = "service"; + STATE_NAMES_CSV[STATE_SERVICE_RESTARTING] = "service-rs"; + STATE_NAMES_CSV[STATE_RECEIVER] = "receiver"; + STATE_NAMES_CSV[STATE_HEAVY_WEIGHT] = "heavy"; + STATE_NAMES_CSV[STATE_HOME] = "home"; + STATE_NAMES_CSV[STATE_LAST_ACTIVITY] = "lastact"; + STATE_NAMES_CSV[STATE_CACHED_ACTIVITY] = "cch-activity"; + STATE_NAMES_CSV[STATE_CACHED_ACTIVITY_CLIENT] = "cch-aclient"; + STATE_NAMES_CSV[STATE_CACHED_EMPTY] = "cch-empty"; STATE_TAGS = new String[STATE_COUNT]; - STATE_TAGS[STATE_PERSISTENT] = "p"; - STATE_TAGS[STATE_TOP] = "t"; - STATE_TAGS[STATE_IMPORTANT_FOREGROUND] = "f"; - STATE_TAGS[STATE_IMPORTANT_BACKGROUND] = "b"; - STATE_TAGS[STATE_BACKUP] = "u"; - STATE_TAGS[STATE_SERVICE] = "s"; - STATE_TAGS[STATE_SERVICE_RESTARTING] = "x"; - STATE_TAGS[STATE_RECEIVER] = "r"; - STATE_TAGS[STATE_HEAVY_WEIGHT] = "w"; - STATE_TAGS[STATE_HOME] = "h"; - STATE_TAGS[STATE_LAST_ACTIVITY] = "l"; - STATE_TAGS[STATE_CACHED_ACTIVITY] = "a"; - STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT] = "c"; - STATE_TAGS[STATE_CACHED_EMPTY] = "e"; + STATE_TAGS[STATE_PERSISTENT] = "p"; + STATE_TAGS[STATE_TOP] = "t"; + STATE_TAGS[STATE_IMPORTANT_FOREGROUND] = "f"; + STATE_TAGS[STATE_IMPORTANT_BACKGROUND] = "b"; + STATE_TAGS[STATE_BACKUP] = "u"; + STATE_TAGS[STATE_SERVICE] = "s"; + STATE_TAGS[STATE_SERVICE_RESTARTING] = "x"; + STATE_TAGS[STATE_RECEIVER] = "r"; + STATE_TAGS[STATE_HEAVY_WEIGHT] = "w"; + STATE_TAGS[STATE_HOME] = "h"; + STATE_TAGS[STATE_LAST_ACTIVITY] = "l"; + STATE_TAGS[STATE_CACHED_ACTIVITY] = "a"; + STATE_TAGS[STATE_CACHED_ACTIVITY_CLIENT] = "c"; + STATE_TAGS[STATE_CACHED_EMPTY] = "e"; STATE_PROTO_ENUMS = new int[STATE_COUNT]; STATE_PROTO_ENUMS[STATE_PERSISTENT] = ProcessStatsProto.State.PERSISTENT; @@ -166,7 +187,7 @@ public final class DumpUtils { pw.print("SOff/"); break; case ADJ_SCREEN_ON: - pw.print("SOn /"); + pw.print(" SOn/"); break; default: pw.print("????/"); @@ -201,11 +222,11 @@ public final class DumpUtils { if (sep != 0) pw.print(sep); break; case ADJ_MEM_FACTOR_MODERATE: - pw.print("Mod "); + pw.print(" Mod"); if (sep != 0) pw.print(sep); break; case ADJ_MEM_FACTOR_LOW: - pw.print("Low "); + pw.print(" Low"); if (sep != 0) pw.print(sep); break; case ADJ_MEM_FACTOR_CRITICAL: diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java index ad42288c25bb..9685f752285d 100644 --- a/core/java/com/android/internal/app/procstats/ProcessState.java +++ b/core/java/com/android/internal/app/procstats/ProcessState.java @@ -787,35 +787,36 @@ public final class ProcessState { pw.print(" / v"); pw.print(mVersion); pw.println(":"); - dumpProcessSummaryDetails(pw, prefix, " TOTAL: ", screenStates, memStates, - procStates, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Persistent: ", screenStates, memStates, - new int[] { STATE_PERSISTENT }, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Top: ", screenStates, memStates, - new int[] {STATE_TOP}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Imp Fg: ", screenStates, memStates, - new int[] { STATE_IMPORTANT_FOREGROUND }, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Imp Bg: ", screenStates, memStates, - new int[] {STATE_IMPORTANT_BACKGROUND}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Backup: ", screenStates, memStates, - new int[] {STATE_BACKUP}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Heavy Wgt: ", screenStates, memStates, - new int[] {STATE_HEAVY_WEIGHT}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Service: ", screenStates, memStates, - new int[] {STATE_SERVICE}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Service Rs: ", screenStates, memStates, - new int[] {STATE_SERVICE_RESTARTING}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Receiver: ", screenStates, memStates, - new int[] {STATE_RECEIVER}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " Heavy: ", screenStates, memStates, - new int[] {STATE_HOME}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " (Home): ", screenStates, memStates, - new int[] {STATE_HOME}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " (Last Act): ", screenStates, memStates, - new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true); - dumpProcessSummaryDetails(pw, prefix, " (Cached): ", screenStates, memStates, - new int[] {STATE_CACHED_ACTIVITY, STATE_CACHED_ACTIVITY_CLIENT, - STATE_CACHED_EMPTY}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_TOTAL, + screenStates, memStates, procStates, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_PERSISTENT], + screenStates, memStates, new int[] { STATE_PERSISTENT }, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_TOP], + screenStates, memStates, new int[] {STATE_TOP}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_IMPORTANT_FOREGROUND], + screenStates, memStates, new int[] { STATE_IMPORTANT_FOREGROUND }, now, totalTime, + true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_IMPORTANT_BACKGROUND], + screenStates, memStates, new int[] {STATE_IMPORTANT_BACKGROUND}, now, totalTime, + true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_BACKUP], + screenStates, memStates, new int[] {STATE_BACKUP}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_SERVICE], + screenStates, memStates, new int[] {STATE_SERVICE}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_SERVICE_RESTARTING], + screenStates, memStates, new int[] {STATE_SERVICE_RESTARTING}, now, totalTime, + true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_RECEIVER], + screenStates, memStates, new int[] {STATE_RECEIVER}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_HEAVY_WEIGHT], + screenStates, memStates, new int[] {STATE_HEAVY_WEIGHT}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_HOME], + screenStates, memStates, new int[] {STATE_HOME}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABELS[STATE_LAST_ACTIVITY], + screenStates, memStates, new int[] {STATE_LAST_ACTIVITY}, now, totalTime, true); + dumpProcessSummaryDetails(pw, prefix, DumpUtils.STATE_LABEL_CACHED, + screenStates, memStates, new int[] {STATE_CACHED_ACTIVITY, + STATE_CACHED_ACTIVITY_CLIENT, STATE_CACHED_EMPTY}, now, totalTime, true); } public void dumpProcessState(PrintWriter pw, String prefix, @@ -846,7 +847,7 @@ public final class ProcessState { printedMem != imem ? imem : STATE_NOTHING, '/'); printedMem = imem; } - pw.print(DumpUtils.STATE_NAMES[procStates[ip]]); pw.print(": "); + pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": "); TimeUtils.formatDuration(time, pw); pw.println(running); totalTime += time; } @@ -861,7 +862,8 @@ public final class ProcessState { if (memStates.length > 1) { DumpUtils.printMemLabel(pw, STATE_NOTHING, '/'); } - pw.print("TOTAL : "); + pw.print(DumpUtils.STATE_LABEL_TOTAL); + pw.print(": "); TimeUtils.formatDuration(totalTime, pw); pw.println(); } @@ -899,7 +901,7 @@ public final class ProcessState { printedMem != imem ? imem : STATE_NOTHING, '/'); printedMem = imem; } - pw.print(DumpUtils.STATE_NAMES[procStates[ip]]); pw.print(": "); + pw.print(DumpUtils.STATE_LABELS[procStates[ip]]); pw.print(": "); pw.print(count); pw.print(" samples "); DebugUtils.printSizeValue(pw, getPssMinimum(bucket) * 1024); @@ -950,7 +952,9 @@ public final class ProcessState { pw.print(prefix); } if (label != null) { + pw.print(" "); pw.print(label); + pw.print(": "); } totals.print(pw, totalTime, full); if (prefix != null) { diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java index 15f140e2ea2d..12b16d040594 100644 --- a/core/java/com/android/internal/app/procstats/ProcessStats.java +++ b/core/java/com/android/internal/app/procstats/ProcessStats.java @@ -158,7 +158,7 @@ public final class ProcessStats implements Parcelable { }; // Current version of the parcel format. - private static final int PARCEL_VERSION = 32; + private static final int PARCEL_VERSION = 33; // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0x50535454; @@ -1380,9 +1380,13 @@ public final class ProcessStats implements Parcelable { final int NUM = mTrackingAssociations.size(); for (int i = NUM - 1; i >= 0; i--) { final AssociationState.SourceState act = mTrackingAssociations.get(i); - if (act.mProcStateSeq != curSeq) { + if (act.mProcStateSeq != curSeq || act.mProcState >= ProcessStats.STATE_HOME) { + // If this association did not get touched the last time we computed + // process states, or its state ended up down in cached, then we no + // longer have a reason to track it at all. + act.stopActive(now); act.mInTrackingList = false; - act.mProcState = STATE_NOTHING; + act.mProcState = ProcessStats.STATE_NOTHING; mTrackingAssociations.remove(i); } else { final ProcessState proc = act.getAssociationState().getProcess(); @@ -1407,7 +1411,7 @@ public final class ProcessStats implements Parcelable { } public void dumpLocked(PrintWriter pw, String reqPackage, long now, boolean dumpSummary, - boolean dumpAll, boolean activeOnly) { + boolean dumpDetails, boolean dumpAll, boolean activeOnly) { long totalTime = DumpUtils.dumpSingleTime(null, null, mMemFactorDurations, mMemFactor, mStartTime, now); boolean sepNeeded = false; @@ -1538,7 +1542,7 @@ public final class ProcessStats implements Parcelable { pw.println(":"); pw.print(" Process: "); pw.println(asc.getProcessName()); asc.dumpStats(pw, " ", " ", " ", - now, totalTime, dumpSummary, dumpAll); + now, totalTime, dumpDetails, dumpAll); } } } @@ -1632,21 +1636,8 @@ public final class ProcessStats implements Parcelable { if (src.mActiveCount > 0) { pw.print(" Active count "); pw.print(src.mActiveCount); - long duration = src.mActiveDuration; - if (src.mActiveStartUptime > 0) { - duration += now - src.mActiveStartUptime; - } - if (dumpAll) { - pw.print(" / Duration "); - TimeUtils.formatDuration(duration, pw); - pw.print(" / "); - } else { - pw.print(" / time "); - } - DumpUtils.printPercent(pw, (double)duration/(double)totalTime); - if (src.mActiveStartUptime > 0) { - pw.print(" (running)"); - } + pw.print(": "); + asc.dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll); pw.println(); } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index af50420208b3..76f9a8d5f8e8 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -13181,7 +13181,7 @@ public class BatteryStatsImpl extends BatteryStats { public static final String KEY_MAX_HISTORY_FILES = "max_history_files"; public static final String KEY_MAX_HISTORY_BUFFER_KB = "max_history_buffer_kb"; - private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = true; + private static final boolean DEFAULT_TRACK_CPU_TIMES_BY_PROC_STATE = false; private static final boolean DEFAULT_TRACK_CPU_ACTIVE_CLUSTER_TIME = true; private static final long DEFAULT_PROC_STATE_CPU_TIMES_READ_DELAY_MS = 5_000; private static final long DEFAULT_KERNEL_UID_READERS_THROTTLE_TIME = 10_000; diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java index dc302058aa80..f88a902b43b6 100644 --- a/core/java/com/android/internal/os/BinderCallsStats.java +++ b/core/java/com/android/internal/os/BinderCallsStats.java @@ -34,6 +34,7 @@ import com.android.internal.util.Preconditions; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -76,16 +77,17 @@ public class BinderCallsStats implements BinderInternal.Observer { @Override public CallSession callStarted(Binder binder, int code) { - return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code)); + return callStarted(binder.getClass(), code, binder.getTransactionName(code)); } - private CallSession callStarted(String className, int code, @Nullable String methodName) { + private CallSession callStarted(Class<? extends Binder> binderClass, int code, + @Nullable String methodName) { CallSession s = mCallSessionsPool.poll(); if (s == null) { s = new CallSession(); } - s.className = className; + s.binderClass = binderClass; s.transactionCode = code; s.methodName = methodName; s.exceptionThrown = false; @@ -117,22 +119,30 @@ public class BinderCallsStats implements BinderInternal.Observer { } private void processCallEnded(CallSession s, int parcelRequestSize, int parcelReplySize) { + // Non-negative time signals we need to record data for this call. + final boolean recordCall = s.cpuTimeStarted >= 0; + final long duration; + final long latencyDuration; + if (recordCall) { + duration = getThreadTimeMicro() - s.cpuTimeStarted; + latencyDuration = getElapsedRealtimeMicro() - s.timeStarted; + } else { + duration = 0; + latencyDuration = 0; + } + final int callingUid = getCallingUid(); + synchronized (mLock) { - final int callingUid = getCallingUid(); UidEntry uidEntry = mUidEntries.get(callingUid); if (uidEntry == null) { uidEntry = new UidEntry(callingUid); mUidEntries.put(callingUid, uidEntry); } uidEntry.callCount++; - CallStat callStat = uidEntry.getOrCreate(s.className, s.transactionCode); + CallStat callStat = uidEntry.getOrCreate(s.binderClass, s.transactionCode); callStat.callCount++; - // Non-negative time signals we need to record data for this call. - final boolean recordCall = s.cpuTimeStarted >= 0; if (recordCall) { - final long duration = getThreadTimeMicro() - s.cpuTimeStarted; - final long latencyDuration = getElapsedRealtimeMicro() - s.timeStarted; uidEntry.cpuTimeMicros += duration; uidEntry.recordedCallCount++; @@ -175,6 +185,9 @@ public class BinderCallsStats implements BinderInternal.Observer { } } + /** + * This method is expensive to call. + */ public ArrayList<ExportedCallStat> getExportedCallStats() { // We do not collect all the data if detailed tracking is off. if (!mDetailedTracking) { @@ -189,7 +202,7 @@ public class BinderCallsStats implements BinderInternal.Observer { for (CallStat stat : entry.getCallStatsList()) { ExportedCallStat exported = new ExportedCallStat(); exported.uid = entry.uid; - exported.className = stat.className; + exported.className = stat.binderClass.getName(); exported.methodName = stat.methodName == null ? String.valueOf(stat.transactionCode) : stat.methodName; exported.cpuTimeMicros = stat.cpuTimeMicros; @@ -250,23 +263,22 @@ public class BinderCallsStats implements BinderInternal.Observer { + "latency_time_micros, max_latency_time_micros, exception_count, " + "max_request_size_bytes, max_reply_size_bytes, recorded_call_count, " + "call_count):"); - for (UidEntry uidEntry : topEntries) { - for (CallStat e : uidEntry.getCallStatsList()) { - sb.setLength(0); - sb.append(" ") - .append(uidToString(uidEntry.uid, appIdToPkgNameMap)) - .append(',').append(e) - .append(',').append(e.cpuTimeMicros) - .append(',').append(e.maxCpuTimeMicros) - .append(',').append(e.latencyMicros) - .append(',').append(e.maxLatencyMicros) - .append(',').append(mDetailedTracking ? e.exceptionCount : '_') - .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_') - .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_') - .append(',').append(e.recordedCallCount) - .append(',').append(e.callCount); - pw.println(sb); - } + for (ExportedCallStat e : sortByCpuDesc(getExportedCallStats())) { + sb.setLength(0); + sb.append(" ") + .append(uidToString(e.uid, appIdToPkgNameMap)) + .append(',').append(e.className) + .append('#').append(e.methodName) + .append(',').append(e.cpuTimeMicros) + .append(',').append(e.maxCpuTimeMicros) + .append(',').append(e.latencyMicros) + .append(',').append(e.maxLatencyMicros) + .append(',').append(mDetailedTracking ? e.exceptionCount : '_') + .append(',').append(mDetailedTracking ? e.maxRequestSizeBytes : '_') + .append(',').append(mDetailedTracking ? e.maxReplySizeBytes : '_') + .append(',').append(e.recordedCallCount) + .append(',').append(e.callCount); + pw.println(sb); } pw.println(); pw.println("Per-UID Summary " + datasetSizeDesc @@ -372,7 +384,7 @@ public class BinderCallsStats implements BinderInternal.Observer { @VisibleForTesting public static class CallStat { - public String className; + public Class<? extends Binder> binderClass; public int transactionCode; // Method name might be null when we cannot resolve the transaction code. For instance, if // the binder was not generated by AIDL. @@ -397,23 +409,15 @@ public class BinderCallsStats implements BinderInternal.Observer { public long maxReplySizeBytes; public long exceptionCount; - CallStat() { - } - - CallStat(String className, int transactionCode) { - this.className = className; + CallStat(Class<? extends Binder> binderClass, int transactionCode) { + this.binderClass = binderClass; this.transactionCode = transactionCode; } - - @Override - public String toString() { - return className + "#" + (methodName == null ? transactionCode : methodName); - } } /** Key used to store CallStat object in a Map. */ public static class CallStatKey { - public String className; + public Class<? extends Binder> binderClass; public int transactionCode; @Override @@ -424,12 +428,12 @@ public class BinderCallsStats implements BinderInternal.Observer { CallStatKey key = (CallStatKey) o; return transactionCode == key.transactionCode - && (className.equals(key.className)); + && (binderClass.equals(key.binderClass)); } @Override public int hashCode() { - int result = className.hashCode(); + int result = binderClass.hashCode(); result = 31 * result + transactionCode; return result; } @@ -457,16 +461,16 @@ public class BinderCallsStats implements BinderInternal.Observer { private Map<CallStatKey, CallStat> mCallStats = new ArrayMap<>(); private CallStatKey mTempKey = new CallStatKey(); - CallStat getOrCreate(String className, int transactionCode) { + CallStat getOrCreate(Class<? extends Binder> binderClass, int transactionCode) { // Use a global temporary key to avoid creating new objects for every lookup. - mTempKey.className = className; + mTempKey.binderClass = binderClass; mTempKey.transactionCode = transactionCode; CallStat mapCallStat = mCallStats.get(mTempKey); // Only create CallStat if it's a new entry, otherwise update existing instance if (mapCallStat == null) { - mapCallStat = new CallStat(className, transactionCode); + mapCallStat = new CallStat(binderClass, transactionCode); CallStatKey key = new CallStatKey(); - key.className = className; + key.binderClass = binderClass; key.transactionCode = transactionCode; mCallStats.put(key, mapCallStat); } @@ -476,17 +480,8 @@ public class BinderCallsStats implements BinderInternal.Observer { /** * Returns list of calls sorted by CPU time */ - public List<CallStat> getCallStatsList() { - List<CallStat> callStats = new ArrayList<>(mCallStats.values()); - callStats.sort((o1, o2) -> { - if (o1.cpuTimeMicros < o2.cpuTimeMicros) { - return 1; - } else if (o1.cpuTimeMicros > o2.cpuTimeMicros) { - return -1; - } - return 0; - }); - return callStats; + public Collection<CallStat> getCallStatsList() { + return mCallStats.values(); } @Override @@ -545,4 +540,15 @@ public class BinderCallsStats implements BinderInternal.Observer { return result; } + private List<ExportedCallStat> sortByCpuDesc(List<ExportedCallStat> callStats) { + callStats.sort((o1, o2) -> { + if (o1.cpuTimeMicros < o2.cpuTimeMicros) { + return 1; + } else if (o1.cpuTimeMicros > o2.cpuTimeMicros) { + return -1; + } + return 0; + }); + return callStats; + } } diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index 4b93c8644f64..af12521f0079 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -75,7 +75,7 @@ public class BinderInternal { */ public static class CallSession { // Binder interface descriptor. - public String className; + public Class<? extends Binder> binderClass; // Binder transaction code. public int transactionCode; // Binder transaction method name. diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 4ee950aa2beb..413f89da571e 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -132,13 +132,14 @@ public final class Zygote { */ public static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir) { + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + String packageName) { VM_HOOKS.preFork(); // Resets nice priority for zygote process. resetNicePriority(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir); + fdsToIgnore, startChildZygote, instructionSet, appDataDir, packageName); // Enable tracing as soon as possible for the child process. if (pid == 0) { Trace.setTracingEnabled(true, runtimeFlags); @@ -152,7 +153,8 @@ public final class Zygote { native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, - int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir); + int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, + String packageName); /** * Called to do any initialization before starting an application. diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 12761b9274e3..b9c717f03749 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -239,7 +239,7 @@ class ZygoteConnection { pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids, parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo, parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote, - parsedArgs.instructionSet, parsedArgs.appDataDir); + parsedArgs.instructionSet, parsedArgs.appDataDir, parsedArgs.packageName); try { if (pid == 0) { @@ -426,6 +426,9 @@ class ZygoteConnection { /** from --invoke-with */ String invokeWith; + /** from --package-name */ + String packageName; + /** * Any args after and including the first non-option arg * (or after a '--') @@ -674,6 +677,8 @@ class ZygoteConnection { "Invalid log sampling rate: " + rateStr, nfe); } expectRuntimeArgs = false; + } else if (arg.startsWith("--package-name=")) { + packageName = arg.substring(arg.indexOf('=') + 1); } else { break; } diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java index c3d33ca84ee1..4b662670f5e7 100644 --- a/core/java/com/android/internal/util/ArrayUtils.java +++ b/core/java/com/android/internal/util/ArrayUtils.java @@ -309,7 +309,7 @@ public class ArrayUtils { } @SuppressWarnings("unchecked") - public static @NonNull <T> T[] concat(Class<T> kind, @Nullable T[] a, @Nullable T[] b) { + public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[] a, @Nullable T[] b) { final int an = (a != null) ? a.length : 0; final int bn = (b != null) ? b.length : 0; if (an == 0 && bn == 0) { diff --git a/core/jni/Android.bp b/core/jni/Android.bp index b675698a461b..6856e2942f21 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -84,8 +84,8 @@ cc_library_shared { "android_view_VelocityTracker.cpp", "android_text_AndroidCharacter.cpp", "android_text_Hyphenator.cpp", + "android_text_LineBreaker.cpp", "android_text_MeasuredParagraph.cpp", - "android_text_StaticLayout.cpp", "android_os_Debug.cpp", "android_os_GraphicsEnvironment.cpp", "android_os_HidlSupport.cpp", @@ -154,6 +154,7 @@ cc_library_shared { "android/graphics/Typeface.cpp", "android/graphics/Utils.cpp", "android/graphics/YuvToJpegEncoder.cpp", + "android/graphics/fonts/Font.cpp", "android/graphics/pdf/PdfDocument.cpp", "android/graphics/pdf/PdfEditor.cpp", "android/graphics/pdf/PdfRenderer.cpp", diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index bd2a8def2618..1820888f9c0a 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -140,6 +140,7 @@ extern int register_android_graphics_Region(JNIEnv* env); extern int register_android_graphics_SurfaceTexture(JNIEnv* env); extern int register_android_graphics_drawable_AnimatedVectorDrawable(JNIEnv* env); extern int register_android_graphics_drawable_VectorDrawable(JNIEnv* env); +extern int register_android_graphics_fonts_Font(JNIEnv* env); extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env); extern int register_android_graphics_pdf_PdfEditor(JNIEnv* env); extern int register_android_graphics_pdf_PdfRenderer(JNIEnv* env); @@ -183,7 +184,7 @@ extern int register_android_net_NetworkUtils(JNIEnv* env); extern int register_android_text_AndroidCharacter(JNIEnv *env); extern int register_android_text_Hyphenator(JNIEnv *env); extern int register_android_text_MeasuredParagraph(JNIEnv* env); -extern int register_android_text_StaticLayout(JNIEnv *env); +extern int register_android_text_LineBreaker(JNIEnv *env); extern int register_android_opengl_classes(JNIEnv *env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); extern int register_android_server_NetworkManagementSocketTagger(JNIEnv* env); @@ -1334,7 +1335,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_text_AndroidCharacter), REG_JNI(register_android_text_Hyphenator), REG_JNI(register_android_text_MeasuredParagraph), - REG_JNI(register_android_text_StaticLayout), + REG_JNI(register_android_text_LineBreaker), REG_JNI(register_android_view_InputDevice), REG_JNI(register_android_view_KeyCharacterMap), REG_JNI(register_android_os_Process), @@ -1406,6 +1407,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_YuvImage), REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable), REG_JNI(register_android_graphics_drawable_VectorDrawable), + REG_JNI(register_android_graphics_fonts_Font), REG_JNI(register_android_graphics_pdf_PdfDocument), REG_JNI(register_android_graphics_pdf_PdfEditor), REG_JNI(register_android_graphics_pdf_PdfRenderer), diff --git a/core/jni/android/graphics/FontUtils.h b/core/jni/android/graphics/FontUtils.h index 9eaaa4964b66..9f6462e67050 100644 --- a/core/jni/android/graphics/FontUtils.h +++ b/core/jni/android/graphics/FontUtils.h @@ -20,6 +20,8 @@ #include <jni.h> #include <memory> +#include <minikin/Font.h> + namespace minikin { class FontFamily; } // namespace minikin @@ -31,6 +33,11 @@ struct FontFamilyWrapper { std::shared_ptr<minikin::FontFamily> family; }; +struct FontWrapper { + FontWrapper(minikin::Font&& font) : font(std::move(font)) {} + minikin::Font font; +}; + // Utility wrapper for java.util.List class ListHelper { public: diff --git a/core/jni/android/graphics/fonts/Font.cpp b/core/jni/android/graphics/fonts/Font.cpp new file mode 100644 index 000000000000..2d1d7a0c4aea --- /dev/null +++ b/core/jni/android/graphics/fonts/Font.cpp @@ -0,0 +1,205 @@ +/* + * 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. + */ + +#define LOG_TAG "Minikin" + +#include <nativehelper/JNIHelp.h> +#include <core_jni_helpers.h> + +#include "SkData.h" +#include "SkFontMgr.h" +#include "SkRefCnt.h" +#include "SkTypeface.h" +#include "GraphicsJNI.h" +#include <nativehelper/ScopedUtfChars.h> +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_util_AssetManager.h> +#include <androidfw/AssetManager2.h> +#include "Utils.h" +#include "FontUtils.h" + +#include <hwui/MinikinSkia.h> +#include <hwui/Typeface.h> +#include <utils/FatVector.h> +#include <minikin/FontFamily.h> + +#include <memory> + +namespace android { + +struct NativeFontBuilder { + std::vector<minikin::FontVariation> axes; +}; + +static inline NativeFontBuilder* toBuilder(jlong ptr) { + return reinterpret_cast<NativeFontBuilder*>(ptr); +} + +static inline Asset* toAsset(jlong ptr) { + return reinterpret_cast<Asset*>(ptr); +} + +static void releaseAsset(jlong asset) { + delete toAsset(asset); +} + +static void releaseFont(jlong font) { + delete reinterpret_cast<FontWrapper*>(font); +} + +static void release_global_ref(const void* /*data*/, void* context) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + if (env == nullptr) { + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_4; + args.name = "release_font_data"; + args.group = nullptr; + jint result = AndroidRuntime::getJavaVM()->AttachCurrentThread(&env, &args); + if (result != JNI_OK) { + ALOGE("failed to attach to thread to release global ref."); + return; + } + } + + jobject obj = reinterpret_cast<jobject>(context); + env->DeleteGlobalRef(obj); +} + +// Regular JNI +static jlong Font_Builder_getNativeAsset( + JNIEnv* env, jobject clazz, jobject assetMgr, jstring path, jboolean isAsset, jint cookie) { + NPE_CHECK_RETURN_ZERO(env, assetMgr); + NPE_CHECK_RETURN_ZERO(env, path); + + Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, assetMgr); + if (mgr == nullptr) { + return 0; + } + + ScopedUtfChars str(env, path); + if (str.c_str() == nullptr) { + return 0; + } + + std::unique_ptr<Asset> asset; + { + ScopedLock<AssetManager2> locked_mgr(*mgr); + if (isAsset) { + asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER); + } else if (cookie > 0) { + // Valid java cookies are 1-based, but AssetManager cookies are 0-based. + asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1), + Asset::ACCESS_BUFFER); + } else { + asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER); + } + } + + return reinterpret_cast<jlong>(asset.release()); +} + +// Regular JNI +static jobject Font_Builder_getAssetBuffer(JNIEnv* env, jobject clazz, jlong nativeAsset) { + Asset* asset = toAsset(nativeAsset); + return env->NewDirectByteBuffer(const_cast<void*>(asset->getBuffer(false)), asset->getLength()); +} + +// CriticalNative +static jlong Font_Builder_getReleaseNativeAssetFunc() { + return reinterpret_cast<jlong>(&releaseAsset); +} + +// Regular JNI +static jlong Font_Builder_initBuilder(JNIEnv*, jobject) { + return reinterpret_cast<jlong>(new NativeFontBuilder()); +} + +// Critical Native +static void Font_Builder_addAxis(jlong builderPtr, jint tag, jfloat value) { + toBuilder(builderPtr)->axes.emplace_back(static_cast<minikin::AxisTag>(tag), value); +} + +// Regular JNI +static jlong Font_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jobject buffer, + jint weight, jboolean italic, jint ttcIndex) { + NPE_CHECK_RETURN_ZERO(env, buffer); + std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr)); + const void* fontPtr = env->GetDirectBufferAddress(buffer); + if (fontPtr == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", "Not a direct buffer"); + return 0; + } + jlong fontSize = env->GetDirectBufferCapacity(buffer); + if (fontSize <= 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "buffer size must not be zero or negative"); + return 0; + } + jobject fontRef = MakeGlobalRefOrDie(env, buffer); + sk_sp<SkData> data(SkData::MakeWithProc(fontPtr, fontSize, + release_global_ref, reinterpret_cast<void*>(fontRef))); + + uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes; + for (const auto& axis : builder->axes) { + skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value}); + } + + std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(std::move(data))); + + SkFontArguments params; + params.setCollectionIndex(ttcIndex); + params.setAxes(skiaAxes.data(), skiaAxes.size()); + + sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault()); + sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), params)); + if (face == nullptr) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "Failed to create internal object. maybe invalid font data."); + return 0; + } + std::shared_ptr<minikin::MinikinFont> minikinFont = + std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex, + builder->axes); + minikin::Font font = minikin::Font::Builder(minikinFont).setWeight(weight) + .setSlant(static_cast<minikin::FontStyle::Slant>(italic)).build(); + return reinterpret_cast<jlong>(new FontWrapper(std::move(font))); +} + +// Critical Native +static jlong Font_Builder_getReleaseNativeFont() { + return reinterpret_cast<jlong>(releaseFont); +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gFontBuilderMethods[] = { + { "nInitBuilder", "()J", (void*) Font_Builder_initBuilder }, + { "nAddAxis", "(JIF)V", (void*) Font_Builder_addAxis }, + { "nBuild", "(JLjava/nio/ByteBuffer;IZI)J", (void*) Font_Builder_build }, + { "nGetReleaseNativeFont", "()J", (void*) Font_Builder_getReleaseNativeFont }, + + { "nGetNativeAsset", "(Landroid/content/res/AssetManager;Ljava/lang/String;ZI)J", + (void*) Font_Builder_getNativeAsset }, + { "nGetAssetBuffer", "(J)Ljava/nio/ByteBuffer;", (void*) Font_Builder_getAssetBuffer }, + { "nGetReleaseNativeAssetFunc", "()J", (void*) Font_Builder_getReleaseNativeAssetFunc }, +}; + +int register_android_graphics_fonts_Font(JNIEnv* env) { + return RegisterMethodsOrDie(env, "android/graphics/fonts/Font$Builder", gFontBuilderMethods, + NELEM(gFontBuilderMethods)); +} + +} diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_LineBreaker.cpp index fec5b6995646..dac108ef5497 100644 --- a/core/jni/android_text_StaticLayout.cpp +++ b/core/jni/android_text_LineBreaker.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#define LOG_TAG "StaticLayout" +#define LOG_TAG "LineBreaker" #include "unicode/locid.h" #include "unicode/brkiter.h" @@ -76,11 +76,15 @@ static jlong nInit(JNIEnv* env, jclass /* unused */, jintArrayToFloatVector(env, indents))); } -// CriticalNative static void nFinish(jlong nativePtr) { delete toNative(nativePtr); } +// CriticalNative +static jlong nGetReleaseFunc() { + return reinterpret_cast<jlong>(nFinish); +} + static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks, jfloatArray recycleWidths, jfloatArray recycleAscents, jfloatArray recycleDescents, jintArray recycleFlags, @@ -144,9 +148,6 @@ static jint nComputeLineBreaks(JNIEnv* env, jclass, jlong nativePtr, recycleCopy(env, recycle, recycleBreaks, recycleWidths, recycleAscents, recycleDescents, recycleFlags, recycleLength, result); - env->SetFloatArrayRegion(charWidths, 0, measuredText->widths.size(), - measuredText->widths.data()); - return static_cast<jint>(result.breakPoints.size()); } @@ -160,7 +161,7 @@ static const JNINativeMethod gMethods[] = { ")J", (void*) nInit}, // Critical Natives - {"nFinish", "(J)V", (void*) nFinish}, + {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Regular JNI {"nComputeLineBreaks", "(" @@ -178,21 +179,20 @@ static const JNINativeMethod gMethods[] = { "I" // indentsOffset // Outputs - "Landroid/text/StaticLayout$LineBreaks;" // recycle + "Landroid/text/NativeLineBreaker$LineBreaks;" // recycle "I" // recycleLength "[I" // recycleBreaks "[F" // recycleWidths "[F" // recycleAscents "[F" // recycleDescents "[I" // recycleFlags - "[F" // charWidths ")I", (void*) nComputeLineBreaks} }; -int register_android_text_StaticLayout(JNIEnv* env) +int register_android_text_LineBreaker(JNIEnv* env) { gLineBreaks_class = MakeGlobalRefOrDie(env, - FindClassOrDie(env, "android/text/StaticLayout$LineBreaks")); + FindClassOrDie(env, "android/text/NativeLineBreaker$LineBreaks")); gLineBreaks_fieldID.breaks = GetFieldIDOrDie(env, gLineBreaks_class, "breaks", "[I"); gLineBreaks_fieldID.widths = GetFieldIDOrDie(env, gLineBreaks_class, "widths", "[F"); @@ -200,7 +200,8 @@ int register_android_text_StaticLayout(JNIEnv* env) gLineBreaks_fieldID.descents = GetFieldIDOrDie(env, gLineBreaks_class, "descents", "[F"); gLineBreaks_fieldID.flags = GetFieldIDOrDie(env, gLineBreaks_class, "flags", "[I"); - return RegisterMethodsOrDie(env, "android/text/StaticLayout", gMethods, NELEM(gMethods)); + return RegisterMethodsOrDie(env, "android/text/NativeLineBreaker", + gMethods, NELEM(gMethods)); } } diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp index 9eb6f8d4189a..18f509c7ec60 100644 --- a/core/jni/android_text_MeasuredParagraph.cpp +++ b/core/jni/android_text_MeasuredParagraph.cpp @@ -109,6 +109,10 @@ static jfloat nGetWidth(jlong ptr, jint start, jint end) { return r; } +static jfloat nGetCharWidthAt(jlong ptr, jint offset) { + return toMeasuredParagraph(ptr)->widths[offset]; +} + // Regular JNI static void nGetBounds(JNIEnv* env, jobject, jlong ptr, jcharArray javaText, jint start, jint end, jobject bounds) { @@ -138,23 +142,29 @@ static jint nGetMemoryUsage(jlong ptr) { return static_cast<jint>(toMeasuredParagraph(ptr)->getMemoryUsage()); } -static const JNINativeMethod gMethods[] = { +static const JNINativeMethod gMTBuilderMethods[] = { // MeasuredParagraphBuilder native functions. {"nInitBuilder", "()J", (void*) nInitBuilder}, {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun}, {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun}, {"nBuildNativeMeasuredParagraph", "(J[CZZ)J", (void*) nBuildNativeMeasuredParagraph}, {"nFreeBuilder", "(J)V", (void*) nFreeBuilder}, +}; +static const JNINativeMethod gMTMethods[] = { // MeasuredParagraph native functions. {"nGetWidth", "(JII)F", (void*) nGetWidth}, // Critical Natives {"nGetBounds", "(J[CIILandroid/graphics/Rect;)V", (void*) nGetBounds}, // Regular JNI {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc}, // Critical Natives {"nGetMemoryUsage", "(J)I", (void*) nGetMemoryUsage}, // Critical Native + {"nGetCharWidthAt", "(JI)F", (void*) nGetCharWidthAt}, // Critical Native }; int register_android_text_MeasuredParagraph(JNIEnv* env) { - return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods)); + return RegisterMethodsOrDie(env, "android/text/NativeMeasuredParagraph", + gMTMethods, NELEM(gMTMethods)) + + RegisterMethodsOrDie(env, "android/text/NativeMeasuredParagraph$Builder", + gMTBuilderMethods, NELEM(gMTBuilderMethods)); } } diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 0022cf889098..738069216b2c 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -70,6 +70,7 @@ namespace { using android::String8; +using android::base::StringAppendF; using android::base::StringPrintf; using android::base::WriteStringToFile; using android::base::GetBoolProperty; @@ -79,6 +80,7 @@ using android::base::GetBoolProperty; static pid_t gSystemServerPid = 0; +static const char kIsolatedStorage[] = "persist.sys.isolated_storage"; static const char kZygoteClassName[] = "com/android/internal/os/Zygote"; static jclass gZygoteClass; static jmethodID gCallPostForkChildHooks; @@ -379,10 +381,32 @@ static int UnmountTree(const char* path) { return 0; } +static bool createPkgSandbox(uid_t uid, const char* package_name, std::string& pkg_sandbox_dir, + std::string* error_msg) { + // Create /mnt/user/0/package/<package-name> + userid_t user_id = multiuser_get_user_id(uid); + StringAppendF(&pkg_sandbox_dir, "/%d", user_id); + if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); + return false; + } + StringAppendF(&pkg_sandbox_dir, "/package"); + if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0700, AID_ROOT, AID_ROOT) != 0) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); + return false; + } + StringAppendF(&pkg_sandbox_dir, "/%s", package_name); + if (fs_prepare_dir(pkg_sandbox_dir.c_str(), 0755, uid, uid) != 0) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", pkg_sandbox_dir.c_str()); + return false; + } + return true; +} + // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. static bool MountEmulatedStorage(uid_t uid, jint mount_mode, - bool force_mount_namespace, std::string* error_msg) { + bool force_mount_namespace, std::string* error_msg, const char* package_name) { // See storage config details at http://source.android.com/tech/storage/ String8 storageSource; @@ -408,27 +432,44 @@ static bool MountEmulatedStorage(uid_t uid, jint mount_mode, return true; } - if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", - NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", - storageSource.string(), - strerror(errno)); - return false; - } + if (GetBoolProperty(kIsolatedStorage, false)) { + if (package_name == nullptr) { + return true; + } - // Mount user-specific symlink helper into place - userid_t user_id = multiuser_get_user_id(uid); - const String8 userSource(String8::format("/mnt/user/%d", user_id)); - if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { - *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); - return false; - } - if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", - NULL, MS_BIND, NULL)) == -1) { - *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", - userSource.string(), - strerror(errno)); - return false; + std::string pkgSandboxDir("/mnt/user"); + if (!createPkgSandbox(uid, package_name, pkgSandboxDir, error_msg)) { + return false; + } + if (TEMP_FAILURE_RETRY(mount(pkgSandboxDir.c_str(), "/storage", + nullptr, MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + pkgSandboxDir.c_str(), strerror(errno)); + return false; + } + } else { + if (TEMP_FAILURE_RETRY(mount(storageSource.string(), "/storage", + NULL, MS_BIND | MS_REC | MS_SLAVE, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage: %s", + storageSource.string(), + strerror(errno)); + return false; + } + + // Mount user-specific symlink helper into place + userid_t user_id = multiuser_get_user_id(uid); + const String8 userSource(String8::format("/mnt/user/%d", user_id)); + if (fs_prepare_dir(userSource.string(), 0751, 0, 0) == -1) { + *error_msg = CREATE_ERROR("fs_prepare_dir failed on %s", userSource.string()); + return false; + } + if (TEMP_FAILURE_RETRY(mount(userSource.string(), "/storage/self", + NULL, MS_BIND, NULL)) == -1) { + *error_msg = CREATE_ERROR("Failed to mount %s to /storage/self: %s", + userSource.string(), + strerror(errno)); + return false; + } } return true; @@ -544,7 +585,7 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGi jlong permittedCapabilities, jlong effectiveCapabilities, jint mount_external, jstring java_se_info, jstring java_se_name, bool is_system_server, bool is_child_zygote, jstring instructionSet, - jstring dataDir) { + jstring dataDir, jstring packageName) { std::string error_msg; auto fail_fn = [env, java_se_name, is_system_server](const std::string& msg) @@ -594,7 +635,18 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGi ALOGW("Native bridge will not be used because dataDir == NULL."); } - if (!MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg)) { + ScopedUtfChars* package_name = nullptr; + const char* package_name_c_str = nullptr; + if (packageName != nullptr) { + package_name = new ScopedUtfChars(env, packageName); + package_name_c_str = package_name->c_str(); + } else if (is_system_server) { + package_name_c_str = "android"; + } + bool success = MountEmulatedStorage(uid, mount_external, use_native_bridge, &error_msg, + package_name_c_str); + delete package_name; + if (!success) { ALOGW("Failed to mount emulated storage: %s (%s)", error_msg.c_str(), strerror(errno)); if (errno == ENOTCONN || errno == EROFS) { // When device is actively encrypting, we get ENOTCONN here @@ -858,7 +910,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, - jstring instructionSet, jstring appDataDir) { + jstring instructionSet, jstring appDataDir, jstring packageName) { jlong capabilities = 0; // Grant CAP_WAKE_ALARM to the Bluetooth process. @@ -911,7 +963,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, se_name, false, - is_child_zygote == JNI_TRUE, instructionSet, appDataDir); + is_child_zygote == JNI_TRUE, instructionSet, appDataDir, packageName); } return pid; } @@ -925,7 +977,7 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permittedCapabilities, effectiveCapabilities, MOUNT_EXTERNAL_DEFAULT, NULL, NULL, true, - false, NULL, NULL); + false, NULL, NULL, nullptr); } else if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -1006,7 +1058,7 @@ static const JNINativeMethod gMethods[] = { { "nativeSecurityInit", "()V", (void *) com_android_internal_os_Zygote_nativeSecurityInit }, { "nativeForkAndSpecialize", - "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto index 04e5658be5a0..ed7316a43156 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -97,7 +97,18 @@ message GlobalSettingsProto { } optional Auto auto = 16; - optional SettingProto autofill_compat_mode_allowed_packages = 17 [ (android.privacy).dest = DEST_AUTOMATIC ]; + reserved 17; // Used to be autofill_compat_mode_allowed_packages + + message Autofill { + option (android.msg_privacy).dest = DEST_EXPLICIT; + + optional SettingProto compat_mode_allowed_packages = 1 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto logging_level = 2 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto max_partitions_size = 3 [ (android.privacy).dest = DEST_AUTOMATIC ]; + optional SettingProto max_visible_datasets = 4 [ (android.privacy).dest = DEST_AUTOMATIC ]; + } + optional Autofill autofill = 140; + optional SettingProto backup_agent_timeout_parameters = 18; message Battery { @@ -943,5 +954,5 @@ message GlobalSettingsProto { // Please insert fields in alphabetical order and group them into messages // if possible (to avoid reaching the method limit). - // Next tag = 140; + // Next tag = 141; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ebf751c861bf..52c76ccaa639 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -62,6 +62,8 @@ message ActivityStackSupervisorProto { optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1; repeated ActivityDisplayProto displays = 2; optional KeyguardControllerProto keyguard_controller = 3; + // TODO(b/111541062): Focused stack and resumed activity are now per-display. Topmost instances + // can be obtained from top display and these fields can be removed. optional int32 focused_stack_id = 4; optional .com.android.server.wm.IdentifierProto resumed_activity = 5; // Whether or not the home activity is the recents activity. This is needed for the CTS tests to @@ -77,6 +79,8 @@ message ActivityDisplayProto { optional .com.android.server.wm.ConfigurationContainerProto configuration_container = 1; optional int32 id = 2; repeated ActivityStackProto stacks = 3; + optional int32 focused_stack_id = 4; + optional .com.android.server.wm.IdentifierProto resumed_activity = 5; } message ActivityStackProto { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 44eea30cf09f..2f710bf4dd29 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3113,6 +3113,9 @@ the alpha channel of the outlineAmbientShadowColor (typically opaque), and the {@link android.R.attr#ambientShadowAlpha} theme attribute. --> <attr name="outlineAmbientShadowColor" format="color" /> + + <!-- Whether to allow the rendering system to force this View to render as light-on-dark. --> + <attr name="allowForceDark" format="boolean" /> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> @@ -7863,8 +7866,7 @@ wallpaper. --> <attr name="showMetadataInPreview" format="boolean" /> - <!-- Wallpapers optimized and capable of drawing in ambient mode will return true. - @hide --> + <!-- Wallpapers optimized and capable of drawing in ambient mode will return true. --> <attr name="supportsAmbientMode" format="boolean" /> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3053fa11f80a..5944108e8c10 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -593,10 +593,6 @@ <integer translatable="false" name="config_wifi_framework_wifi_score_entry_rssi_threshold_24GHz">-80</integer> <integer translatable="false" name="config_wifi_framework_wifi_score_low_rssi_threshold_24GHz">-73</integer> <integer translatable="false" name="config_wifi_framework_wifi_score_good_rssi_threshold_24GHz">-60</integer> - <integer translatable="false" name="config_wifi_framework_wifi_score_bad_link_speed_24">6</integer> - <integer translatable="false" name="config_wifi_framework_wifi_score_bad_link_speed_5">12</integer> - <integer translatable="false" name="config_wifi_framework_wifi_score_good_link_speed_24">24</integer> - <integer translatable="false" name="config_wifi_framework_wifi_score_good_link_speed_5">36</integer> <!-- Integer delay in milliseconds before shutting down soft AP when there are no connected devices. Framework will enforce a minimum limit on diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 618997159c80..e25bb79cee7b 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2907,6 +2907,8 @@ <public name="opticalInsetTop" /> <public name="opticalInsetRight" /> <public name="opticalInsetBottom" /> + <public name="allowForceDark" /> + <public name="supportsAmbientMode" /> </public-group> <public-group type="style" first-id="0x010302e2"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 1c66b2b8ca41..86952d638fee 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -394,10 +394,6 @@ <java-symbol type="integer" name="config_wifi_framework_wifi_score_entry_rssi_threshold_5GHz" /> <java-symbol type="integer" name="config_wifi_framework_wifi_score_low_rssi_threshold_5GHz" /> <java-symbol type="integer" name="config_wifi_framework_wifi_score_good_rssi_threshold_5GHz" /> - <java-symbol type="integer" name="config_wifi_framework_wifi_score_bad_link_speed_24" /> - <java-symbol type="integer" name="config_wifi_framework_wifi_score_bad_link_speed_5" /> - <java-symbol type="integer" name="config_wifi_framework_wifi_score_good_link_speed_24" /> - <java-symbol type="integer" name="config_wifi_framework_wifi_score_good_link_speed_5" /> <java-symbol type="integer" name="config_wifi_framework_scan_result_rssi_level_patchup_value" /> <java-symbol type="integer" name="config_wifi_framework_current_network_boost" /> <java-symbol type="string" name="config_wifi_random_mac_oui" /> diff --git a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java new file mode 100644 index 000000000000..7c206d7eecf7 --- /dev/null +++ b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java @@ -0,0 +1,66 @@ +/* + * 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.database; + +import static android.database.DatabaseUtils.bindSelection; + +import static org.junit.Assert.assertEquals; + +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class DatabaseUtilsTest { + private static final Object[] ARGS = { "baz", 4, null }; + + @Test + public void testBindSelection_none() throws Exception { + assertEquals(null, + bindSelection(null, ARGS)); + assertEquals("", + bindSelection("", ARGS)); + assertEquals("foo=bar", + bindSelection("foo=bar", ARGS)); + } + + @Test + public void testBindSelection_normal() throws Exception { + assertEquals("foo='baz'", + bindSelection("foo=?", ARGS)); + assertEquals("foo='baz' AND bar=4", + bindSelection("foo=? AND bar=?", ARGS)); + assertEquals("foo='baz' AND bar=4 AND meow=NULL", + bindSelection("foo=? AND bar=? AND meow=?", ARGS)); + } + + @Test + public void testBindSelection_whitespace() throws Exception { + assertEquals("BETWEEN 5 AND 10", + bindSelection("BETWEEN? AND ?", 5, 10)); + assertEquals("IN 'foo'", + bindSelection("IN?", "foo")); + } + + @Test + public void testBindSelection_indexed() throws Exception { + assertEquals("foo=10 AND bar=11 AND meow=1", + bindSelection("foo=?10 AND bar=? AND meow=?1", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); + } +} diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java index 952a64dbeaaa..425ab8962307 100644 --- a/core/tests/coretests/src/android/os/WorkSourceTest.java +++ b/core/tests/coretests/src/android/os/WorkSourceTest.java @@ -378,6 +378,17 @@ public class WorkSourceTest extends TestCase { assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid()); } + public void testRemove_fromSameWorkSource() { + WorkSource ws1 = new WorkSource(50, "foo"); + WorkSource ws2 = ws1; + ws2.add(ws1); + assertTrue(ws2.remove(ws1)); + + assertEquals(0, ws1.size()); + assertEquals(50, ws1.get(0)); + assertEquals("foo", ws1.getName(0)); + } + public void testTransferWorkChains() { WorkSource ws1 = new WorkSource(); WorkChain wc1 = ws1.createWorkChain().addNode(100, "tag"); diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 60e512cb2c1c..37dec134b86e 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -119,6 +119,9 @@ public class SettingsBackupTest { Settings.Global.ASSISTED_GPS_ENABLED, Settings.Global.AUDIO_SAFE_VOLUME_STATE, Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, + Settings.Global.AUTOFILL_LOGGING_LEVEL, + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, diff --git a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java index 6f1d47d2dac2..f3d6013b8ee3 100644 --- a/core/tests/coretests/src/android/text/MeasuredParagraphTest.java +++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java @@ -19,6 +19,7 @@ package android.text; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import android.content.Context; import android.graphics.Typeface; @@ -72,7 +73,7 @@ public class MeasuredParagraphTest { assertEquals(0, mt.getWidths().size()); assertEquals(0, mt.getSpanEndCache().size()); assertEquals(0, mt.getFontMetrics().size()); - assertEquals(0, mt.getNativePtr()); + assertNull(mt.getNativeMeasuredParagraph()); // Recycle it MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt); @@ -84,7 +85,7 @@ public class MeasuredParagraphTest { assertEquals(0, mt2.getWidths().size()); assertEquals(0, mt2.getSpanEndCache().size()); assertEquals(0, mt2.getFontMetrics().size()); - assertEquals(0, mt2.getNativePtr()); + assertNull(mt.getNativeMeasuredParagraph()); mt2.recycle(); } @@ -106,7 +107,7 @@ public class MeasuredParagraphTest { assertEquals(10, mt.getWidths().get(2), 0); assertEquals(0, mt.getSpanEndCache().size()); assertEquals(0, mt.getFontMetrics().size()); - assertEquals(0, mt.getNativePtr()); + assertNull(mt.getNativeMeasuredParagraph()); // Recycle it MeasuredParagraph mt2 = @@ -123,7 +124,7 @@ public class MeasuredParagraphTest { assertEquals(5, mt2.getWidths().get(2), 0); assertEquals(0, mt2.getSpanEndCache().size()); assertEquals(0, mt2.getFontMetrics().size()); - assertEquals(0, mt2.getNativePtr()); + assertNull(mt.getNativeMeasuredParagraph()); mt2.recycle(); } @@ -143,7 +144,7 @@ public class MeasuredParagraphTest { assertEquals(1, mt.getSpanEndCache().size()); assertEquals(3, mt.getSpanEndCache().get(0)); assertNotEquals(0, mt.getFontMetrics().size()); - assertNotEquals(0, mt.getNativePtr()); + assertNotNull(mt.getNativeMeasuredParagraph()); // Recycle it MeasuredParagraph mt2 = @@ -158,7 +159,7 @@ public class MeasuredParagraphTest { assertEquals(1, mt2.getSpanEndCache().size()); assertEquals(4, mt2.getSpanEndCache().get(0)); assertNotEquals(0, mt2.getFontMetrics().size()); - assertNotEquals(0, mt2.getNativePtr()); + assertNotNull(mt.getNativeMeasuredParagraph()); mt2.recycle(); } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index 993378d8a4d0..ec80d20e3179 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -543,6 +543,27 @@ public class AccessibilityCacheTest { } } + @Test + public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRefreshed() { + AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); + nodeInfo1.setAccessibilityFocused(true); + mAccessibilityCache.add(nodeInfo1); + AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1); + nodeInfo2.setAccessibilityFocused(true); + mAccessibilityCache.add(nodeInfo2); + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); + event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); + mAccessibilityCache.onAccessibilityEvent(event); + event.recycle(); + try { + verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo1, true); + } finally { + nodeInfo1.recycle(); + nodeInfo2.recycle(); + } + } + private void assertNodeIsRefreshedWithEventType(int eventType, int contentChangeTypes) { AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); mAccessibilityCache.add(nodeInfo); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 44b1f088111a..0dd768530603 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -119,6 +119,10 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon return false; } + public int getSoftKeyboardShowMode() { + return 0; + } + public void setSoftKeyboardCallbackEnabled(boolean enabled) {} public boolean isAccessibilityButtonAvailable() { diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java index 20dc87205b2d..c0b0a3800e11 100644 --- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java @@ -31,6 +31,7 @@ import org.junit.runner.RunWith; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -62,11 +63,11 @@ public class BinderCallsStatsTest { assertEquals(1, uidEntries.size()); BinderCallsStats.UidEntry uidEntry = uidEntries.get(TEST_UID); Assert.assertNotNull(uidEntry); - List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList(); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(1, uidEntry.callCount); assertEquals(1, uidEntry.recordedCallCount); assertEquals(10, uidEntry.cpuTimeMicros); - assertEquals(binder.getClass().getName(), callStatsList.get(0).className); + assertEquals(binder.getClass(), callStatsList.get(0).binderClass); assertEquals(1, callStatsList.get(0).transactionCode); // CPU usage is sampled, should not be tracked here. @@ -85,7 +86,7 @@ public class BinderCallsStatsTest { assertEquals(3, uidEntry.callCount); assertEquals(1, uidEntry.recordedCallCount); // Still sampled even for another API. - callStatsList = uidEntry.getCallStatsList(); + callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(2, callStatsList.size()); } @@ -105,12 +106,12 @@ public class BinderCallsStatsTest { Assert.assertNotNull(uidEntry); assertEquals(1, uidEntry.callCount); assertEquals(10, uidEntry.cpuTimeMicros); - assertEquals(1, uidEntry.getCallStatsList().size()); + assertEquals(1, new ArrayList(uidEntry.getCallStatsList()).size()); - List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList(); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(1, callStatsList.get(0).callCount); assertEquals(10, callStatsList.get(0).cpuTimeMicros); - assertEquals(binder.getClass().getName(), callStatsList.get(0).className); + assertEquals(binder.getClass(), callStatsList.get(0).binderClass); assertEquals(1, callStatsList.get(0).transactionCode); callSession = bcs.callStarted(binder, 1); @@ -120,7 +121,7 @@ public class BinderCallsStatsTest { uidEntry = bcs.getUidEntries().get(TEST_UID); assertEquals(2, uidEntry.callCount); assertEquals(30, uidEntry.cpuTimeMicros); - callStatsList = uidEntry.getCallStatsList(); + callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(1, callStatsList.size()); callSession = bcs.callStarted(binder, 2); @@ -131,7 +132,7 @@ public class BinderCallsStatsTest { // This is the first transaction of a new type, so the real CPU time will be measured assertEquals(80, uidEntry.cpuTimeMicros); - callStatsList = uidEntry.getCallStatsList(); + callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(2, callStatsList.size()); } @@ -182,7 +183,7 @@ public class BinderCallsStatsTest { assertEquals(3, uidEntry.callCount); assertEquals(60 /* 10 + 50 */, uidEntry.cpuTimeMicros); - List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList(); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(1, callStatsList.size()); BinderCallsStats.CallStat callStats = callStatsList.get(0); assertEquals(3, callStats.callCount); @@ -216,7 +217,7 @@ public class BinderCallsStatsTest { assertEquals(1, uidEntry.recordedCallCount); assertEquals(10, uidEntry.cpuTimeMicros); - List<BinderCallsStats.CallStat> callStatsList = uidEntry.getCallStatsList(); + List<BinderCallsStats.CallStat> callStatsList = new ArrayList(uidEntry.getCallStatsList()); assertEquals(2, callStatsList.size()); BinderCallsStats.CallStat callStats = callStatsList.get(0); @@ -248,7 +249,7 @@ public class BinderCallsStatsTest { bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); List<BinderCallsStats.CallStat> callStatsList = - bcs.getUidEntries().get(TEST_UID).getCallStatsList(); + new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList()); assertEquals(1, callStatsList.get(0).transactionCode); assertEquals("resolved", callStatsList.get(0).methodName); } @@ -263,7 +264,7 @@ public class BinderCallsStatsTest { bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); List<BinderCallsStats.CallStat> callStatsList = - bcs.getUidEntries().get(TEST_UID).getCallStatsList(); + new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList()); assertEquals(REQUEST_SIZE, callStatsList.get(0).maxRequestSizeBytes); assertEquals(REPLY_SIZE, callStatsList.get(0).maxReplySizeBytes); @@ -283,7 +284,7 @@ public class BinderCallsStatsTest { bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); List<BinderCallsStats.CallStat> callStatsList = - bcs.getUidEntries().get(TEST_UID).getCallStatsList(); + new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList()); assertEquals(50, callStatsList.get(0).maxCpuTimeMicros); } @@ -302,7 +303,7 @@ public class BinderCallsStatsTest { bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE); List<BinderCallsStats.CallStat> callStatsList = - bcs.getUidEntries().get(TEST_UID).getCallStatsList(); + new ArrayList(bcs.getUidEntries().get(TEST_UID).getCallStatsList()); assertEquals(5, callStatsList.get(0).maxLatencyMicros); } @@ -388,6 +389,20 @@ public class BinderCallsStatsTest { assertEquals(0, stat.exceptionCount); } + @Test + public void testGetExportedStatsWithoutCalls() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + Binder binder = new Binder(); + assertEquals(0, bcs.getExportedCallStats().size()); + } + + @Test + public void testGetExportedExceptionsWithoutCalls() { + TestBinderCallsStats bcs = new TestBinderCallsStats(); + Binder binder = new Binder(); + assertEquals(0, bcs.getExceptionCounts().size()); + } + static class TestBinderCallsStats extends BinderCallsStats { int callingUid = TEST_UID; long time = 1234; diff --git a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java index 6464ad3e9709..39bb84a20d7a 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java @@ -16,6 +16,8 @@ package com.android.internal.util; +import static com.android.internal.util.ArrayUtils.concatElements; + import static org.junit.Assert.assertArrayEquals; import junit.framework.TestCase; @@ -156,23 +158,23 @@ public class ArrayUtilsTest extends TestCase { public void testConcatEmpty() throws Exception { assertArrayEquals(new Long[] {}, - ArrayUtils.concat(Long.class, null, null)); + concatElements(Long.class, null, null)); assertArrayEquals(new Long[] {}, - ArrayUtils.concat(Long.class, new Long[] {}, null)); + concatElements(Long.class, new Long[] {}, null)); assertArrayEquals(new Long[] {}, - ArrayUtils.concat(Long.class, null, new Long[] {})); + concatElements(Long.class, null, new Long[] {})); assertArrayEquals(new Long[] {}, - ArrayUtils.concat(Long.class, new Long[] {}, new Long[] {})); + concatElements(Long.class, new Long[] {}, new Long[] {})); } - public void testConcat() throws Exception { + public void testconcatElements() throws Exception { assertArrayEquals(new Long[] { 1L }, - ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] {})); + concatElements(Long.class, new Long[] { 1L }, new Long[] {})); assertArrayEquals(new Long[] { 1L }, - ArrayUtils.concat(Long.class, new Long[] {}, new Long[] { 1L })); + concatElements(Long.class, new Long[] {}, new Long[] { 1L })); assertArrayEquals(new Long[] { 1L, 2L }, - ArrayUtils.concat(Long.class, new Long[] { 1L }, new Long[] { 2L })); + concatElements(Long.class, new Long[] { 1L }, new Long[] { 2L })); assertArrayEquals(new Long[] { 1L, 2L, 3L, 4L }, - ArrayUtils.concat(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L })); + concatElements(Long.class, new Long[] { 1L, 2L }, new Long[] { 3L, 4L })); } } diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java index 97130f166eb2..ffe6abc13aae 100644 --- a/graphics/java/android/graphics/BaseCanvas.java +++ b/graphics/java/android/graphics/BaseCanvas.java @@ -501,7 +501,7 @@ public abstract class BaseCanvas { contextStart - paraStart, contextEnd - contextStart, x, y, isRtl, paint.getNativeInstance(), - mp.getNativePtr()); + mp.getNativeMeasuredParagraph().getNativePtr()); return; } } diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java new file mode 100644 index 000000000000..9da61db94780 --- /dev/null +++ b/graphics/java/android/graphics/fonts/Font.java @@ -0,0 +1,472 @@ +/* + * 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.graphics.fonts; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.util.TypedValue; + +import com.android.internal.util.Preconditions; + +import dalvik.annotation.optimization.CriticalNative; + +import libcore.util.NativeAllocationRegistry; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * A font class can be used for creating FontFamily. + */ +public class Font { + private static final String TAG = "Font"; + + private static final int NOT_SPECIFIED = -1; + private static final int STYLE_ITALIC = 1; + private static final int STYLE_NORMAL = 0; + + /** + * A font weight value for the thin weight + */ + public static final int FONT_WEIGHT_THIN = 100; + + /** + * A font weight value for the extra-light weight + */ + public static final int FONT_WEIGHT_EXTRA_LIGHT = 200; + + /** + * A font weight value for the light weight + */ + public static final int FONT_WEIGHT_LIGHT = 300; + + /** + * A font weight value for the normal weight + */ + public static final int FONT_WEIGHT_NORMAL = 400; + + /** + * A font weight value for the medium weight + */ + public static final int FONT_WEIGHT_MEDIUM = 500; + + /** + * A font weight value for the semi-bold weight + */ + public static final int FONT_WEIGHT_SEMI_BOLD = 600; + + /** + * A font weight value for the bold weight. + */ + public static final int FONT_WEIGHT_BOLD = 700; + + /** + * A font weight value for the extra-bold weight + */ + public static final int FONT_WEIGHT_EXTRA_BOLD = 800; + + /** + * A font weight value for the black weight + */ + public static final int FONT_WEIGHT_BLACK = 900; + + /** + * A builder class for creating new Font. + */ + public static class Builder { + private static final NativeAllocationRegistry sAssetByteBufferRegistroy = + new NativeAllocationRegistry(ByteBuffer.class.getClassLoader(), + nGetReleaseNativeAssetFunc(), 64); + + private static final NativeAllocationRegistry sFontRegistory = + new NativeAllocationRegistry(Font.class.getClassLoader(), + nGetReleaseNativeFont(), 64); + + private @Nullable ByteBuffer mBuffer; + private @IntRange(from = -1, to = 1000) int mWeight = NOT_SPECIFIED; + private @IntRange(from = -1, to = 1) int mItalic = NOT_SPECIFIED; + private @IntRange(from = 0) int mTtcIndex = 0; + private @Nullable FontVariationAxis[] mAxes = null; + + /** + * Constructs a builder with a byte buffer. + * + * Note that only direct buffer can be used as the source of font data. + * + * @see ByteBuffer#allocateDirect(int) + * @param buffer a byte buffer of a font data + */ + public Builder(@NonNull ByteBuffer buffer) { + Preconditions.checkNotNull(buffer, "buffer can not be null"); + if (!buffer.isDirect()) { + throw new IllegalArgumentException( + "Only direct buffer can be used as the source of font data."); + } + mBuffer = buffer; + } + + /** + * Constructs a builder with a file path. + * + * @param path a file path to the font file + */ + public Builder(@NonNull File path) throws IOException { + Preconditions.checkNotNull(path, "path can not be null"); + try (FileInputStream fis = new FileInputStream(path)) { + final FileChannel fc = fis.getChannel(); + mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + } + } + + /** + * Constructs a builder with a file descriptor. + * + * @param fd a file descriptor + */ + public Builder(@NonNull FileDescriptor fd) throws IOException { + this(fd, 0, -1); + } + + /** + * Constructs a builder with a file descriptor. + * + * @param fd a file descriptor + * @param offset an offset to of the font data in the file + * @param size a size of the font data. If -1 is passed, use until end of the file. + */ + public Builder(@NonNull FileDescriptor fd, @IntRange(from = 0) long offset, + @IntRange(from = -1) long size) throws IOException { + try (FileInputStream fis = new FileInputStream(fd)) { + final FileChannel fc = fis.getChannel(); + size = (size == -1) ? fc.size() - offset : size; + mBuffer = fc.map(FileChannel.MapMode.READ_ONLY, offset, size); + } + } + + /** + * Constructs a builder from an asset manager and a file path in an asset directory. + * + * @param am the application's asset manager + * @param path the file name of the font data in the asset directory + */ + public Builder(@NonNull AssetManager am, @NonNull String path) throws IOException { + final long nativeAsset = nGetNativeAsset(am, path, true /* is asset */, 0 /* cookie */); + if (nativeAsset == 0) { + throw new FileNotFoundException("Unable to open " + path); + } + final ByteBuffer b = nGetAssetBuffer(nativeAsset); + sAssetByteBufferRegistroy.registerNativeAllocation(b, nativeAsset); + if (b == null) { + throw new FileNotFoundException(path + " not found"); + } + mBuffer = b; + } + + /** + * Constructs a builder from resources. + * + * Resource ID must points the font file. XML font can not be used here. + * + * @param res the resource of this application. + * @param resId the resource ID of font file. + */ + public Builder(@NonNull Resources res, int resId) throws IOException { + final TypedValue value = new TypedValue(); + res.getValue(resId, value, true); + if (value.string == null) { + throw new FileNotFoundException(resId + " not found"); + } + final String str = value.string.toString(); + if (str.toLowerCase().endsWith(".xml")) { + throw new FileNotFoundException(resId + " must be font file."); + } + final long nativeAsset = nGetNativeAsset(res.getAssets(), str, false /* is asset */, + value.assetCookie); + if (nativeAsset == 0) { + throw new FileNotFoundException("Unable to open " + str); + } + final ByteBuffer b = nGetAssetBuffer(nativeAsset); + sAssetByteBufferRegistroy.registerNativeAllocation(b, nativeAsset); + if (b == null) { + throw new FileNotFoundException(str + " not found"); + } + mBuffer = b; + } + + /** + * Sets weight of the font. + * + * Tells the system the weight of the given font. If this function is not called, the system + * will resolve the weight value by reading font tables. + * + * Here are pairs of the common names and their values. + * <p> + * <table> + * <thead> + * <tr> + * <th align="center">Value</th> + * <th align="center">Name</th> + * <th align="center">Android Definition</th> + * </tr> + * </thead> + * <tbody> + * <tr> + * <td align="center">100</td> + * <td align="center">Thin</td> + * <td align="center">{@link Font#FONT_WEIGHT_THIN}</td> + * </tr> + * <tr> + * <td align="center">200</td> + * <td align="center">Extra Light (Ultra Light)</td> + * <td align="center">{@link Font#FONT_WEIGHT_EXTRA_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">300</td> + * <td align="center">Light</td> + * <td align="center">{@link Font#FONT_WEIGHT_LIGHT}</td> + * </tr> + * <tr> + * <td align="center">400</td> + * <td align="center">Normal (Regular)</td> + * <td align="center">{@link Font#FONT_WEIGHT_NORMAL}</td> + * </tr> + * <tr> + * <td align="center">500</td> + * <td align="center">Medium</td> + * <td align="center">{@link Font#FONT_WEIGHT_MEDIUM}</td> + * </tr> + * <tr> + * <td align="center">600</td> + * <td align="center">Semi Bold (Demi Bold)</td> + * <td align="center">{@link Font#FONT_WEIGHT_SEMI_BOLD}</td> + * </tr> + * <tr> + * <td align="center">700</td> + * <td align="center">Bold</td> + * <td align="center">{@link Font#FONT_WEIGHT_BOLD}</td> + * </tr> + * <tr> + * <td align="center">800</td> + * <td align="center">Extra Bold (Ultra Bold)</td> + * <td align="center">{@link Font#FONT_WEIGHT_EXTRA_BOLD}</td> + * </tr> + * <tr> + * <td align="center">900</td> + * <td align="center">Black (Heavy)</td> + * <td align="center">{@link Font#FONT_WEIGHT_BLACK}</td> + * </tr> + * </tbody> + * </p> + * + * @see Font#FONT_WEIGHT_THIN + * @see Font#FONT_WEIGHT_EXTRA_LIGHT + * @see Font#FONT_WEIGHT_LIGHT + * @see Font#FONT_WEIGHT_NORMAL + * @see Font#FONT_WEIGHT_MEDIUM + * @see Font#FONT_WEIGHT_SEMI_BOLD + * @see Font#FONT_WEIGHT_BOLD + * @see Font#FONT_WEIGHT_EXTRA_BOLD + * @see Font#FONT_WEIGHT_BLACK + * @param weight a weight value + * @return this builder + */ + public @NonNull Builder setWeight(@IntRange(from = 1, to = 1000) int weight) { + Preconditions.checkArgument(1 <= weight && weight <= 1000); + mWeight = weight; + return this; + } + + /** + * Sets italic information of the font. + * + * Tells the system the style of the given font. If this function is not called, the system + * will resolve the style by reading font tables. + * + * For example, if you want to use italic font as upright font, call {@code + * setItalic(false)} explicitly. + * + * @param italic {@code true} if the font is italic. Otherwise {@code false}. + * @return this builder + */ + public @NonNull Builder setItalic(boolean italic) { + mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL; + return this; + } + + /** + * Sets an index of the font collection. See {@link android.R.attr#ttcIndex}. + * + * @param ttcIndex An index of the font collection. If the font source is not font + * collection, do not call this method or specify 0. + * @return this builder + */ + public @NonNull Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) { + mTtcIndex = ttcIndex; + return this; + } + + /** + * Sets the font variation settings. + * + * @param variationSettings see {@link FontVariationAxis#fromFontVariationSettings(String)} + * @return this builder + * @throws IllegalArgumentException If given string is not a valid font variation settings + * format. + */ + public @NonNull Builder setFontVariationSettings(@Nullable String variationSettings) { + mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings); + return this; + } + + /** + * Sets the font variation settings. + * + * @param axes an array of font variation axis tag-value pairs + * @return this builder + */ + public @NonNull Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) { + mAxes = axes; + return this; + } + + /** + * Creates the font based on the configured values. + * @return the Font object + */ + public @Nullable Font build() { + if (mWeight == NOT_SPECIFIED || mItalic == NOT_SPECIFIED) { + final int packed = FontFileUtil.analyzeStyle(mBuffer, mTtcIndex, mAxes); + if (FontFileUtil.isSuccess(packed)) { + if (mWeight == NOT_SPECIFIED) { + mWeight = FontFileUtil.unpackWeight(packed); + } + if (mItalic == NOT_SPECIFIED) { + mItalic = FontFileUtil.unpackItalic(packed) ? STYLE_ITALIC : STYLE_NORMAL; + } + } else { + mWeight = 400; + mItalic = STYLE_NORMAL; + } + } + final boolean italic = (mItalic == STYLE_ITALIC); + final long builderPtr = nInitBuilder(); + if (mAxes != null) { + for (FontVariationAxis axis : mAxes) { + nAddAxis(builderPtr, axis.getOpenTypeTagValue(), axis.getStyleValue()); + } + } + final long ptr = nBuild(builderPtr, mBuffer, mWeight, italic, mTtcIndex); + final Font font = new Font(ptr, mWeight, italic, mTtcIndex, mAxes); + sFontRegistory.registerNativeAllocation(font, ptr); + return font; + } + + /** + * Native methods for accessing underlying buffer in Asset + */ + private static native long nGetNativeAsset( + @NonNull AssetManager am, @NonNull String path, boolean isAsset, int cookie); + private static native ByteBuffer nGetAssetBuffer(long nativeAsset); + @CriticalNative + private static native long nGetReleaseNativeAssetFunc(); + + /** + * Native methods for creating Font + */ + private static native long nInitBuilder(); + @CriticalNative + private static native void nAddAxis(long builderPtr, int tag, float value); + private static native long nBuild( + long builderPtr, ByteBuffer buffer, int weight, boolean italic, int ttcIndex); + @CriticalNative + private static native long nGetReleaseNativeFont(); + } + + private final long mNativePtr; // address of the shared ptr of minikin::Font + private final @IntRange(from = 0, to = 1000) int mWeight; + private final boolean mItalic; + private final @IntRange(from = 0) int mTtcIndex; + private final @Nullable FontVariationAxis[] mAxes; + + /** + * Use Builder instead + */ + private Font(long nativePtr, @IntRange(from = 0, to = 1000) int weight, boolean italic, + @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] axes) { + mWeight = weight; + mItalic = italic; + mNativePtr = nativePtr; + mTtcIndex = ttcIndex; + mAxes = axes; + } + + /** + * Get a weight value associated with this font. + * + * @see Builder#setWeight(int) + * @return a weight value + */ + public @IntRange(from = 0, to = 1000)int getWeight() { + return mWeight; + } + + /** + * Returns true if this font is marked as italic, otherwise returns false. + * + * @see Builder#setItalic(boolean) + * @return true if italic, otherwise false + */ + public boolean isItalic() { + return mItalic; + } + + /** + * Get a TTC index value associated with this font. + * + * If TTF/OTF file is provided, this value is always 0. + * + * @see Builder#setTtcIndex(int) + * @return a TTC index value + */ + public @IntRange(from = 0) int getTtcIndex() { + return mTtcIndex; + } + + /** + * Get a font variation settings associated with this font + * + * @see Builder#setFontVariationSettings(String) + * @see Builder#setFontVariationSettings(FontVariationAxis[]) + * @return font variation settings + */ + public @Nullable FontVariationAxis[] getAxes() { + return mAxes; + } + + /** @hide */ + public long getNativePtr() { + return mNativePtr; + } +} diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index d15f581f918c..f8b456b982c5 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -20,7 +20,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -46,6 +45,13 @@ public class FontFileUtil { return (packed & 0x10000) != 0; } + /** + * Returns true if the analyzeStyle succeeded + */ + public static boolean isSuccess(int packed) { + return packed != ANALYZE_ERROR; + } + private static int pack(@IntRange(from = 0, to = 1000) int weight, boolean italic) { return weight | (italic ? 0x10000 : 0); } @@ -55,12 +61,13 @@ public class FontFileUtil { private static final int TTC_TAG = 0x74746366; private static final int OS2_TABLE_TAG = 0x4F532F32; + private static final int ANALYZE_ERROR = 0xFFFFFFFF; + /** * Analyze the font file returns packed style info */ public static final int analyzeStyle(@NonNull ByteBuffer buffer, - @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) - throws IOException { + @IntRange(from = 0) int ttcIndex, @Nullable FontVariationAxis[] varSettings) { int weight = -1; int italic = -1; if (varSettings != null) { @@ -88,7 +95,7 @@ public class FontFileUtil { if (magicNumber == TTC_TAG) { // TTC file. if (ttcIndex >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { - throw new IOException("Font index out of bounds"); + return ANALYZE_ERROR; } fontFileOffset = buffer.getInt( 12 /* offset to array of offsets of font files */ + 4 * ttcIndex); @@ -96,7 +103,7 @@ public class FontFileUtil { int sfntVersion = buffer.getInt(fontFileOffset); if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { - throw new IOException("Unknown font file format"); + return ANALYZE_ERROR; } int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 59760ab0a20f..8d0b615c7ba0 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -230,7 +230,6 @@ cc_defaults { "RenderProperties.cpp", "ResourceCache.cpp", "SkiaCanvas.cpp", - "SkiaCanvasProxy.cpp", "Snapshot.cpp", "Texture.cpp", "VectorDrawable.cpp", diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp deleted file mode 100644 index fc009d871620..000000000000 --- a/libs/hwui/SkiaCanvasProxy.cpp +++ /dev/null @@ -1,486 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "SkiaCanvasProxy.h" - -#include <memory> - -#include <log/log.h> - -#include <SkLatticeIter.h> -#include <SkPaint.h> -#include <SkPatchUtils.h> -#include <SkPath.h> -#include <SkPixelRef.h> -#include <SkRRect.h> -#include <SkRSXform.h> -#include <SkRect.h> -#include <SkSurface.h> -#include <SkTextBlobRunIterator.h> -#include <SkVertices.h> -#include "hwui/Bitmap.h" - -namespace android { -namespace uirenderer { - -SkiaCanvasProxy::SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls) - : INHERITED(canvas->width(), canvas->height()) - , mCanvas(canvas) - , mFilterHwuiCalls(filterHwuiCalls) {} - -void SkiaCanvasProxy::onDrawPaint(const SkPaint& paint) { - mCanvas->drawPaint(paint); -} - -void SkiaCanvasProxy::onDrawPoints(PointMode pointMode, size_t count, const SkPoint pts[], - const SkPaint& paint) { - if (!pts || count == 0) { - return; - } - - // convert the SkPoints into floats - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats"); - const size_t floatCount = count << 1; - const float* floatArray = &pts[0].fX; - - switch (pointMode) { - case kPoints_PointMode: { - mCanvas->drawPoints(floatArray, floatCount, paint); - break; - } - case kLines_PointMode: { - mCanvas->drawLines(floatArray, floatCount, paint); - break; - } - case kPolygon_PointMode: { - SkPaint strokedPaint(paint); - strokedPaint.setStyle(SkPaint::kStroke_Style); - - SkPath path; - for (size_t i = 0; i < count - 1; i++) { - path.moveTo(pts[i]); - path.lineTo(pts[i + 1]); - this->drawPath(path, strokedPaint); - path.rewind(); - } - break; - } - default: - LOG_ALWAYS_FATAL("Unknown point type"); - } -} - -void SkiaCanvasProxy::onDrawOval(const SkRect& rect, const SkPaint& paint) { - mCanvas->drawOval(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawRect(const SkRect& rect, const SkPaint& paint) { - mCanvas->drawRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawRRect(const SkRRect& roundRect, const SkPaint& paint) { - if (!roundRect.isComplex()) { - const SkRect& rect = roundRect.rect(); - SkVector radii = roundRect.getSimpleRadii(); - mCanvas->drawRoundRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, radii.fX, radii.fY, - paint); - } else { - SkPath path; - path.addRRect(roundRect); - mCanvas->drawPath(path, paint); - } -} - -void SkiaCanvasProxy::onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, - bool useCenter, const SkPaint& paint) { - mCanvas->drawArc(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, startAngle, sweepAngle, - useCenter, paint); -} - -void SkiaCanvasProxy::onDrawPath(const SkPath& path, const SkPaint& paint) { - mCanvas->drawPath(path, paint); -} - -void SkiaCanvasProxy::onDrawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, - const SkPaint* paint) { - sk_sp<Bitmap> hwuiBitmap = Bitmap::createFrom(bitmap.info(), *bitmap.pixelRef()); - // HWUI doesn't support extractSubset(), so convert any subsetted bitmap into - // a drawBitmapRect(); pass through an un-subsetted bitmap. - if (hwuiBitmap && bitmap.dimensions() != hwuiBitmap->info().dimensions()) { - SkIPoint origin = bitmap.pixelRefOrigin(); - mCanvas->drawBitmap( - *hwuiBitmap, origin.fX, origin.fY, origin.fX + bitmap.dimensions().width(), - origin.fY + bitmap.dimensions().height(), left, top, - left + bitmap.dimensions().width(), top + bitmap.dimensions().height(), paint); - } else { - mCanvas->drawBitmap(*hwuiBitmap, left, top, paint); - } -} - -void SkiaCanvasProxy::onDrawBitmapRect(const SkBitmap& skBitmap, const SkRect* srcPtr, - const SkRect& dst, const SkPaint* paint, SrcRectConstraint) { - SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(skBitmap.width(), skBitmap.height()); - // TODO: if bitmap is a subset, do we need to add pixelRefOrigin to src? - Bitmap* bitmap = reinterpret_cast<Bitmap*>(skBitmap.pixelRef()); - mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, dst.fTop, - dst.fRight, dst.fBottom, paint); -} - -void SkiaCanvasProxy::onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, - const SkRect& dst, const SkPaint*) { - // TODO make nine-patch drawing a method on Canvas.h - SkDEBUGFAIL("SkiaCanvasProxy::onDrawBitmapNine is not yet supported"); -} - -void SkiaCanvasProxy::onDrawImage(const SkImage* image, SkScalar left, SkScalar top, - const SkPaint* paint) { - SkBitmap skiaBitmap; - SkPixmap pixmap; - if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { - onDrawBitmap(skiaBitmap, left, top, paint); - } -} - -void SkiaCanvasProxy::onDrawImageRect(const SkImage* image, const SkRect* srcPtr, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint constraint) { - SkBitmap skiaBitmap; - SkPixmap pixmap; - if (image->peekPixels(&pixmap) && skiaBitmap.installPixels(pixmap)) { - sk_sp<Bitmap> bitmap = Bitmap::createFrom(skiaBitmap.info(), *skiaBitmap.pixelRef()); - SkRect src = (srcPtr) ? *srcPtr : SkRect::MakeWH(image->width(), image->height()); - mCanvas->drawBitmap(*bitmap, src.fLeft, src.fTop, src.fRight, src.fBottom, dst.fLeft, - dst.fTop, dst.fRight, dst.fBottom, paint); - } -} - -void SkiaCanvasProxy::onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, - const SkPaint*) { - SkDEBUGFAIL("SkiaCanvasProxy::onDrawImageNine is not yet supported"); -} - -void SkiaCanvasProxy::onDrawImageLattice(const SkImage* image, const Lattice& lattice, - const SkRect& dst, const SkPaint* paint) { - SkLatticeIter iter(lattice, dst); - SkRect srcR, dstR; - while (iter.next(&srcR, &dstR)) { - onDrawImageRect(image, &srcR, dstR, paint, SkCanvas::kFast_SrcRectConstraint); - } -} - -void SkiaCanvasProxy::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode bmode, - const SkPaint& paint) { - if (mFilterHwuiCalls) { - return; - } - mCanvas->drawVertices(vertices, bmode, paint); -} - -sk_sp<SkSurface> SkiaCanvasProxy::onNewSurface(const SkImageInfo&, const SkSurfaceProps&) { - SkDEBUGFAIL("SkiaCanvasProxy::onNewSurface is not supported"); - return NULL; -} - -void SkiaCanvasProxy::willSave() { - mCanvas->save(android::SaveFlags::MatrixClip); -} - -static inline SaveFlags::Flags saveFlags(SkCanvas::SaveLayerFlags layerFlags) { - SaveFlags::Flags saveFlags = 0; - - if (!(layerFlags & SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag)) { - saveFlags |= SaveFlags::ClipToLayer; - } - - return saveFlags; -} - -SkCanvas::SaveLayerStrategy SkiaCanvasProxy::getSaveLayerStrategy( - const SaveLayerRec& saveLayerRec) { - SkRect rect; - if (saveLayerRec.fBounds) { - rect = *saveLayerRec.fBounds; - } else if (!mCanvas->getClipBounds(&rect)) { - rect = SkRect::MakeEmpty(); - } - mCanvas->saveLayer(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, saveLayerRec.fPaint, - saveFlags(saveLayerRec.fSaveLayerFlags)); - return SkCanvas::kNoLayer_SaveLayerStrategy; -} - -void SkiaCanvasProxy::willRestore() { - mCanvas->restore(); -} - -void SkiaCanvasProxy::didConcat(const SkMatrix& matrix) { - mCanvas->concat(matrix); -} - -void SkiaCanvasProxy::didSetMatrix(const SkMatrix& matrix) { - mCanvas->setMatrix(matrix); -} - -void SkiaCanvasProxy::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, - const SkPaint& paint) { - SkPath path; - path.addRRect(outer); - path.addRRect(inner); - path.setFillType(SkPath::kEvenOdd_FillType); - this->drawPath(path, paint); -} - -/** - * Utility class that converts the incoming text & paint from the given encoding - * into glyphIDs. - */ -class GlyphIDConverter { -public: - GlyphIDConverter(const void* text, size_t byteLength, const SkPaint& origPaint) { - paint = origPaint; - if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) { - glyphIDs = (uint16_t*)text; - count = byteLength >> 1; - } else { - // ensure space for one glyph per ID given UTF8 encoding. - storage.reset(new uint16_t[byteLength]); - glyphIDs = storage.get(); - count = paint.textToGlyphs(text, byteLength, storage.get()); - paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); - } - } - - SkPaint paint; - uint16_t* glyphIDs; - int count; - -private: - std::unique_ptr<uint16_t[]> storage; -}; - -void SkiaCanvasProxy::onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint& origPaint) { - // convert to glyphIDs if necessary - GlyphIDConverter glyphs(text, byteLength, origPaint); - - // compute the glyph positions - std::unique_ptr<SkScalar[]> glyphWidths(new SkScalar[glyphs.count]); - glyphs.paint.getTextWidths(glyphs.glyphIDs, glyphs.count << 1, glyphWidths.get()); - - // compute conservative bounds - // NOTE: We could call the faster paint.getFontBounds for a less accurate, - // but even more conservative bounds if this is too slow. - SkRect bounds; - glyphs.paint.measureText(glyphs.glyphIDs, glyphs.count << 1, &bounds); - - // adjust for non-left alignment - if (glyphs.paint.getTextAlign() != SkPaint::kLeft_Align) { - SkScalar stop = 0; - for (int i = 0; i < glyphs.count; i++) { - stop += glyphWidths[i]; - } - if (glyphs.paint.getTextAlign() == SkPaint::kCenter_Align) { - stop = SkScalarHalf(stop); - } - if (glyphs.paint.isVerticalText()) { - y -= stop; - } else { - x -= stop; - } - } - - // setup the first glyph position and adjust bounds if needed - int xBaseline = 0; - int yBaseline = 0; - if (mCanvas->drawTextAbsolutePos()) { - bounds.offset(x, y); - xBaseline = x; - yBaseline = y; - } - - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats"); - auto glyphFunc = [&](uint16_t* text, float* positions) { - memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t)); - size_t posIndex = 0; - // setup the first glyph position - positions[posIndex++] = xBaseline; - positions[posIndex++] = yBaseline; - // setup the remaining glyph positions - if (glyphs.paint.isVerticalText()) { - float yPosition = yBaseline; - for (int i = 1; i < glyphs.count; i++) { - positions[posIndex++] = xBaseline; - yPosition += glyphWidths[i - 1]; - positions[posIndex++] = yPosition; - } - } else { - float xPosition = xBaseline; - for (int i = 1; i < glyphs.count; i++) { - xPosition += glyphWidths[i - 1]; - positions[posIndex++] = xPosition; - positions[posIndex++] = yBaseline; - } - } - }; - mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, - bounds.fRight, bounds.fBottom, 0); -} - -void SkiaCanvasProxy::onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint& origPaint) { - // convert to glyphIDs if necessary - GlyphIDConverter glyphs(text, byteLength, origPaint); - - // convert to relative positions if necessary - int x, y; - if (mCanvas->drawTextAbsolutePos()) { - x = 0; - y = 0; - } else { - x = pos[0].fX; - y = pos[0].fY; - } - - // Compute conservative bounds. If the content has already been processed - // by Minikin then it had already computed these bounds. Unfortunately, - // there is no way to capture those bounds as part of the Skia drawPosText - // API so we need to do that computation again here. - SkRect bounds = SkRect::MakeEmpty(); - for (int i = 0; i < glyphs.count; i++) { - SkRect glyphBounds = SkRect::MakeEmpty(); - glyphs.paint.measureText(&glyphs.glyphIDs[i], sizeof(uint16_t), &glyphBounds); - glyphBounds.offset(pos[i].fX, pos[i].fY); - bounds.join(glyphBounds); - } - - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint is no longer two floats"); - auto glyphFunc = [&](uint16_t* text, float* positions) { - memcpy(text, glyphs.glyphIDs, glyphs.count * sizeof(uint16_t)); - if (mCanvas->drawTextAbsolutePos()) { - memcpy(positions, pos, 2 * glyphs.count * sizeof(float)); - } else { - for (int i = 0, posIndex = 0; i < glyphs.count; i++) { - positions[posIndex++] = pos[i].fX - x; - positions[posIndex++] = pos[i].fY - y; - } - } - }; - mCanvas->drawGlyphs(glyphFunc, glyphs.count, glyphs.paint, x, y, bounds.fLeft, bounds.fTop, - bounds.fRight, bounds.fBottom, 0); -} - -void SkiaCanvasProxy::onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], - SkScalar constY, const SkPaint& paint) { - const size_t pointCount = byteLength >> 1; - std::unique_ptr<SkPoint[]> pts(new SkPoint[pointCount]); - for (size_t i = 0; i < pointCount; i++) { - pts[i].set(xpos[i], constY); - } - this->onDrawPosText(text, byteLength, pts.get(), paint); -} - -void SkiaCanvasProxy::onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, - const SkMatrix* matrix, const SkPaint& origPaint) { - SkDEBUGFAIL("SkiaCanvasProxy::onDrawTextOnPath is not supported"); -} - -void SkiaCanvasProxy::onDrawTextRSXform(const void* text, size_t byteLength, - const SkRSXform xform[], const SkRect* cullRect, - const SkPaint& paint) { - GlyphIDConverter glyphs(text, byteLength, paint); // Just get count - SkMatrix localM, currM, origM; - mCanvas->getMatrix(&currM); - origM = currM; - for (int i = 0; i < glyphs.count; i++) { - localM.setRSXform(*xform++); - currM.setConcat(origM, localM); - mCanvas->setMatrix(currM); - this->onDrawText((char*)text + (byteLength / glyphs.count * i), byteLength / glyphs.count, - 0, 0, paint); - } - mCanvas->setMatrix(origM); -} - -void SkiaCanvasProxy::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, - const SkPaint& paint) { - SkPaint runPaint = paint; - - SkTextBlobRunIterator it(blob); - for (; !it.done(); it.next()) { - size_t textLen = it.glyphCount() * sizeof(uint16_t); - const SkPoint& offset = it.offset(); - // applyFontToPaint() always overwrites the exact same attributes, - // so it is safe to not re-seed the paint for this reason. - it.applyFontToPaint(&runPaint); - - switch (it.positioning()) { - case SkTextBlob::kDefault_Positioning: - this->drawText(it.glyphs(), textLen, x + offset.x(), y + offset.y(), runPaint); - break; - case SkTextBlob::kHorizontal_Positioning: { - std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); - for (size_t i = 0; i < it.glyphCount(); i++) { - pts[i].set(x + offset.x() + it.pos()[i], y + offset.y()); - } - this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); - break; - } - case SkTextBlob::kFull_Positioning: { - std::unique_ptr<SkPoint[]> pts(new SkPoint[it.glyphCount()]); - for (size_t i = 0; i < it.glyphCount(); i++) { - const size_t xIndex = i * 2; - const size_t yIndex = xIndex + 1; - pts[i].set(x + offset.x() + it.pos()[xIndex], - y + offset.y() + it.pos()[yIndex]); - } - this->drawPosText(it.glyphs(), textLen, pts.get(), runPaint); - break; - } - default: - SK_ABORT("unhandled positioning mode"); - } - } -} - -void SkiaCanvasProxy::onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], - const SkPoint texCoords[4], SkBlendMode bmode, - const SkPaint& paint) { - if (mFilterHwuiCalls) { - return; - } - SkMatrix matrix; - mCanvas->getMatrix(&matrix); - SkISize lod = SkPatchUtils::GetLevelOfDetail(cubics, &matrix); - - mCanvas->drawVertices( - SkPatchUtils::MakeVertices(cubics, colors, texCoords, lod.width(), lod.height()).get(), - bmode, paint); -} - -void SkiaCanvasProxy::onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle) { - mCanvas->clipRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, op); -} - -void SkiaCanvasProxy::onClipRRect(const SkRRect& roundRect, SkClipOp op, ClipEdgeStyle) { - SkPath path; - path.addRRect(roundRect); - mCanvas->clipPath(&path, op); -} - -void SkiaCanvasProxy::onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle) { - mCanvas->clipPath(&path, op); -} - -}; // namespace uirenderer -}; // namespace android diff --git a/libs/hwui/SkiaCanvasProxy.h b/libs/hwui/SkiaCanvasProxy.h deleted file mode 100644 index 360d5a08b974..000000000000 --- a/libs/hwui/SkiaCanvasProxy.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2015 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. - */ - -#ifndef SkiaCanvasProxy_DEFINED -#define SkiaCanvasProxy_DEFINED - -#include <SkCanvas.h> -#include <cutils/compiler.h> - -#include "hwui/Canvas.h" - -namespace android { -namespace uirenderer { - -/** - * This class serves as a proxy between Skia's SkCanvas and Android Framework's - * Canvas. The class does not maintain any draw-related state and will pass - * through most requests directly to the Canvas provided in the constructor. - * - * Upon construction it is expected that the provided Canvas has already been - * prepared for recording and will continue to be in the recording state while - * this proxy class is being used. - * - * If filterHwuiCalls is true, the proxy silently ignores away draw calls that - * aren't supported by HWUI. - */ -class ANDROID_API SkiaCanvasProxy : public SkCanvas { -public: - explicit SkiaCanvasProxy(Canvas* canvas, bool filterHwuiCalls = false); - virtual ~SkiaCanvasProxy() {} - -protected: - virtual sk_sp<SkSurface> onNewSurface(const SkImageInfo&, const SkSurfaceProps&) override; - - virtual void willSave() override; - virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; - virtual void willRestore() override; - - virtual void didConcat(const SkMatrix&) override; - virtual void didSetMatrix(const SkMatrix&) override; - - virtual void onDrawPaint(const SkPaint& paint) override; - virtual void onDrawPoints(PointMode, size_t count, const SkPoint pts[], - const SkPaint&) override; - virtual void onDrawOval(const SkRect&, const SkPaint&) override; - virtual void onDrawRect(const SkRect&, const SkPaint&) override; - virtual void onDrawRRect(const SkRRect&, const SkPaint&) override; - virtual void onDrawPath(const SkPath& path, const SkPaint&) override; - virtual void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, - const SkPaint&) override; - virtual void onDrawBitmap(const SkBitmap&, SkScalar left, SkScalar top, - const SkPaint*) override; - virtual void onDrawBitmapRect(const SkBitmap&, const SkRect* src, const SkRect& dst, - const SkPaint* paint, SrcRectConstraint) override; - virtual void onDrawBitmapNine(const SkBitmap& bitmap, const SkIRect& center, const SkRect& dst, - const SkPaint*) override; - virtual void onDrawImage(const SkImage*, SkScalar dx, SkScalar dy, const SkPaint*); - virtual void onDrawImageRect(const SkImage*, const SkRect*, const SkRect&, const SkPaint*, - SrcRectConstraint); - virtual void onDrawImageNine(const SkImage*, const SkIRect& center, const SkRect& dst, - const SkPaint*); - virtual void onDrawImageLattice(const SkImage*, const Lattice& lattice, const SkRect& dst, - const SkPaint*); - virtual void onDrawVerticesObject(const SkVertices*, SkBlendMode, const SkPaint&) override; - - virtual void onDrawDRRect(const SkRRect&, const SkRRect&, const SkPaint&) override; - - virtual void onDrawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, - const SkPaint&) override; - virtual void onDrawPosText(const void* text, size_t byteLength, const SkPoint pos[], - const SkPaint&) override; - virtual void onDrawPosTextH(const void* text, size_t byteLength, const SkScalar xpos[], - SkScalar constY, const SkPaint&) override; - virtual void onDrawTextOnPath(const void* text, size_t byteLength, const SkPath& path, - const SkMatrix* matrix, const SkPaint&) override; - virtual void onDrawTextRSXform(const void* text, size_t byteLength, const SkRSXform[], - const SkRect* cullRect, const SkPaint& paint); - virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, - const SkPaint& paint) override; - - virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], - const SkPoint texCoords[4], SkBlendMode, - const SkPaint& paint) override; - - virtual void onClipRect(const SkRect&, SkClipOp, ClipEdgeStyle) override; - virtual void onClipRRect(const SkRRect&, SkClipOp, ClipEdgeStyle) override; - virtual void onClipPath(const SkPath&, SkClipOp, ClipEdgeStyle) override; - -private: - Canvas* mCanvas; - bool mFilterHwuiCalls; - - typedef SkCanvas INHERITED; -}; - -}; // namespace uirenderer -}; // namespace android - -#endif // SkiaCanvasProxy_DEFINED diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h index 5d380a68631f..b9af7de26721 100644 --- a/libs/hwui/hwui/Canvas.h +++ b/libs/hwui/hwui/Canvas.h @@ -74,7 +74,6 @@ typedef uint32_t Flags; } // namespace SaveFlags namespace uirenderer { -class SkiaCanvasProxy; namespace VectorDrawable { class Tree; }; @@ -305,7 +304,6 @@ protected: friend class DrawTextFunctor; friend class DrawTextOnPathFunctor; - friend class uirenderer::SkiaCanvasProxy; }; }; // namespace android diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h index 9a72706e44dd..69641d57ccbb 100644 --- a/libs/hwui/renderthread/VulkanManager.h +++ b/libs/hwui/renderthread/VulkanManager.h @@ -17,11 +17,14 @@ #ifndef VULKANMANAGER_H #define VULKANMANAGER_H +#if !defined(VK_USE_PLATFORM_ANDROID_KHR) +# define VK_USE_PLATFORM_ANDROID_KHR +#endif +#include <vulkan/vulkan.h> + #include <SkSurface.h> #include <vk/GrVkBackendContext.h> -#include <vulkan/vulkan.h> - namespace android { namespace uirenderer { namespace renderthread { diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java index dccfd7a87ba7..d5dd46caca7b 100644 --- a/media/java/android/media/MediaPlayer2Impl.java +++ b/media/java/android/media/MediaPlayer2Impl.java @@ -370,8 +370,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * after current data source is finished. * * @param dsd the descriptor of data source you want to play after current one - * @throws IllegalStateException if it is called in an invalid state - * @throws NullPointerException if dsd is null */ @Override public void setNextDataSource(@NonNull DataSourceDesc dsd) { @@ -384,14 +382,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { mNextDSDs.add(dsd); mNextSrcId = mSrcIdGenerator++; mNextSourceState = NEXT_SOURCE_STATE_INIT; - mNextSourcePlayPending = false; - } - int state = getState(); - if (state != PLAYER_STATE_IDLE) { - synchronized (mSrcLock) { - prepareNextDataSource_l(); - } } + prepareNextDataSource(); } }); } @@ -400,8 +392,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { * Sets a list of data sources to be played sequentially after current data source is done. * * @param dsds the list of data sources you want to play after current one - * @throws IllegalStateException if it is called in an invalid state - * @throws IllegalArgumentException if dsds is null or empty, or contains null DataSourceDesc */ @Override public void setNextDataSources(@NonNull List<DataSourceDesc> dsds) { @@ -422,14 +412,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { mNextDSDs = new ArrayList(dsds); mNextSrcId = mSrcIdGenerator++; mNextSourceState = NEXT_SOURCE_STATE_INIT; - mNextSourcePlayPending = false; - } - int state = getState(); - if (state != PLAYER_STATE_IDLE) { - synchronized (mSrcLock) { - prepareNextDataSource_l(); - } } + prepareNextDataSource(); } }); } @@ -904,67 +888,84 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { private native void nativeHandleDataSourceCallback( boolean isCurrent, long srcId, Media2DataSource dataSource); - // This function shall be called with |mSrcLock| acquired. - private void prepareNextDataSource_l() { - if (mNextDSDs == null || mNextDSDs.isEmpty() - || mNextSourceState != NEXT_SOURCE_STATE_INIT) { - // There is no next source or it's in preparing or prepared state. - return; + // This function should be always called on |mHandlerThread|. + private void prepareNextDataSource() { + if (Looper.myLooper() != mHandlerThread.getLooper()) { + Log.e(TAG, "prepareNextDataSource: called on wrong looper"); } - try { - mNextSourceState = NEXT_SOURCE_STATE_PREPARING; - handleDataSource(false /* isCurrent */, mNextDSDs.get(0), mNextSrcId); - } catch (Exception e) { - Message msg2 = mTaskHandler.obtainMessage( - MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); - final long nextSrcId = mNextSrcId; - mTaskHandler.post(new Runnable() { - @Override - public void run() { - mTaskHandler.handleMessage(msg2, nextSrcId); - } - }); - } - } - - // This function shall be called with |mSrcLock| acquired. - private void playNextDataSource_l() { - if (mNextDSDs == null || mNextDSDs.isEmpty()) { + int state = getState(); + if (state == PLAYER_STATE_ERROR || state == PLAYER_STATE_IDLE) { + // Current source has not been prepared yet. return; } - if (mNextSourceState == NEXT_SOURCE_STATE_PREPARED) { - // Switch to next source only when it's in prepared state. - mCurrentDSD = mNextDSDs.get(0); - mCurrentSrcId = mNextSrcId; - mBufferedPercentageCurrent.set(mBufferedPercentageNext.get()); - mNextDSDs.remove(0); - mNextSrcId = mSrcIdGenerator++; // make it different from mCurrentSrcId - mBufferedPercentageNext.set(0); - mNextSourceState = NEXT_SOURCE_STATE_INIT; - mNextSourcePlayPending = false; + synchronized (mSrcLock) { + if (mNextDSDs == null || mNextDSDs.isEmpty() + || mNextSourceState != NEXT_SOURCE_STATE_INIT) { + // There is no next source or it's in preparing or prepared state. + return; + } - long srcId = mCurrentSrcId; try { - nativePlayNextDataSource(srcId); + mNextSourceState = NEXT_SOURCE_STATE_PREPARING; + handleDataSource(false /* isCurrent */, mNextDSDs.get(0), mNextSrcId); } catch (Exception e) { - Message msg2 = mTaskHandler.obtainMessage( - MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); - mTaskHandler.post(new Runnable() { - @Override - public void run() { - mTaskHandler.handleMessage(msg2, srcId); - } - }); + Message msg = mTaskHandler.obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_IO, MEDIA_ERROR_UNKNOWN, null); + mTaskHandler.handleMessage(msg, mNextSrcId); + + mNextDSDs.remove(0); + // make a new SrcId to obsolete notification for previous one. + mNextSrcId = mSrcIdGenerator++; + mNextSourceState = NEXT_SOURCE_STATE_INIT; + prepareNextDataSource(); } + } + } - // Wait for MEDIA2_INFO_STARTED_AS_NEXT to prepare next source. - } else { - if (mNextSourceState == NEXT_SOURCE_STATE_INIT) { - prepareNextDataSource_l(); + // This function should be always called on |mHandlerThread|. + private void playNextDataSource() { + if (Looper.myLooper() != mHandlerThread.getLooper()) { + Log.e(TAG, "playNextDataSource: called on wrong looper"); + } + + synchronized (mSrcLock) { + if (mNextDSDs == null || mNextDSDs.isEmpty()) { + return; + } + + if (mNextSourceState == NEXT_SOURCE_STATE_PREPARED) { + // Switch to next source only when it has been prepared. + mCurrentDSD = mNextDSDs.get(0); + mCurrentSrcId = mNextSrcId; + mBufferedPercentageCurrent.set(mBufferedPercentageNext.get()); + mNextDSDs.remove(0); + mNextSrcId = mSrcIdGenerator++; // make it different from |mCurrentSrcId| + mBufferedPercentageNext.set(0); + mNextSourceState = NEXT_SOURCE_STATE_INIT; + + long srcId = mCurrentSrcId; + try { + nativePlayNextDataSource(srcId); + } catch (Exception e) { + Message msg2 = mTaskHandler.obtainMessage( + MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, MEDIA_ERROR_UNSUPPORTED, null); + mTaskHandler.handleMessage(msg2, srcId); + // Keep |mNextSourcePlayPending| + prepareNextDataSource(); + return; + } + stayAwake(true); + + // Now a new current src is playing. + // Wait for MEDIA2_INFO_STARTED_AS_NEXT to prepare next source. + mNextSourcePlayPending = false; + } else { + if (mNextSourceState == NEXT_SOURCE_STATE_INIT) { + prepareNextDataSource(); + } } - mNextSourcePlayPending = true; } } @@ -2664,6 +2665,21 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { final int what = msg.arg1; final int extra = msg.arg2; + final DataSourceDesc dsd; + boolean isCurrentSrcId = false; + boolean isNextSrcId = false; + synchronized (mSrcLock) { + if (srcId == mCurrentSrcId) { + dsd = mCurrentDSD; + isCurrentSrcId = true; + } else if (mNextDSDs != null && !mNextDSDs.isEmpty() && srcId == mNextSrcId) { + dsd = mNextDSDs.get(0); + isNextSrcId = true; + } else { + return; + } + } + switch(msg.what) { case MEDIA_PREPARED: { @@ -2678,33 +2694,29 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { sendMessage(msg2); } - final DataSourceDesc dsd; + if (dsd != null) { + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0)); + } + } + } + synchronized (mSrcLock) { Log.i(TAG, "MEDIA_PREPARED: srcId=" + srcId + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId); - if (srcId == mCurrentSrcId) { - dsd = mCurrentDSD; - prepareNextDataSource_l(); - } else if (mNextDSDs != null && !mNextDSDs.isEmpty() - && srcId == mNextSrcId) { - dsd = mNextDSDs.get(0); + + if (isCurrentSrcId) { + prepareNextDataSource(); + } else if (isNextSrcId) { mNextSourceState = NEXT_SOURCE_STATE_PREPARED; if (mNextSourcePlayPending) { - playNextDataSource_l(); + playNextDataSource(); } - } else { - dsd = null; } } - if (dsd != null) { - synchronized (mEventCbLock) { - for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, dsd, MEDIA_INFO_PREPARED, 0)); - } - } - } synchronized (mTaskLock) { if (mCurrentTask != null && mCurrentTask.mMediaCallType == CALL_COMPLETED_PREPARE @@ -2739,7 +2751,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, DrmEventCallback> cb : mDrmEventCallbackRecords) { cb.first.execute(() -> cb.second.onDrmInfo( - mMediaPlayer, mCurrentDSD, drmInfo)); + mMediaPlayer, dsd, drmInfo)); } } } @@ -2751,22 +2763,25 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { case MEDIA_PLAYBACK_COMPLETE: { - final DataSourceDesc dsd = mCurrentDSD; - synchronized (mSrcLock) { - if (srcId == mCurrentSrcId) { + if (isCurrentSrcId) { + synchronized (mEventCbLock) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + } + } + stayAwake(false); + + synchronized (mSrcLock) { + mNextSourcePlayPending = true; + Log.i(TAG, "MEDIA_PLAYBACK_COMPLETE: srcId=" + srcId + ", currentSrcId=" + mCurrentSrcId + ", nextSrcId=" + mNextSrcId); - playNextDataSource_l(); } - } - synchronized (mEventCbLock) { - for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); - } + playNextDataSource(); } - stayAwake(false); + return; } @@ -2793,21 +2808,18 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { { final int percent = msg.arg1; synchronized (mEventCbLock) { - if (srcId == mCurrentSrcId) { + for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { + cb.first.execute(() -> cb.second.onInfo( + mMediaPlayer, dsd, MEDIA_INFO_BUFFERING_UPDATE, + percent)); + } + } + + synchronized (mSrcLock) { + if (isCurrentSrcId) { mBufferedPercentageCurrent.set(percent); - for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, mCurrentDSD, MEDIA_INFO_BUFFERING_UPDATE, - percent)); - } - } else if (srcId == mNextSrcId && !mNextDSDs.isEmpty()) { + } else if (isNextSrcId) { mBufferedPercentageNext.set(percent); - DataSourceDesc nextDSD = mNextDSDs.get(0); - for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { - cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, nextDSD, MEDIA_INFO_BUFFERING_UPDATE, - percent)); - } } } return; @@ -2843,7 +2855,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onVideoSizeChanged( - mMediaPlayer, mCurrentDSD, width, height)); + mMediaPlayer, dsd, width, height)); } } return; @@ -2855,9 +2867,9 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onError( - mMediaPlayer, mCurrentDSD, what, extra)); + mMediaPlayer, dsd, what, extra)); cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, mCurrentDSD, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); + mMediaPlayer, dsd, MEDIA_INFO_PLAYBACK_COMPLETE, 0)); } } stayAwake(false); @@ -2868,8 +2880,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { { switch (msg.arg1) { case MEDIA_INFO_STARTED_AS_NEXT: - if (srcId == mCurrentSrcId) { - prepareNextDataSource_l(); + if (isCurrentSrcId) { + prepareNextDataSource(); } break; @@ -2908,7 +2920,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onInfo( - mMediaPlayer, mCurrentDSD, what, extra)); + mMediaPlayer, dsd, what, extra)); } } // No real default action so far. @@ -2938,7 +2950,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onTimedText( - mMediaPlayer, mCurrentDSD, text)); + mMediaPlayer, dsd, text)); } } return; @@ -2953,7 +2965,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onSubtitleData( - mMediaPlayer, mCurrentDSD, data)); + mMediaPlayer, dsd, data)); } } } @@ -2974,7 +2986,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 { synchronized (mEventCbLock) { for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) { cb.first.execute(() -> cb.second.onTimedMetaDataAvailable( - mMediaPlayer, mCurrentDSD, data)); + mMediaPlayer, dsd, data)); } } return; diff --git a/media/jni/android_media_MediaDrm.cpp b/media/jni/android_media_MediaDrm.cpp index 54541f02f575..7cf8828c0623 100644 --- a/media/jni/android_media_MediaDrm.cpp +++ b/media/jni/android_media_MediaDrm.cpp @@ -43,27 +43,27 @@ namespace android { #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ - LOG_FATAL_IF(! (var), "Unable to find class " className); + LOG_FATAL_IF(! (var), "Unable to find class %s", className); #define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! (var), "Unable to find field " fieldName); + LOG_FATAL_IF(! (var), "Unable to find field %s", fieldName); #define GET_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetMethodID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! (var), "Unable to find method " fieldName); + LOG_FATAL_IF(! (var), "Unable to find method %s", fieldName); #define GET_STATIC_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetStaticFieldID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! (var), "Unable to find field " fieldName); + LOG_FATAL_IF(! (var), "Unable to find field %s", fieldName); #define GET_STATIC_METHOD_ID(var, clazz, fieldName, fieldDescriptor) \ var = env->GetStaticMethodID(clazz, fieldName, fieldDescriptor); \ - LOG_FATAL_IF(! (var), "Unable to find static method " fieldName); + LOG_FATAL_IF(! (var), "Unable to find static method %s", fieldName); -#define GET_STATIC_OBJECT_FIELD(var, clazz, fieldName) \ - var = env->GetStaticObjectField(clazz, fieldName); \ - LOG_FATAL_IF(! (var), "Unable to find static object field " fieldName); +#define GET_STATIC_OBJECT_FIELD(var, clazz, fieldId) \ + var = env->GetStaticObjectField(clazz, fieldId); \ + LOG_FATAL_IF(! (var), "Unable to find static object field %p", fieldId); struct RequestFields { diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index 7788f7e8c585..04f2411a46bd 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1120,4 +1120,5 @@ <!-- time label for event have that happened very recently [CHAR LIMIT=60] --> <string name="time_unit_just_now">Just now</string> -</resources> + + </resources> diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index 77eb6c472e4a..10c1211b1d7f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -186,9 +186,21 @@ class SettingsProtoDumpUtil { GlobalSettingsProto.Auto.TIME_ZONE); p.end(autoToken); + final long autofillToken = p.start(GlobalSettingsProto.AUTOFILL); dumpSetting(s, p, Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES, - GlobalSettingsProto.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES); + GlobalSettingsProto.Autofill.COMPAT_MODE_ALLOWED_PACKAGES); + dumpSetting(s, p, + Settings.Global.AUTOFILL_LOGGING_LEVEL, + GlobalSettingsProto.Autofill.LOGGING_LEVEL); + dumpSetting(s, p, + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, + GlobalSettingsProto.Autofill.MAX_PARTITIONS_SIZE); + dumpSetting(s, p, + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, + GlobalSettingsProto.Autofill.MAX_VISIBLE_DATASETS); + p.end(autofillToken); + dumpSetting(s, p, Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, GlobalSettingsProto.BACKUP_AGENT_TIMEOUT_PARAMETERS); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 9592b63c6d1e..8ac9fb91d39d 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -816,7 +816,7 @@ public class SettingsProvider extends ContentProvider { @Override public void onPackageRemoved(String packageName, int uid) { synchronized (mLock) { - mSettingsRegistry.onPackageRemovedLocked(packageName, + mSettingsRegistry.removeSettingsForPackageLocked(packageName, UserHandle.getUserId(uid)); } } @@ -827,6 +827,14 @@ public class SettingsProvider extends ContentProvider { mSettingsRegistry.onUidRemovedLocked(uid); } } + + @Override + public void onPackageDataCleared(String packageName, int uid) { + synchronized (mLock) { + mSettingsRegistry.removeSettingsForPackageLocked(packageName, + UserHandle.getUserId(uid)); + } + } }; // package changes @@ -2547,7 +2555,7 @@ public class SettingsProvider extends ContentProvider { } } - public void onPackageRemovedLocked(String packageName, int userId) { + public void removeSettingsForPackageLocked(String packageName, int userId) { // Global and secure settings are signature protected. Apps signed // by the platform certificate are generally not uninstalled and // the main exception is tests. We trust components signed @@ -2556,7 +2564,7 @@ public class SettingsProvider extends ContentProvider { final int systemKey = makeKey(SETTINGS_TYPE_SYSTEM, userId); SettingsState systemSettings = mSettingsStates.get(systemKey); if (systemSettings != null) { - systemSettings.onPackageRemovedLocked(packageName); + systemSettings.removeSettingsForPackageLocked(packageName); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 449946d7ab15..e57483a8c734 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -293,7 +293,7 @@ final class SettingsState { } // The settings provider must hold its lock when calling here. - public void onPackageRemovedLocked(String packageName) { + public void removeSettingsForPackageLocked(String packageName) { boolean removedSomething = false; final int settingCount = mSettings.size(); diff --git a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml index 37e2b537f90c..c9f51486e983 100644 --- a/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml +++ b/packages/SystemUI/res/layout/car_fullscreen_user_switcher.xml @@ -38,8 +38,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="@dimen/car_user_switcher_margin_top" + android:theme="@style/Theme.Car.Light.List" app:verticallyCenterListContent="true" - app:dayNightStyle="always_light" app:showPagedListViewDivider="false" app:gutter="both" app:itemSpacing="@dimen/car_user_switcher_vertical_spacing_between_users"/> diff --git a/packages/SystemUI/res/layout/car_qs_panel.xml b/packages/SystemUI/res/layout/car_qs_panel.xml index c43afc980bbf..e7413de342fa 100644 --- a/packages/SystemUI/res/layout/car_qs_panel.xml +++ b/packages/SystemUI/res/layout/car_qs_panel.xml @@ -39,7 +39,7 @@ android:id="@+id/user_grid" android:layout_width="match_parent" android:layout_height="match_parent" - app:dayNightStyle="always_light" + android:theme="@style/Theme.Car.Light.List" app:showPagedListViewDivider="false" app:gutter="both" app:itemSpacing="@dimen/car_user_switcher_vertical_spacing_between_users"/> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index 3ac67056ff80..d5383b975211 100644 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -146,6 +146,7 @@ public class BatteryMeterView extends LinearLayout implements getContext().getContentResolver().registerContentObserver( Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, newUserId); + updateShowPercent(); } }; diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java index 4bb4e79c91f6..36b234704592 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java @@ -23,6 +23,8 @@ import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Handler; import android.os.Trace; +import android.os.UserHandle; +import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; @@ -111,7 +113,7 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen int brightness = computeBrightness(mLastSensorValue); boolean brightnessReady = brightness > 0; if (brightnessReady) { - mDozeService.setDozeScreenBrightness(brightness); + mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness)); } int scrimOpacity = -1; @@ -150,10 +152,17 @@ public class DozeScreenBrightness implements DozeMachine.Part, SensorEventListen } private void resetBrightnessToDefault() { - mDozeService.setDozeScreenBrightness(mDefaultDozeBrightness); + mDozeService.setDozeScreenBrightness(clampToUserSetting(mDefaultDozeBrightness)); mDozeHost.setAodDimmingScrim(0f); } + private int clampToUserSetting(int brightness) { + int userSetting = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, Integer.MAX_VALUE, + UserHandle.USER_CURRENT); + return Math.min(brightness, userSetting); + } + private void setLightSensorEnabled(boolean enabled) { if (enabled && !mRegistered && mLightSensor != null) { // Wait until we get an event from the sensor until indicating ready. diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 02937d8457bc..bb69b7a17660 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -50,6 +50,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; +import java.util.function.Predicate; /** Platform implementation of the quick settings tile host **/ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory> { @@ -226,23 +227,22 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory> { } @Override - public void removeTile(String tileSpec) { - ArrayList<String> specs = new ArrayList<>(mTileSpecs); - specs.remove(tileSpec); - Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, - TextUtils.join(",", specs), ActivityManager.getCurrentUser()); + public void removeTile(String spec) { + changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)); } public void addTile(String spec) { + changeTileSpecs(tileSpecs-> tileSpecs.add(spec)); + } + + private void changeTileSpecs(Predicate<List<String>> changeFunction) { final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), - TILES_SETTING, ActivityManager.getCurrentUser()); + TILES_SETTING, ActivityManager.getCurrentUser()); final List<String> tileSpecs = loadTileSpecs(mContext, setting); - if (tileSpecs.contains(spec)) { - return; - } - tileSpecs.add(spec); - Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, + if (changeFunction.test(tileSpecs)) { + Settings.Secure.putStringForUser(mContext.getContentResolver(), TILES_SETTING, TextUtils.join(",", tileSpecs), ActivityManager.getCurrentUser()); + } } public void addTile(ComponentName tile) { @@ -300,7 +300,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory> { throw new RuntimeException("Default factory didn't create view for " + tile.getTileSpec()); } - protected List<String> loadTileSpecs(Context context, String tileList) { + protected static List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); final String defaultTileList = res.getString(R.string.quick_settings_tiles_default); if (tileList == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e8cd32b0dfde..987de0e2a646 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -3287,8 +3287,20 @@ public class NotificationStackScrollLayout extends ViewGroup private void generateViewResizeEvent() { if (mNeedViewResizeAnimation) { - mAnimationEvents.add( - new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); + boolean hasDisappearAnimation = false; + for (AnimationEvent animationEvent : mAnimationEvents) { + final int type = animationEvent.animationType; + if (type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK + || type == AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR) { + hasDisappearAnimation = true; + break; + } + } + + if (!hasDisappearAnimation) { + mAnimationEvents.add( + new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE)); + } } mNeedViewResizeAnimation = false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index c4008934c49f..4c91a9d5ff60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -516,7 +516,7 @@ public class StatusBar extends SystemUI implements DemoMode, } WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT); final boolean supportsAmbientMode = info != null && - info.getSupportsAmbientMode(); + info.supportsAmbientMode(); mStatusBarWindowManager.setWallpaperSupportsAmbientMode(supportsAmbientMode); mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index f729120f98bc..8dbdd21c2d64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -16,6 +16,12 @@ package com.android.systemui.statusbar.policy; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -55,7 +61,6 @@ import com.android.systemui.R; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; -import com.android.systemui.statusbar.policy.MobileSignalController.MobileState; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -65,8 +70,6 @@ import java.util.Comparator; import java.util.List; import java.util.Locale; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; - /** Platform implementation of the network controller. **/ public class NetworkControllerImpl extends BroadcastReceiver implements NetworkController, DemoMode, DataUsageController.NetworkNameProvider, @@ -849,20 +852,20 @@ public class NetworkControllerImpl extends BroadcastReceiver if (activity != null) { switch (activity) { case "inout": - mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_INOUT); + mWifiSignalController.setActivity(DATA_ACTIVITY_INOUT); break; case "in": - mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_IN); + mWifiSignalController.setActivity(DATA_ACTIVITY_IN); break; case "out": - mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_OUT); + mWifiSignalController.setActivity(DATA_ACTIVITY_OUT); break; default: - mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE); + mWifiSignalController.setActivity(DATA_ACTIVITY_NONE); break; } } else { - mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE); + mWifiSignalController.setActivity(DATA_ACTIVITY_NONE); } String ssid = args.getString("ssid"); if (ssid != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java index cf80988ba61d..0233ad1c86ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java @@ -15,21 +15,19 @@ */ package com.android.systemui.statusbar.policy; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; +import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; + import android.content.Context; import android.content.Intent; import android.net.ConnectivityManager; import android.net.NetworkCapabilities; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; import android.text.TextUtils; -import android.util.Log; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.AsyncChannel; import com.android.settingslib.wifi.WifiStatusTracker; import com.android.systemui.R; import com.android.systemui.statusbar.policy.NetworkController.IconState; @@ -37,10 +35,8 @@ import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import java.util.Objects; - public class WifiSignalController extends SignalController<WifiSignalController.WifiState, SignalController.IconGroup> { - private final AsyncChannel mWifiChannel; private final boolean mHasMobileData; private final WifiStatusTracker mWifiTracker; @@ -57,12 +53,7 @@ public class WifiSignalController extends connectivityManager, this::handleStatusUpdated); mWifiTracker.setListening(true); mHasMobileData = hasMobileData; - Handler handler = new WifiHandler(Looper.getMainLooper()); - mWifiChannel = new AsyncChannel(); - Messenger wifiMessenger = wifiManager.getWifiServiceMessenger(); - if (wifiMessenger != null) { - mWifiChannel.connect(context, handler, wifiMessenger); - } + wifiManager.registerTrafficStateCallback(new WifiTrafficStateCallback(), null); // WiFi only has one state. mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup( "Wi-Fi Icons", @@ -124,39 +115,20 @@ public class WifiSignalController extends @VisibleForTesting void setActivity(int wifiActivity) { - mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT - || wifiActivity == WifiManager.DATA_ACTIVITY_IN; - mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT - || wifiActivity == WifiManager.DATA_ACTIVITY_OUT; + mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT + || wifiActivity == DATA_ACTIVITY_IN; + mCurrentState.activityOut = wifiActivity == DATA_ACTIVITY_INOUT + || wifiActivity == DATA_ACTIVITY_OUT; notifyListenersIfNecessary(); } /** * Handler to receive the data activity on wifi. */ - private class WifiHandler extends Handler { - WifiHandler(Looper looper) { - super(looper); - } - + private class WifiTrafficStateCallback implements WifiManager.TrafficStateCallback { @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: - if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { - mWifiChannel.sendMessage(Message.obtain(this, - AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)); - } else { - Log.e(mTag, "Failed to connect to wifi"); - } - break; - case WifiManager.DATA_ACTIVITY_NOTIFICATION: - setActivity(msg.arg1); - break; - default: - // Ignore - break; - } + public void onStateChanged(int state) { + setActivity(state); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java index 5e12781399e5..eaa0dcf88588 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java @@ -34,6 +34,8 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import android.os.PowerManager; +import android.os.UserHandle; +import android.provider.Settings; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -60,6 +62,9 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @Before public void setUp() throws Exception { + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS, + UserHandle.USER_CURRENT); mServiceFake = new DozeServiceFake(); mHostFake = new DozeHostFake(); mSensorManager = new FakeSensorManager(mContext); @@ -88,6 +93,17 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { } @Test + public void testAod_usesLightSensorRespectingUserSetting() throws Exception { + int maxBrightness = 3; + Settings.System.putIntForUser(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS, maxBrightness, + UserHandle.USER_CURRENT); + + mScreen.transitionTo(UNINITIALIZED, INITIALIZED); + assertEquals(maxBrightness, mServiceFake.screenBrightness); + } + + @Test public void testPausingAod_doesntPauseLightSensor() throws Exception { mScreen.transitionTo(UNINITIALIZED, INITIALIZED); mScreen.transitionTo(INITIALIZED, DOZE_AOD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java index dff06659906e..6e3d9067f63c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java @@ -87,15 +87,15 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest { WifiIcons.QS_WIFI_SIGNAL_STRENGTH[1][testLevel], testSsid); // Set to different activity state first to ensure a callback happens. - setWifiActivity(WifiManager.DATA_ACTIVITY_IN); + setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN); - setWifiActivity(WifiManager.DATA_ACTIVITY_NONE); + setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE); verifyLastQsDataDirection(false, false); - setWifiActivity(WifiManager.DATA_ACTIVITY_IN); + setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN); verifyLastQsDataDirection(true, false); - setWifiActivity(WifiManager.DATA_ACTIVITY_OUT); + setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT); verifyLastQsDataDirection(false, true); - setWifiActivity(WifiManager.DATA_ACTIVITY_INOUT); + setWifiActivity(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT); verifyLastQsDataDirection(true, true); } diff --git a/proto/Android.bp b/proto/Android.bp index 5fd288513384..f3811bdd7d81 100644 --- a/proto/Android.bp +++ b/proto/Android.bp @@ -17,33 +17,3 @@ java_library_static { }, }, } - -cc_library { - name: "libmetricprotos", - host_supported: true, - proto: { - export_proto_headers: true, - include_dirs: ["external/protobuf/src"], - }, - cflags: [ - "-Wall", - "-Werror", - "-Wno-unused-parameter", - ], - srcs: ["src/metrics_constants.proto"], - target: { - host: { - proto: { - type: "full", - }, - }, - android: { - proto: { - type: "lite", - }, - shared: { - enabled: false, - }, - }, - }, -} diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto index 496cd962c085..02f04398d6ac 100644 --- a/proto/src/metrics_constants.proto +++ b/proto/src/metrics_constants.proto @@ -6175,6 +6175,11 @@ message MetricsEvent { // OS: Q FACE = 1511; + // OPEN: Settings > Acessibility > HearingAid pairing instructions dialog + // CATEGORY: SETTINGS + // OS: Q + DIALOG_ACCESSIBILITY_HEARINGAID = 1512; + // ---- End Q Constants, all Q constants go above this line ---- diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2da0818ebc95..9c0e1105eb4a 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -16,12 +16,18 @@ package com.android.server.accessibility; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS; import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS; import static com.android.internal.util.FunctionalUtils.ignoreRemoteException; import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_WITH_HARD_KEYBOARD; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE; +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN; import android.Manifest; import android.accessibilityservice.AccessibilityService; @@ -1807,7 +1813,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub updateDisplayDaltonizerLocked(userState); updateDisplayInversionLocked(userState); updateMagnificationLocked(userState); - updateSoftKeyboardShowModeLocked(userState); scheduleUpdateFingerprintGestureHandling(userState); scheduleUpdateInputFilter(userState); scheduleUpdateClientsIfNeededLocked(userState); @@ -2001,18 +2006,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - private boolean readSoftKeyboardShowModeChangedLocked(UserState userState) { - final int softKeyboardShowMode = Settings.Secure.getIntForUser( - mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, - userState.mUserId); - if (softKeyboardShowMode != userState.mSoftKeyboardShowMode) { - userState.mSoftKeyboardShowMode = softKeyboardShowMode; - return true; - } - return false; - } - private void updateTouchExplorationLocked(UserState userState) { boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked(); final int serviceCount = userState.mBoundServices.size(); @@ -2211,34 +2204,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - private void updateSoftKeyboardShowModeLocked(UserState userState) { - final int userId = userState.mUserId; - // Only check whether we need to reset the soft keyboard mode if it is not set to the - // default. - if ((userId == mCurrentUserId) && (userState.mSoftKeyboardShowMode != 0)) { - // Check whether the last AccessibilityService that changed the soft keyboard mode to - // something other than the default is still enabled and, if not, remove flag and - // reset to the default soft keyboard behavior. - boolean serviceChangingSoftKeyboardModeIsEnabled = - userState.mEnabledServices.contains(userState.mServiceChangingSoftKeyboardMode); - - if (!serviceChangingSoftKeyboardModeIsEnabled) { - final long identity = Binder.clearCallingIdentity(); - try { - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, - 0, - userState.mUserId); - } finally { - Binder.restoreCallingIdentity(identity); - } - userState.mSoftKeyboardShowMode = 0; - userState.mServiceChangingSoftKeyboardMode = null; - notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode); - } - } - } - private void updateFingerprintGestureHandling(UserState userState) { final List<AccessibilityServiceConnection> services; synchronized (mLock) { @@ -2473,6 +2438,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private void putSecureIntForUser(String key, int value, int userid) { + final long identity = Binder.clearCallingIdentity(); + try { + Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userid); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + class RemoteAccessibilityConnection implements DeathRecipient { private final int mUid; private final String mPackageName; @@ -3683,7 +3657,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public int mLastSentClientState = -1; - public int mSoftKeyboardShowMode = 0; + private int mSoftKeyboardShowMode = 0; public boolean mIsNavBarMagnificationAssignedToAccessibilityButton; public ComponentName mServiceAssignedToAccessibilityButton; @@ -3744,7 +3718,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mServiceAssignedToAccessibilityButton = null; mIsNavBarMagnificationAssignedToAccessibilityButton = false; mIsAutoclickEnabled = false; - mSoftKeyboardShowMode = 0; } public void addServiceLocked(AccessibilityServiceConnection serviceConnection) { @@ -3766,6 +3739,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void removeServiceLocked(AccessibilityServiceConnection serviceConnection) { mBoundServices.remove(serviceConnection); serviceConnection.onRemoved(); + if ((mServiceChangingSoftKeyboardMode != null) + && (mServiceChangingSoftKeyboardMode.equals( + serviceConnection.getServiceInfo().getComponentName()))) { + setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null); + } // It may be possible to bind a service twice, which confuses the map. Rebuild the map // to make sure we can still reach a service mComponentNameToServiceMap.clear(); @@ -3792,6 +3770,134 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public Set<ComponentName> getBindingServicesLocked() { return mBindingServices; } + + public int getSoftKeyboardShowMode() { + return mSoftKeyboardShowMode; + } + + /** + * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings. + * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system + * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting + * setting can be changed by the user, and prevents the system from suppressing the soft + * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer + * to the user's preference, if they have supplied one. + * + * @param newMode The new mode + * @param requester The service requesting the change, so we can undo it when the + * service stops. Set to null if something other than a service is forcing + * the change. + * + * @return Whether or not the soft keyboard mode equals the new mode after the call + */ + public boolean setSoftKeyboardModeLocked(int newMode, @Nullable ComponentName requester) { + if ((newMode != SHOW_MODE_AUTO) && (newMode != SHOW_MODE_HIDDEN) + && (newMode != SHOW_MODE_WITH_HARD_KEYBOARD)) + { + Slog.w(LOG_TAG, "Invalid soft keyboard mode"); + return false; + } + if (mSoftKeyboardShowMode == newMode) return true; + + if (newMode == SHOW_MODE_WITH_HARD_KEYBOARD) { + if (hasUserOverriddenHardKeyboardSettingLocked()) { + // The user has specified a default for this setting + return false; + } + // Save the original value. But don't do this if the value in settings is already + // the new mode. That happens when we start up after a reboot, and we don't want + // to overwrite the value we had from when we first started controlling the setting. + if (getSoftKeyboardValueFromSettings() != SHOW_MODE_WITH_HARD_KEYBOARD) { + setOriginalHardKeyboardValue( + Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0); + } + putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId); + } else if (mSoftKeyboardShowMode == SHOW_MODE_WITH_HARD_KEYBOARD) { + putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, + getOriginalHardKeyboardValue() ? 1 : 0, mUserId); + } + + saveSoftKeyboardValueToSettings(newMode); + mSoftKeyboardShowMode = newMode; + mServiceChangingSoftKeyboardMode = requester; + notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode); + return true; + } + + /** + * If the settings are inconsistent with the internal state, make the internal state + * match the settings. + */ + public void reconcileSoftKeyboardModeWithSettingsLocked() { + final ContentResolver cr = mContext.getContentResolver(); + final boolean showWithHardKeyboardSettings = + Settings.Secure.getInt(cr, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0; + if (mSoftKeyboardShowMode == SHOW_MODE_WITH_HARD_KEYBOARD) { + if (!showWithHardKeyboardSettings) { + // The user has overridden the setting. Respect that and prevent further changes + // to this behavior. + setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null); + setUserOverridesHardKeyboardSettingLocked(); + } + } + + // If the setting and the internal state are out of sync, set both to default + if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode) + { + Slog.e(LOG_TAG, + "Show IME setting inconsistent with internal state. Overwriting"); + setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null); + putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, + SHOW_MODE_AUTO, mUserId); + } + } + + private void setUserOverridesHardKeyboardSettingLocked() { + final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0); + putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, + softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN, + mUserId); + } + + private boolean hasUserOverriddenHardKeyboardSettingLocked() { + final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0); + return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN) + != 0; + } + + private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) { + final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0); + final int newSoftKeyboardSetting = oldSoftKeyboardSetting + & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) + | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0); + putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, + newSoftKeyboardSetting, mUserId); + } + + private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) { + final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0); + final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK) + | softKeyboardShowMode; + putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, + newSoftKeyboardSetting, mUserId); + } + + private int getSoftKeyboardValueFromSettings() { + return Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, + SHOW_MODE_AUTO) & SHOW_MODE_MASK; + } + + private boolean getOriginalHardKeyboardValue() { + return (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0) + & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0; + } } private final class AccessibilityContentObserver extends ContentObserver { @@ -3829,6 +3935,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final Uri mAccessibilitySoftKeyboardModeUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); + private final Uri mShowImeWithHardKeyboardUri = Settings.Secure.getUriFor( + Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); + private final Uri mAccessibilityShortcutServiceIdUri = Settings.Secure.getUriFor( Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); @@ -3864,6 +3973,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub contentResolver.registerContentObserver( mAccessibilitySoftKeyboardModeUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( + mShowImeWithHardKeyboardUri, false, this, UserHandle.USER_ALL); + contentResolver.registerContentObserver( mAccessibilityShortcutServiceIdUri, false, this, UserHandle.USER_ALL); contentResolver.registerContentObserver( mAccessibilityButtonComponentIdUri, false, this, UserHandle.USER_ALL); @@ -3906,11 +4017,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (readHighTextContrastEnabledSettingLocked(userState)) { onUserStateChangedLocked(userState); } - } else if (mAccessibilitySoftKeyboardModeUri.equals(uri)) { - if (readSoftKeyboardShowModeChangedLocked(userState)) { - notifySoftKeyboardShowModeChangedLocked(userState.mSoftKeyboardShowMode); - onUserStateChangedLocked(userState); - } + } else if (mAccessibilitySoftKeyboardModeUri.equals(uri) + || mShowImeWithHardKeyboardUri.equals(uri)) { + userState.reconcileSoftKeyboardModeWithSettingsLocked(); } else if (mAccessibilityShortcutServiceIdUri.equals(uri)) { if (readAccessibilityShortcutSettingLocked(userState)) { onUserStateChangedLocked(userState); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 8f921454196f..6237212685a4 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -218,26 +218,20 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!isCalledForCurrentUserLocked()) { return false; } + final UserState userState = mUserStateWeakReference.get(); + if (userState == null) return false; + return userState.setSoftKeyboardModeLocked(showMode, mComponentName); } - UserState userState = mUserStateWeakReference.get(); - if (userState == null) return false; - final long identity = Binder.clearCallingIdentity(); - try { - // Keep track of the last service to request a non-default show mode. The show mode - // should be restored to default should this service be disabled. - userState.mServiceChangingSoftKeyboardMode = (showMode == SHOW_MODE_AUTO) - ? null : mComponentName; - - Settings.Secure.putIntForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, showMode, - userState.mUserId); - } finally { - Binder.restoreCallingIdentity(identity); - } - return true; } @Override + public int getSoftKeyboardShowMode() { + final UserState userState = mUserStateWeakReference.get(); + return (userState != null) ? userState.getSoftKeyboardShowMode() : 0; + } + + + @Override public boolean isAccessibilityButtonAvailable() { synchronized (mLock) { if (!isCalledForCurrentUserLocked()) { diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index ed3b3e770338..ff2931160f09 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -259,6 +259,11 @@ class UiAutomationManager { } @Override + public int getSoftKeyboardShowMode() { + return 0; + } + + @Override public boolean isAccessibilityButtonAvailable() { return false; } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 021fdcd84971..0610256ae10b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -166,9 +166,9 @@ public final class AutofillManagerService extends SystemService { mContext = context; mUi = new AutoFillUI(ActivityThread.currentActivityThread().getSystemUiContext()); - final boolean debug = Build.IS_DEBUGGABLE; - Slog.i(TAG, "Setting debug to " + debug); - setDebugLocked(debug); + setLogLevelFromSettings(); + setMaxPartitionsFromSettings(); + setMaxVisibleDatasetsFromSettings(); final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -438,12 +438,28 @@ public final class AutofillManagerService extends SystemService { Slog.i(TAG, "setLogLevel(): " + level); mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AUTOFILL_LOGGING_LEVEL, level); + } + + private void setLogLevelFromSettings() { + final int level = Settings.Global.getInt( + mContext.getContentResolver(), + Settings.Global.AUTOFILL_LOGGING_LEVEL, AutofillManager.DEFAULT_LOGGING_LEVEL); boolean debug = false; boolean verbose = false; - if (level == AutofillManager.FLAG_ADD_CLIENT_VERBOSE) { - debug = verbose = true; - } else if (level == AutofillManager.FLAG_ADD_CLIENT_DEBUG) { - debug = true; + if (level != AutofillManager.NO_LOGGING) { + if (level == AutofillManager.FLAG_ADD_CLIENT_VERBOSE) { + debug = verbose = true; + } else if (level == AutofillManager.FLAG_ADD_CLIENT_DEBUG) { + debug = true; + } else { + Slog.w(TAG, "setLogLevelFromSettings(): invalid level: " + level); + } + } + if (debug || sDebug) { + Slog.d(TAG, "setLogLevelFromSettings(): level=" + level + ", debug=" + debug + + ", verbose=" + verbose); } synchronized (mLock) { setDebugLocked(debug); @@ -475,6 +491,17 @@ public final class AutofillManagerService extends SystemService { void setMaxPartitions(int max) { mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); Slog.i(TAG, "setMaxPartitions(): " + max); + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, max); + } + + private void setMaxPartitionsFromSettings() { + final int max = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE, + AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE); + if (sDebug) Slog.d(TAG, "setMaxPartitionsFromSettings(): " + max); + synchronized (mLock) { sPartitionMaxCount = max; } @@ -493,6 +520,16 @@ public final class AutofillManagerService extends SystemService { void setMaxVisibleDatasets(int max) { mContext.enforceCallingPermission(MANAGE_AUTO_FILL, TAG); Slog.i(TAG, "setMaxVisibleDatasets(): " + max); + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, max); + } + + private void setMaxVisibleDatasetsFromSettings() { + final int max = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS, 0); + + if (sDebug) Slog.d(TAG, "setMaxVisibleDatasetsFromSettings(): " + max); synchronized (mLock) { sVisibleDatasetsMaxCount = max; } @@ -1289,13 +1326,34 @@ public final class AutofillManagerService extends SystemService { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_LOGGING_LEVEL), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE), false, this, + UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS), false, this, + UserHandle.USER_ALL); } @Override public void onChange(boolean selfChange, Uri uri, int userId) { if (sVerbose) Slog.v(TAG, "onChange(): uri=" + uri + ", userId=" + userId); - synchronized (mLock) { - updateCachedServiceLocked(userId); + switch (uri.getLastPathSegment()) { + case Settings.Global.AUTOFILL_LOGGING_LEVEL: + setLogLevelFromSettings(); + break; + case Settings.Global.AUTOFILL_MAX_PARTITIONS_SIZE: + setMaxPartitionsFromSettings(); + break; + case Settings.Global.AUTOFILL_MAX_VISIBLE_DATASETS: + setMaxVisibleDatasetsFromSettings(); + break; + default: + synchronized (mLock) { + updateCachedServiceLocked(userId); + } } } } diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java index f7b7ceb4a6da..522280e4690b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java @@ -185,7 +185,7 @@ public final class AutofillManagerServiceShellCommand extends ShellCommand { mService.setLogLevel(AutofillManager.FLAG_ADD_CLIENT_DEBUG); return 0; case "off": - mService.setLogLevel(0); + mService.setLogLevel(AutofillManager.NO_LOGGING); return 0; default: pw.println("Invalid level: " + logLevel); diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java index 4f45a7770311..420c2be008cb 100644 --- a/services/autofill/java/com/android/server/autofill/Helper.java +++ b/services/autofill/java/com/android/server/autofill/Helper.java @@ -28,6 +28,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.WindowManager; import android.view.autofill.AutofillId; +import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -43,28 +44,32 @@ public final class Helper { /** * Defines a logging flag that can be dynamically changed at runtime using - * {@code cmd autofill set log_level debug}. + * {@code cmd autofill set log_level debug} or through + * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}. */ public static boolean sDebug = false; /** * Defines a logging flag that can be dynamically changed at runtime using - * {@code cmd autofill set log_level verbose}. + * {@code cmd autofill set log_level verbose} or through + * {@link android.provider.Settings.Global#AUTOFILL_LOGGING_LEVEL}. */ public static boolean sVerbose = false; /** * Maximum number of partitions that can be allowed in a session. * - * <p>Can be modified using {@code cmd autofill set max_partitions}. + * <p>Can be modified using {@code cmd autofill set max_partitions} or through + * {@link android.provider.Settings.Global#AUTOFILL_MAX_PARTITIONS_SIZE}. */ - static int sPartitionMaxCount = 10; + static int sPartitionMaxCount = AutofillManager.DEFAULT_MAX_PARTITIONS_SIZE; /** * Maximum number of visible datasets in the dataset picker UI, or {@code 0} to use default * value from resources. * - * <p>Can be modified using {@code cmd autofill set max_visible_datasets}. + * <p>Can be modified using {@code cmd autofill set max_visible_datasets} or through + * {@link android.provider.Settings.Global#AUTOFILL_MAX_VISIBLE_DATASETS}. */ public static int sVisibleDatasetsMaxCount = 0; diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 8f59ca9c4b8d..48c2eaef02db 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -37,7 +37,6 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; -import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IAssistDataReceiver; import android.app.assist.AssistStructure; diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index fd746131862f..784dfb4d0cec 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -51,6 +51,7 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.Manifest; +import android.accessibilityservice.AccessibilityService; import android.annotation.AnyThread; import android.annotation.BinderThread; import android.annotation.ColorInt; @@ -888,10 +889,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (showImeUri.equals(uri)) { updateKeyboardFromSettingsLocked(); } else if (accessibilityRequestingNoImeUri.equals(uri)) { - mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser( + final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, - 0, mUserId) == 1; + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId); + mAccessibilityRequestingNoSoftKeyboard = + (accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK) + == AccessibilityService.SHOW_MODE_HIDDEN; if (mAccessibilityRequestingNoSoftKeyboard) { final boolean showRequested = mShowRequested; hideCurrentInputLocked(0, null); diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 29937ab42d15..5bf6892adb23 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -3658,7 +3658,7 @@ public final class ActiveServices { } app = r.app; - if (app != null && app.debugging) { + if (app != null && app.isDebugging()) { // The app's being debugged; let it ride return; } diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java index f166e64fd881..73ffd5cc4ff4 100644 --- a/services/core/java/com/android/server/am/ActivityDisplay.java +++ b/services/core/java/com/android/server/am/ActivityDisplay.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; @@ -31,13 +32,18 @@ import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; import static com.android.server.am.ActivityDisplayProto.CONFIGURATION_CONTAINER; +import static com.android.server.am.ActivityDisplayProto.FOCUSED_STACK_ID; import static com.android.server.am.ActivityDisplayProto.ID; +import static com.android.server.am.ActivityDisplayProto.RESUMED_ACTIVITY; import static com.android.server.am.ActivityDisplayProto.STACKS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityStackSupervisor.TAG_STATES; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.WindowConfiguration; @@ -319,16 +325,6 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> + windowingMode); } - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - // TODO: Should be okay to have stacks with with undefined windowing mode long term, but - // have to set them to something for now due to logic that depending on them. - windowingMode = getWindowingMode(); // Put in current display's windowing mode - if (windowingMode == WINDOWING_MODE_UNDEFINED) { - // Else fullscreen for now... - windowingMode = WINDOWING_MODE_FULLSCREEN; - } - } - final int stackId = getNextStackId(); return createStackUnchecked(windowingMode, activityType, stackId, onTop); } @@ -354,6 +350,45 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> return null; } + ActivityStack getNextFocusableStack() { + return getNextFocusableStack(null /* currentFocus */, false /* ignoreCurrent */); + } + + ActivityStack getNextFocusableStack(ActivityStack currentFocus, boolean ignoreCurrent) { + final int currentWindowingMode = currentFocus != null + ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED; + + ActivityStack candidate = null; + for (int i = mStacks.size() - 1; i >= 0; --i) { + final ActivityStack stack = mStacks.get(i); + if (ignoreCurrent && stack == currentFocus) { + continue; + } + if (!stack.isFocusable() || !stack.shouldBeVisible(null)) { + continue; + } + + if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY + && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) { + // If the currently focused stack is in split-screen secondary we save off the + // top primary split-screen stack as a candidate for focus because we might + // prefer focus to move to an other stack to avoid primary split-screen stack + // overlapping with a fullscreen stack when a fullscreen stack is higher in z + // than the next split-screen stack. Assistant stack, I am looking at you... + // We only move the focus to the primary-split screen stack if there isn't a + // better alternative. + candidate = stack; + continue; + } + if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) { + // Use the candidate stack since we are now at the secondary split-screen. + return candidate; + } + return stack; + } + return candidate; + } + ActivityRecord getResumedActivity() { final ActivityStack focusedStack = getFocusedStack(); if (focusedStack == null) { @@ -376,6 +411,30 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> } /** + * Pause all activities in either all of the stacks or just the back stacks. + * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). + * @param resuming The resuming activity. + * @param dontWait The resuming activity isn't going to wait for all activities to be paused + * before resuming. + * @return true if any activity was paused as a result of this call. + */ + boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { + boolean someActivityPaused = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + // TODO(b/111541062): Check if resumed activity on this display instead + if (!mSupervisor.isTopDisplayFocusedStack(stack) + && stack.getResumedActivity() != null) { + if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + + " mResumedActivity=" + stack.getResumedActivity()); + someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, + dontWait); + } + } + return someActivityPaused; + } + + /** * Removes stacks in the input windowing modes from the system if they are of activity type * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED */ @@ -582,7 +641,21 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> windowingMode = getWindowingMode(); } } + return validateWindowingMode(windowingMode, r, task, activityType); + } + /** + * Check that the requested windowing-mode is appropriate for the specified task and/or activity + * on this display. + * + * @param windowingMode The windowing-mode to validate. + * @param r The {@link ActivityRecord} to check against. + * @param task The {@link TaskRecord} to check against. + * @param activityType An activity type. + * @return The provided windowingMode or the closest valid mode which is appropriate. + */ + int validateWindowingMode(int windowingMode, @Nullable ActivityRecord r, + @Nullable TaskRecord task, int activityType) { // Make sure the windowing mode we are trying to use makes sense for what is supported. final ActivityTaskManagerService service = mSupervisor.mService; boolean supportsMultiWindow = service.mSupportsMultiWindow; @@ -902,6 +975,16 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); proto.write(ID, mDisplayId); + final ActivityStack focusedStack = getFocusedStack(); + if (focusedStack != null) { + proto.write(FOCUSED_STACK_ID, focusedStack.mStackId); + final ActivityRecord focusedActivity = focusedStack.getDisplay().getResumedActivity(); + if (focusedActivity != null) { + focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY); + } + } else { + proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID); + } for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = mStacks.get(stackNdx); stack.writeToProto(proto, STACKS); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3a7c00cbb141..c64dfe0a4052 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -463,12 +463,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int BROADCAST_FG_TIMEOUT = 10*1000; static final int BROADCAST_BG_TIMEOUT = 60*1000; - // How long we wait until we timeout on key dispatching. - static final int KEY_DISPATCHING_TIMEOUT = 5*1000; - - // How long we wait until we timeout on key dispatching during instrumentation. - static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000; - // Disable hidden API checks for the newly started instrumentation. // Must be kept in sync with Am. private static final int INSTRUMENTATION_FLAG_DISABLE_HIDDEN_API_CHECKS = 1 << 0; @@ -574,8 +568,6 @@ public class ActivityManagerService extends IActivityManager.Stub final AppErrors mAppErrors; - final AppWarnings mAppWarnings; - /** * Dump of the activity state at the time of the last ANR. Cleared after * {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS} @@ -695,10 +687,42 @@ public class ActivityManagerService extends IActivityManager.Stub * All of the processes we currently have running organized by pid. * The keys are the pid running the application. * - * <p>NOTE: This object is protected by its own lock, NOT the global - * activity manager lock! + * <p>NOTE: This object is protected by its own lock, NOT the global activity manager lock! */ - final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>(); + final PidMap mPidsSelfLocked = new PidMap(); + final class PidMap { + private final SparseArray<ProcessRecord> mPidMap = new SparseArray<>(); + + void put(int key, ProcessRecord value) { + mPidMap.put(key, value); + mAtmInternal.onProcessMapped(key, value.getWindowProcessController()); + } + + void remove(int pid) { + mPidMap.remove(pid); + mAtmInternal.onProcessUnMapped(pid); + } + + ProcessRecord get(int pid) { + return mPidMap.get(pid); + } + + int size() { + return mPidMap.size(); + } + + ProcessRecord valueAt(int index) { + return mPidMap.valueAt(index); + } + + int keyAt(int index) { + return mPidMap.keyAt(index); + } + + int indexOfKey(int key) { + return mPidMap.indexOfKey(key); + } + } /** * All of the processes that have been forced to be important. The key @@ -1425,7 +1449,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int CHECK_EXCESSIVE_POWER_USE_MSG = 27; static final int CLEAR_DNS_CACHE_MSG = 28; static final int UPDATE_HTTP_PROXY_MSG = 29; - static final int SHOW_COMPAT_MODE_DIALOG_UI_MSG = 30; static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31; static final int DISPATCH_PROCESS_DIED_UI_MSG = 32; static final int REPORT_MEM_USAGE_MSG = 33; @@ -1456,7 +1479,6 @@ public class ActivityManagerService extends IActivityManager.Stub static ServiceThread sKillThread = null; static KillHandler sKillHandler = null; - CompatModeDialog mCompatModeDialog; long mLastMemUsageReportTime = 0; /** @@ -1587,34 +1609,6 @@ public class ActivityManagerService extends IActivityManager.Stub } } } break; - case SHOW_COMPAT_MODE_DIALOG_UI_MSG: { - synchronized (ActivityManagerService.this) { - ActivityRecord ar = (ActivityRecord) msg.obj; - if (mCompatModeDialog != null) { - if (mCompatModeDialog.mAppInfo.packageName.equals( - ar.info.applicationInfo.packageName)) { - return; - } - mCompatModeDialog.dismiss(); - mCompatModeDialog = null; - } - if (ar != null && false) { - if (mCompatModePackages.getPackageAskCompatModeLocked( - ar.packageName)) { - int mode = mCompatModePackages.computeCompatModeLocked( - ar.info.applicationInfo); - if (mode == ActivityManager.COMPAT_MODE_DISABLED - || mode == ActivityManager.COMPAT_MODE_ENABLED) { - mCompatModeDialog = new CompatModeDialog( - ActivityManagerService.this, mUiContext, - ar.info.applicationInfo); - mCompatModeDialog.show(); - } - } - } - } - break; - } case DISPATCH_PROCESSES_CHANGED_UI_MSG: { dispatchProcessesChanged(); break; @@ -2433,7 +2427,6 @@ public class ActivityManagerService extends IActivityManager.Stub mUiContext = null; GL_ES_VERSION = 0; mAppErrors = null; - mAppWarnings = null; mAppOpsService = mInjector.getAppOpsService(null, null); mBatteryStatsService = null; mCompatModePackages = null; @@ -2500,8 +2493,6 @@ public class ActivityManagerService extends IActivityManager.Stub final File systemDir = SystemServiceManager.ensureSystemDir(); - mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir); - // TODO: Move creation of battery stats service outside of activity manager service. mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler); mBatteryStatsService.getActiveStatistics().readLocked(); @@ -2891,28 +2882,6 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.unregisterTaskStackListener(listener); } - final void showAskCompatModeDialogLocked(ActivityRecord r) { - Message msg = Message.obtain(); - msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG; - msg.obj = r.getTask().askedCompatMode ? null : r; - mUiHandler.sendMessage(msg); - } - - final AppWarnings getAppWarningsLocked() { - return mAppWarnings; - } - - /** - * Shows app warning dialogs, if necessary. - * - * @param r activity record for which the warnings may be displayed - */ - final void showAppWarningsIfNeededLocked(ActivityRecord r) { - mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r); - mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r); - mAppWarnings.showDeprecatedTargetDialogIfNeeded(r); - } - private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, String what, Object obj, ProcessRecord srcApp) { app.lastActivityTime = now; @@ -3635,8 +3604,8 @@ public class ActivityManagerService extends IActivityManager.Stub app.pendingStart = false; return; } - app.usingWrapper = invokeWith != null - || SystemProperties.get("wrap." + app.processName) != null; + app.setUsingWrapper(invokeWith != null + || SystemProperties.get("wrap." + app.processName) != null); mPendingStarts.put(startSeq, app); } final ProcessStartResult startResult = startProcess(app.hostingType, entryPoint, @@ -3688,13 +3657,13 @@ public class ActivityManagerService extends IActivityManager.Stub startResult = startWebView(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, - app.info.dataDir, null, + app.info.dataDir, null, app.info.packageName, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, - app.info.dataDir, invokeWith, + app.info.dataDir, invokeWith, app.info.packageName, new String[] {PROC_START_SEQ_IDENT + app.startSeq}); } checkTime(startTime, "startProcess: returned from zygote!"); @@ -3732,7 +3701,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Indicates that this process start has been taken care of. if (mPendingStarts.get(expectedStartSeq) == null) { if (pending.pid == startResult.pid) { - pending.usingWrapper = startResult.usingWrapper; + pending.setUsingWrapper(startResult.usingWrapper); // TODO: Update already existing clients of usingWrapper } return false; @@ -3795,7 +3764,7 @@ public class ActivityManagerService extends IActivityManager.Stub } reportUidInfoMessageLocked(TAG, buf.toString(), app.startUid); app.setPid(pid); - app.usingWrapper = usingWrapper; + app.setUsingWrapper(usingWrapper); app.pendingStart = false; checkTime(app.startTime, "startProcess: starting to update pids map"); ProcessRecord oldApp; @@ -3880,7 +3849,7 @@ public class ActivityManagerService extends IActivityManager.Stub aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, aInfo.applicationInfo.uid, true); - if (app == null || app.instr == null) { + if (app == null || app.getActiveInstrumentation() == null) { intent.setFlags(intent.getFlags() | FLAG_ACTIVITY_NEW_TASK); final int resolvedUserId = UserHandle.getUserId(aInfo.applicationInfo.uid); // For ANR debugging to verify if the user activity is the one that actually @@ -4400,9 +4369,9 @@ public class ActivityManagerService extends IActivityManager.Stub app.clearRecentTasks(); app.clearActivities(); - if (app.instr != null) { + if (app.getActiveInstrumentation() != null) { Slog.w(TAG, "Crash of app " + app.processName - + " running instrumentation " + app.instr.mClass); + + " running instrumentation " + app.getActiveInstrumentation().mClass); Bundle info = new Bundle(); info.putString("shortMsg", "Process crashed."); finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info); @@ -4411,7 +4380,7 @@ public class ActivityManagerService extends IActivityManager.Stub mWindowManager.deferSurfaceLayout(); try { if (!restarting && hasVisibleActivities - && !mStackSupervisor.resumeFocusedStackTopActivityLocked()) { + && !mStackSupervisor.resumeFocusedStacksTopActivitiesLocked()) { // If there was nothing to resume, and we are not already restarting this process, but // there is a visible activity that is hosted by the process... then make sure all // visible activities are running, taking care of restarting this process. @@ -4556,7 +4525,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { - boolean doLowMem = app.instr == null; + boolean doLowMem = app.getActiveInstrumentation() == null; boolean doOomAdj = doLowMem; if (!app.killedByAm) { reportUidInfoMessageLocked(TAG, @@ -5499,7 +5468,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Clean-up disabled activities. if (mStackSupervisor.finishDisabledPackageActivitiesLocked( packageName, disabledClasses, true, false, userId) && mBooted) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); mStackSupervisor.scheduleIdleLocked(); } @@ -5673,7 +5642,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (mBooted) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); mStackSupervisor.scheduleIdleLocked(); } } @@ -5885,7 +5854,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (app == null && startSeq > 0) { final ProcessRecord pending = mPendingStarts.get(startSeq); if (pending != null && pending.startUid == callingUid - && handleProcessStartedLocked(pending, pid, pending.usingWrapper, + && handleProcessStartedLocked(pending, pid, pending.isUsingWrapper(), startSeq, true)) { app = pending; } @@ -5939,7 +5908,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.forcingToImportant = null; updateProcessForegroundLocked(app, false, false); app.hasShownUi = false; - app.debugging = false; + app.setDebugging(false); app.cached = false; app.killedByAm = false; app.killed = false; @@ -5976,7 +5945,7 @@ public class ActivityManagerService extends IActivityManager.Stub testMode = mWaitForDebugger ? ApplicationThreadConstants.DEBUG_WAIT : ApplicationThreadConstants.DEBUG_ON; - app.debugging = true; + app.setDebugging(true); if (mDebugTransient) { mDebugApp = mOrigDebugApp; mWaitForDebugger = mOrigWaitForDebugger; @@ -5998,13 +5967,15 @@ public class ActivityManagerService extends IActivityManager.Stub || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL)); } - if (app.instr != null) { - notifyPackageUse(app.instr.mClass.getPackageName(), + final ActiveInstrumentation instr = app.getActiveInstrumentation(); + + if (instr != null) { + notifyPackageUse(instr.mClass.getPackageName(), PackageManager.NOTIFY_PACKAGE_USE_INSTRUMENTATION); } if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Binding proc " + processName + " with config " + getGlobalConfiguration()); - ApplicationInfo appInfo = app.instr != null ? app.instr.mTargetInfo : app.info; + ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info; app.compat = compatibilityInfoForPackageLocked(appInfo); ProfilerInfo profilerInfo = null; @@ -6021,8 +5992,8 @@ public class ActivityManagerService extends IActivityManager.Stub preBindAgent = mProfilerInfo.agent; } } - } else if (app.instr != null && app.instr.mProfileFile != null) { - profilerInfo = new ProfilerInfo(app.instr.mProfileFile, null, 0, false, false, + } else if (instr != null && instr.mProfileFile != null) { + profilerInfo = new ProfilerInfo(instr.mProfileFile, null, 0, false, false, null, false); } if (mAppAgentMap != null && mAppAgentMap.containsKey(processName)) { @@ -6059,21 +6030,22 @@ public class ActivityManagerService extends IActivityManager.Stub // currently active instrumentation. (Note we do this AFTER all of the profiling // stuff above because profiling can currently happen only in the primary // instrumentation process.) - if (mActiveInstrumentation.size() > 0 && app.instr == null) { - for (int i = mActiveInstrumentation.size() - 1; i >= 0 && app.instr == null; i--) { + if (mActiveInstrumentation.size() > 0 && instr == null) { + for (int i = mActiveInstrumentation.size() - 1; + i >= 0 && app.getActiveInstrumentation() == null; i--) { ActiveInstrumentation aInstr = mActiveInstrumentation.get(i); if (!aInstr.mFinished && aInstr.mTargetInfo.uid == app.uid) { if (aInstr.mTargetProcesses.length == 0) { // This is the wildcard mode, where every process brought up for // the target instrumentation should be included. if (aInstr.mTargetInfo.packageName.equals(app.info.packageName)) { - app.instr = aInstr; + app.setActiveInstrumentation(aInstr); aInstr.mRunningProcesses.add(app); } } else { for (String proc : aInstr.mTargetProcesses) { if (proc.equals(app.processName)) { - app.instr = aInstr; + app.setActiveInstrumentation(aInstr); aInstr.mRunningProcesses.add(app); break; } @@ -6103,16 +6075,17 @@ public class ActivityManagerService extends IActivityManager.Stub checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); mStackSupervisor.getActivityMetricsLogger().notifyBindApplication(app); + final ActiveInstrumentation instr2 = app.getActiveInstrumentation(); if (app.isolatedEntryPoint != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. thread.runIsolatedEntryPoint(app.isolatedEntryPoint, app.isolatedEntryPointArgs); - } else if (app.instr != null) { + } else if (instr2 != null) { thread.bindApplication(processName, appInfo, providers, - app.instr.mClass, - profilerInfo, app.instr.mArguments, - app.instr.mWatcher, - app.instr.mUiAutomationConnection, testMode, + instr2.mClass, + profilerInfo, instr2.mArguments, + instr2.mWatcher, + instr2.mUiAutomationConnection, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || !normalMode, app.isPersistent(), new Configuration(getGlobalConfiguration()), app.compat, @@ -9257,7 +9230,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (proc == null) { throw new SecurityException("Unknown process: " + callingPid); } - if (proc.instr == null || proc.instr.mUiAutomationConnection == null) { + if (proc.getActiveInstrumentation() == null + || proc.getActiveInstrumentation().mUiAutomationConnection == null) { throw new SecurityException("Only an instrumentation process " + "with a UiAutomation can call setUserIsMonkey"); } @@ -9380,89 +9354,6 @@ public class ActivityManagerService extends IActivityManager.Stub ActivityManager.BUGREPORT_OPTION_WIFI); } - - public static long getInputDispatchingTimeoutLocked(ActivityRecord r) { - if (r == null || !r.hasProcess()) { - return KEY_DISPATCHING_TIMEOUT; - } - return getInputDispatchingTimeoutLocked((ProcessRecord) r.app.mOwner); - } - - public static long getInputDispatchingTimeoutLocked(ProcessRecord r) { - if (r != null && (r.instr != null || r.usingWrapper)) { - return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT; - } - return KEY_DISPATCHING_TIMEOUT; - } - - @Override - public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { - if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires permission " - + android.Manifest.permission.FILTER_EVENTS); - } - ProcessRecord proc; - long timeout; - synchronized (this) { - synchronized (mPidsSelfLocked) { - proc = mPidsSelfLocked.get(pid); - } - timeout = getInputDispatchingTimeoutLocked(proc); - } - - if (inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { - return -1; - } - - return timeout; - } - - /** - * Handle input dispatching timeouts. - * Returns whether input dispatching should be aborted or not. - */ - public boolean inputDispatchingTimedOut(final ProcessRecord proc, - final ActivityRecord activity, final ActivityRecord parent, - final boolean aboveSystem, String reason) { - if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Requires permission " - + android.Manifest.permission.FILTER_EVENTS); - } - - final String annotation; - if (reason == null) { - annotation = "Input dispatching timed out"; - } else { - annotation = "Input dispatching timed out (" + reason + ")"; - } - - if (proc != null) { - synchronized (this) { - if (proc.debugging) { - return false; - } - - if (proc.instr != null) { - Bundle info = new Bundle(); - info.putString("shortMsg", "keyDispatchingTimedOut"); - info.putString("longMsg", annotation); - finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info); - return true; - } - } - mHandler.post(new Runnable() { - @Override - public void run() { - mAppErrors.appNotResponding(proc, activity, parent, aboveSystem, annotation); - } - }); - } - - return true; - } - public void registerProcessObserver(IProcessObserver observer) { enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, "registerProcessObserver()"); @@ -10307,7 +10198,7 @@ public class ActivityManagerService extends IActivityManager.Stub } finally { Binder.restoreCallingIdentity(ident); } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); mUserController.sendUserSwitchBroadcasts(-1, currentUserId); BinderInternal.nSetBinderProxyCountWatermarks(6000,5500); @@ -10406,6 +10297,15 @@ public class ActivityManagerService extends IActivityManager.Stub : StatsLog.APP_CRASH_OCCURRED__FOREGROUND_STATE__UNKNOWN ); + final int relaunchReason = r == null ? ActivityRecord.RELAUNCH_REASON_NONE + : r.getWindowProcessController().computeRelaunchReason(); + final String relaunchReasonString = ActivityRecord.relaunchReasonToString(relaunchReason); + if (crashInfo.crashTag == null) { + crashInfo.crashTag = relaunchReasonString; + } else { + crashInfo.crashTag = crashInfo.crashTag + " " + relaunchReasonString; + } + addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo); mAppErrors.crashApplication(r, crashInfo); @@ -10732,6 +10632,9 @@ public class ActivityManagerService extends IActivityManager.Stub if (Debug.isDebuggerConnected()) { sb.append("Debugger: Connected\n"); } + if (crashInfo != null && crashInfo.crashTag != null && !crashInfo.crashTag.isEmpty()) { + sb.append("Crash-Tag: ").append(crashInfo.crashTag).append("\n"); + } sb.append("\n"); // Do the rest in a worker thread to avoid blocking the caller on I/O @@ -16413,7 +16316,7 @@ public class ActivityManagerService extends IActivityManager.Stub mActivityTaskManager.getRecentTasks().removeTasksByPackageName(ssp, userId); mServices.forceStopPackageLocked(ssp, userId); - mAppWarnings.onPackageUninstalled(ssp); + mAtmInternal.onPackageUninstalled(ssp); mCompatModePackages.handlePackageUninstalledLocked(ssp); mBatteryStatsService.notePackageUninstalled(ssp); } @@ -16494,7 +16397,7 @@ public class ActivityManagerService extends IActivityManager.Stub String ssp; if (data != null && (ssp = data.getSchemeSpecificPart()) != null) { mCompatModePackages.handlePackageDataClearedLocked(ssp); - mAppWarnings.onPackageDataCleared(ssp); + mAtmInternal.onPackageDataCleared(ssp); } break; } @@ -17125,7 +17028,7 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessRecord app = addAppLocked(ai, defProcess, false, disableHiddenApiChecks, abiOverride); - app.instr = activeInstr; + app.setActiveInstrumentation(activeInstr); activeInstr.mFinished = false; activeInstr.mRunningProcesses.add(app); if (!mActiveInstrumentation.contains(activeInstr)) { @@ -17158,16 +17061,17 @@ public class ActivityManagerService extends IActivityManager.Stub } void addInstrumentationResultsLocked(ProcessRecord app, Bundle results) { - if (app.instr == null) { + final ActiveInstrumentation instr = app.getActiveInstrumentation(); + if (instr == null) { Slog.w(TAG, "finishInstrumentation called on non-instrumented: " + app); return; } - if (!app.instr.mFinished && results != null) { - if (app.instr.mCurResults == null) { - app.instr.mCurResults = new Bundle(results); + if (!instr.mFinished && results != null) { + if (instr.mCurResults == null) { + instr.mCurResults = new Bundle(results); } else { - app.instr.mCurResults.putAll(results); + instr.mCurResults.putAll(results); } } } @@ -17193,37 +17097,38 @@ public class ActivityManagerService extends IActivityManager.Stub @GuardedBy("this") void finishInstrumentationLocked(ProcessRecord app, int resultCode, Bundle results) { - if (app.instr == null) { + final ActiveInstrumentation instr = app.getActiveInstrumentation(); + if (instr == null) { Slog.w(TAG, "finishInstrumentation called on non-instrumented: " + app); return; } - if (!app.instr.mFinished) { - if (app.instr.mWatcher != null) { - Bundle finalResults = app.instr.mCurResults; + if (!instr.mFinished) { + if (instr.mWatcher != null) { + Bundle finalResults = instr.mCurResults; if (finalResults != null) { - if (app.instr.mCurResults != null && results != null) { + if (instr.mCurResults != null && results != null) { finalResults.putAll(results); } } else { finalResults = results; } - mInstrumentationReporter.reportFinished(app.instr.mWatcher, - app.instr.mClass, resultCode, finalResults); + mInstrumentationReporter.reportFinished(instr.mWatcher, + instr.mClass, resultCode, finalResults); } // Can't call out of the system process with a lock held, so post a message. - if (app.instr.mUiAutomationConnection != null) { + if (instr.mUiAutomationConnection != null) { mAppOpsService.setAppOpsServiceDelegate(null); getPackageManagerInternalLocked().setCheckPermissionDelegate(null); mHandler.obtainMessage(SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG, - app.instr.mUiAutomationConnection).sendToTarget(); + instr.mUiAutomationConnection).sendToTarget(); } - app.instr.mFinished = true; + instr.mFinished = true; } - app.instr.removeProcess(app); - app.instr = null; + instr.removeProcess(app); + app.setActiveInstrumentation(null); forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false, app.userId, "finished inst"); @@ -17697,7 +17602,7 @@ public class ActivityManagerService extends IActivityManager.Stub if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app); } - } else if (app.instr != null) { + } else if (app.getActiveInstrumentation() != null) { // Don't want to kill running instrumentation. adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; @@ -18332,8 +18237,10 @@ public class ActivityManagerService extends IActivityManager.Stub } if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; - reportOomAdjMessageLocked(TAG_OOM_ADJ, - "Raise procstate to external provider: " + app); + if (DEBUG_OOM_ADJ_REASON || logUid == appUid) { + reportOomAdjMessageLocked(TAG_OOM_ADJ, + "Raise procstate to external provider: " + app); + } } } } @@ -21194,6 +21101,13 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManagerService.this.getHomeIntent(); } } + + @Override + public void scheduleAppGcs() { + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.scheduleAppGcsLocked(); + } + } } /** diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 68e70a9634d1..67abedc96eed 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -339,6 +339,17 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo int mStartingWindowState = STARTING_WINDOW_NOT_SHOWN; boolean mTaskOverlay = false; // Task is always on-top of other activities in the task. + // This activity is not being relaunched, or being relaunched for a non-resize reason. + static final int RELAUNCH_REASON_NONE = 0; + // This activity is being relaunched due to windowing mode change. + static final int RELAUNCH_REASON_WINDOWING_MODE_RESIZE = 1; + // This activity is being relaunched due to a free-resize operation. + static final int RELAUNCH_REASON_FREE_RESIZE = 2; + // Marking the reason why this activity is being relaunched. Mainly used to track that this + // activity is being relaunched to fulfill a resize request due to compatibility issues, e.g. in + // pre-NYC apps that don't have a sense of being resized. + int mRelaunchReason = RELAUNCH_REASON_NONE; + TaskDescription taskDescription; // the recents information for this activity boolean mLaunchTaskBehind; // this activity is actively being launched with // ActivityOptions.setLaunchTaskBehind, will be cleared once launch is completed. @@ -1025,7 +1036,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges, task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(), appInfo.targetSdkVersion, mRotationAnimationHint, - ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L); + ActivityTaskManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L); task.addActivityToTop(this); @@ -1930,7 +1941,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } else { if (deferRelaunchUntilPaused) { stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config"); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } else { mStackSupervisor.updatePreviousProcessLocked(this); } @@ -2123,7 +2134,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mStackSupervisor.processStoppingActivitiesLocked(null /* idleActivity */, false /* remove */, true /* processPausingActivities */); } - service.mAm.scheduleAppGcsLocked(); + service.scheduleAppGcsLocked(); } } } @@ -2148,13 +2159,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo !hasProcess() || app.getPid() == windowPid || windowPid == -1; } if (windowFromSameProcessAsActivity) { - return service.mAm.inputDispatchingTimedOut( - (ProcessRecord) anrApp.mOwner, anrActivity, this, false, reason); + return service.inputDispatchingTimedOut(anrApp, anrActivity, this, false, reason); } else { // In this case another process added windows using this activity token. So, we call the // generic service input dispatch timed out method so that the right process is blamed. - return service.mAm.inputDispatchingTimedOut( - windowPid, false /* aboveSystem */, reason) < 0; + return service.inputDispatchingTimedOut(windowPid, false /* aboveSystem */, reason) < 0; } } @@ -2353,7 +2362,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo frozenBeforeDestroy = true; if (!service.updateDisplayOverrideConfigurationLocked(config, this, false /* deferResume */, displayId)) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } service.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged( @@ -2617,6 +2626,15 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo startFreezingScreenLocked(app, globalChanges); forceNewConfig = false; preserveWindow &= isResizeOnlyChange(changes); + final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged()); + if (hasResizeChange) { + final boolean isDragResizing = + getTask().getWindowContainerController().isDragResizing(); + mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE + : RELAUNCH_REASON_WINDOWING_MODE_RESIZE; + } else { + mRelaunchReason = RELAUNCH_REASON_NONE; + } if (!attachedToProcess()) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is destroying non-running " + this); @@ -2738,6 +2756,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo | CONFIG_SCREEN_LAYOUT)) == 0; } + private static boolean hasResizeChange(int change) { + return (change & (CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_ORIENTATION + | CONFIG_SCREEN_LAYOUT)) != 0; + } + void relaunchActivityLocked(boolean andResume, boolean preserveWindow) { if (service.mSuppressResizeConfigChanges && preserveWindow) { configChangeFlags = 0; @@ -2794,10 +2817,12 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } results = null; newIntents = null; - service.mAm.getAppWarningsLocked().onResumeActivity(this); - service.mAm.showAskCompatModeDialogLocked(this); + service.getAppWarningsLocked().onResumeActivity(this); } else { - service.mAm.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); + final ActivityStack stack = getStack(); + if (stack != null) { + stack.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); + } setState(PAUSED, "relaunchActivityLocked"); } @@ -3017,6 +3042,17 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mWindowContainerController.registerRemoteAnimations(definition); } + static String relaunchReasonToString(int relaunchReason) { + switch (relaunchReason) { + case RELAUNCH_REASON_WINDOWING_MODE_RESIZE: + return "window_resize"; + case RELAUNCH_REASON_FREE_RESIZE: + return "free_resize"; + default: + return null; + } + } + @Override public String toString() { if (stringName != null) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 68a6b5afe71d..19b62e718164 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -72,6 +72,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAV import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityRecord.RELAUNCH_REASON_FREE_RESIZE; +import static com.android.server.am.ActivityRecord.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; import static com.android.server.am.ActivityStack.ActivityState.FINISHING; @@ -495,7 +497,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_STACK) Slog.v(TAG_STACK, "set resumed activity to:" + record + " reason:" + reason); setResumedActivity(record, reason + " - onActivityStateChanged"); - mService.setResumedActivityUncheckLocked(record, reason); + if (record == mStackSupervisor.getTopResumedActivity()) { + // TODO(b/111541062): Support tracking multiple resumed activities + mService.setResumedActivityUncheckLocked(record, reason); + } mStackSupervisor.mRecentTasks.add(record.getTask()); } } @@ -535,15 +540,17 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final ActivityStack splitScreenStack = display.getSplitScreenPrimaryStack(); mTmpOptions.setLaunchWindowingMode(preferredWindowingMode); + int windowingMode = preferredWindowingMode; // Need to make sure windowing mode is supported. If we in the process of creating the stack // no need to resolve the windowing mode again as it is already resolved to the right mode. - int windowingMode = creating - ? preferredWindowingMode - : display.resolveWindowingMode( - null /* ActivityRecord */, mTmpOptions, topTask, getActivityType()); - if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { - // Resolution to split-screen secondary for the primary split-screen stack means we want - // to go fullscreen. + if (!creating) { + windowingMode = display.validateWindowingMode(windowingMode, + null /* ActivityRecord */, topTask, getActivityType()); + } + if (splitScreenStack == this + && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) { + // Resolution to split-screen secondary for the primary split-screen stack means + // we want to go fullscreen. windowingMode = WINDOWING_MODE_FULLSCREEN; } @@ -593,6 +600,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.mNoAnimActivities.add(topActivity); } super.setWindowingMode(windowingMode); + windowingMode = getWindowingMode(); if (creating) { // Nothing else to do if we don't have a window container yet. E.g. call from ctor. @@ -619,15 +627,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mTmpRect2.setEmpty(); if (windowingMode != WINDOWING_MODE_FULLSCREEN) { mWindowContainerController.getRawBounds(mTmpRect2); - if (windowingMode == WINDOWING_MODE_FREEFORM) { - if (topTask != null) { - // TODO: Can we consolidate this and other sites that call this methods? - Rect bounds = topTask().getLaunchBounds(); - if (bounds != null) { - mTmpRect2.set(bounds); - } - } - } } if (!Objects.equals(getOverrideBounds(), mTmpRect2)) { @@ -661,7 +660,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!deferEnsuringVisibility) { mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -694,7 +693,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mWindowContainerController.reparent(activityDisplay.mDisplayId, mTmpRect2, onTop); postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop); adjustFocusToNextFocusableStack("reparent", true /* allowFocusSelf */); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); // Update visibility of activities before notifying WM. This way it won't try to resize // windows that are no longer visible. mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */, 0 /* configChanges */, @@ -1443,7 +1442,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (prev == null) { if (resuming == null) { Slog.wtf(TAG, "Trying to pause when nothing is resumed"); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } return false; } @@ -1523,7 +1522,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // pause, so just treat it as being paused now. if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Activity not running, resuming next."); if (resuming == null) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } return false; } @@ -1617,7 +1616,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (resumeNext) { final ActivityStack topStack = mStackSupervisor.getTopDisplayFocusedStack(); if (!topStack.shouldSleepOrShutDownActivities()) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(topStack, prev, null); } else { checkReadyForSleep(); ActivityRecord top = topStack.topRunningActivityLocked(); @@ -1626,7 +1625,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // something. Also if the top activity on the stack is not the just paused // activity, we need to go ahead and resume it to ensure we complete an // in-flight app switch. - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } } @@ -2305,7 +2304,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai * * NOTE: It is not safe to call this method directly as it can cause an activity in a * non-focused stack to be resumed. - * Use {@link ActivityStackSupervisor#resumeFocusedStackTopActivityLocked} to resume the + * Use {@link ActivityStackSupervisor#resumeFocusedStacksTopActivitiesLocked} to resume the * right activity for the current system state. */ @GuardedBy("mService") @@ -2468,7 +2467,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0 && !lastResumedCanPip; - boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false); + boolean pausing = getDisplay().pauseBackStacks(userLeaving, next, false); if (mResumedActivity != null) { if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Pausing " + mResumedActivity); @@ -2657,7 +2656,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // the screen based on the new activity order. boolean notUpdated = true; - if (mStackSupervisor.isTopDisplayFocusedStack(this)) { + if (isFocusedStackOnDisplay()) { // We have special rotation behavior when here is some active activity that // requests specific orientation or Keyguard is locked. Make sure all activity // visibilities are set correctly as well as the transition is updated if needed @@ -2720,8 +2719,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.shortComponentName); next.sleeping = false; - mService.mAm.getAppWarningsLocked().onResumeActivity(next); - mService.mAm.showAskCompatModeDialogLocked(next); + mService.getAppWarningsLocked().onResumeActivity(next); next.app.setPendingUiCleanAndForceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); transaction.setLifecycleStateRequest( @@ -2790,12 +2788,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private boolean resumeTopActivityInNextFocusableStack(ActivityRecord prev, ActivityOptions options, String reason) { - if (adjustFocusToNextFocusableStack(reason)) { + final ActivityStack nextFocusedStack = adjustFocusToNextFocusableStack(reason); + if (nextFocusedStack != null) { // Try to move focus to the next visible stack with a running activity if this // stack is not covering the entire screen or is on a secondary display (with no home // stack). - return mStackSupervisor.resumeFocusedStackTopActivityLocked( - mStackSupervisor.getTopDisplayFocusedStack(), prev, null); + return mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(nextFocusedStack, prev, + null /* targetOptions */); } // Let's just start up the Launcher... @@ -2808,20 +2807,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.resumeHomeStackTask(prev, reason); } - private TaskRecord getNextTask(TaskRecord targetTask) { - final int index = mTaskHistory.indexOf(targetTask); - if (index >= 0) { - final int numTasks = mTaskHistory.size(); - for (int i = index + 1; i < numTasks; ++i) { - TaskRecord task = mTaskHistory.get(i); - if (task.userId == targetTask.userId) { - return task; - } - } - } - return null; - } - /** Returns the position the input task should be placed in this stack. */ int getAdjustedPositionForTask(TaskRecord task, int suggestedPosition, ActivityRecord starting) { @@ -3436,7 +3421,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } // Move focus to next focusable stack if possible. - if (adjustFocusToNextFocusableStack(myReason)) { + if (adjustFocusToNextFocusableStack(myReason) != null) { return; } @@ -3444,21 +3429,25 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mStackSupervisor.moveHomeStackTaskToTop(myReason); } - /** Find next proper focusable stack and make it focused. */ - boolean adjustFocusToNextFocusableStack(String reason) { + /** + * Find next proper focusable stack and make it focused. + * @return The stack that now got the focus, {@code null} if none found. + */ + ActivityStack adjustFocusToNextFocusableStack(String reason) { return adjustFocusToNextFocusableStack(reason, false /* allowFocusSelf */); } /** * Find next proper focusable stack and make it focused. * @param allowFocusSelf Is the focus allowed to remain on the same stack. + * @return The stack that now got the focus, {@code null} if none found. */ - private boolean adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) { + private ActivityStack adjustFocusToNextFocusableStack(String reason, boolean allowFocusSelf) { final ActivityStack stack = mStackSupervisor.getNextFocusableStackLocked(this, !allowFocusSelf); final String myReason = reason + " adjustFocusToNextFocusableStack"; if (stack == null) { - return false; + return null; } final ActivityRecord top = stack.topRunningActivityLocked(); @@ -3466,11 +3455,12 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (stack.isActivityTypeHome() && (top == null || !top.visible)) { // If we will be focusing on the home stack next and its current top activity isn't // visible, then use the move the home stack task to top to make the activity visible. - return mStackSupervisor.moveHomeStackTaskToTop(reason); + mStackSupervisor.moveHomeStackTaskToTop(reason); + return stack; } stack.moveToFront(myReason); - return true; + return stack; } final void stopActivityLocked(ActivityRecord r) { @@ -3876,7 +3866,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai false /* markFrozenIfConfigChanged */, true /* deferResume */); } if (activityRemoved) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "destroyActivityLocked: finishCurrentActivityLocked r=" + r + @@ -3889,7 +3879,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (DEBUG_ALL) Slog.v(TAG, "Enqueueing pending finish: " + r); mStackSupervisor.mFinishingActivities.add(r); r.resumeKeyDispatchingLocked(); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); return r; } @@ -4235,7 +4225,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } if (activityRemoved) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -4430,7 +4420,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list, @@ -4483,7 +4473,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai hasVisibleActivities = true; } final boolean remove; - if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { + if ((r.mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE + || r.mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE) + && r.launchCount < 3 && !r.finishing) { + // If the process crashed during a resize, always try to relaunch it, unless + // it has failed more than twice. Skip activities that's already finishing + // cleanly by itself. + remove = false; + } else if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { // Don't currently have state for the activity, or // it is finishing -- always remove it. remove = true; @@ -4656,7 +4653,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai topActivity.supportsEnterPipOnTaskSwitch = true; } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId); mService.getTaskChangeNotificationController().notifyTaskMovedToFront(tr.taskId); @@ -4727,7 +4724,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai return true; } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); return true; } @@ -4774,7 +4771,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (updatedConfig) { // Ensure the resumed state of the focus activity if we updated the configuration of // any activity. - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } @@ -4809,9 +4806,11 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // For freeform stack we don't adjust the size of the tasks to match that // of the stack, but we do try to make sure the tasks are still contained // with the bounds of the stack. - mTmpRect2.set(task.getOverrideBounds()); - fitWithinBounds(mTmpRect2, bounds); - task.updateOverrideConfiguration(mTmpRect2); + if (task.getOverrideBounds() != null) { + mTmpRect2.set(task.getOverrideBounds()); + fitWithinBounds(mTmpRect2, bounds); + task.updateOverrideConfiguration(mTmpRect2); + } } else { task.updateOverrideConfiguration(taskBounds, insetBounds); } @@ -5174,7 +5173,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP && mStackSupervisor.isTopDisplayFocusedStack(this)) { String myReason = reason + " leftTaskHistoryEmpty"; - if (!inMultiWindowMode() || !adjustFocusToNextFocusableStack(myReason)) { + if (!inMultiWindowMode() || adjustFocusToNextFocusableStack(myReason) == null) { mStackSupervisor.moveHomeStackToFront(myReason); } } @@ -5279,7 +5278,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // The task might have already been running and its visibility needs to be synchronized with // the visibility of the stack / windows. ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } private ActivityStack preAddTask(TaskRecord task, String reason, boolean toTop) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index b08efde051b7..d1585cf8fb48 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -17,10 +17,8 @@ package com.android.server.am; import static android.Manifest.permission.ACTIVITY_EMBEDDING; -import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.START_ANY_ACTIVITY; -import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED; import static android.app.ActivityManager.START_DELIVERED_TO_TOP; import static android.app.ActivityManager.START_TASK_TO_FRONT; @@ -74,8 +72,8 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.ActivityManagerService.ANIMATE; import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; +import static com.android.server.am.ActivityRecord.RELAUNCH_REASON_NONE; import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; -import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.am.ActivityStack.ActivityState.PAUSED; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; @@ -200,8 +198,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D private static final String TAG_RECENTS = TAG + POSTFIX_RECENTS; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; private static final String TAG_STACK = TAG + POSTFIX_STACK; - private static final String TAG_STATES = TAG + POSTFIX_STATES; private static final String TAG_SWITCH = TAG + POSTFIX_SWITCH; + static final String TAG_STATES = TAG + POSTFIX_STATES; static final String TAG_TASKS = TAG + POSTFIX_TASKS; /** How long we wait until giving up on the last activity telling us it is idle. */ @@ -820,7 +818,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Only resume home activity if isn't finishing. if (r != null && !r.finishing) { moveFocusableActivityStackToFrontLocked(r, myReason); - return resumeFocusedStackTopActivityLocked(mHomeStack, prev, null); + return resumeFocusedStacksTopActivitiesLocked(mHomeStack, prev, null); } return mService.mAm.startHomeActivityLocked(mCurrentUser, myReason); } @@ -1118,16 +1116,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D boolean pauseBackStacks(boolean userLeaving, ActivityRecord resuming, boolean dontWait) { boolean someActivityPaused = false; for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { - final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = display.getChildAt(stackNdx); - if (!isTopDisplayFocusedStack(stack) && stack.getResumedActivity() != null) { - if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack + - " mResumedActivity=" + stack.getResumedActivity()); - someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming, - dontWait); - } - } + someActivityPaused |= mActivityDisplays.valueAt(displayNdx) + .pauseBackStacks(userLeaving, resuming, dontWait); } return someActivityPaused; } @@ -1399,7 +1389,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // (e.g. AMS.startActivityAsUser). final long token = Binder.clearCallingIdentity(); try { - return mService.mAm.getPackageManagerInternalLocked().resolveIntent( + return mService.getPackageManagerInternalLocked().resolveIntent( intent, resolvedType, modifiedFlags, userId, true, filterCallingUid); } finally { Binder.restoreCallingIdentity(token); @@ -1522,8 +1512,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY); r.sleeping = false; r.forceNewConfig = false; - mService.mAm.getAppWarningsLocked().onStartActivity(r); - mService.mAm.showAskCompatModeDialogLocked(r); + mService.getAppWarningsLocked().onStartActivity(r); r.compat = mService.mAm.compatibilityInfoForPackageLocked(r.info.applicationInfo); ProfilerInfo profilerInfo = null; if (mService.mAm.mProfileApp != null && mService.mAm.mProfileApp.equals(app.processName)) { @@ -2100,11 +2089,15 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D if (isTopDisplayFocusedStack(r.getStack()) || fromTimeout) { booting = checkFinishBootingLocked(); } + + // When activity is idle, we consider the relaunch must be successful, so let's clear + // the flag. + r.mRelaunchReason = RELAUNCH_REASON_NONE; } if (allResumedActivitiesIdle()) { if (r != null) { - mService.mAm.scheduleAppGcsLocked(); + mService.scheduleAppGcsLocked(); } if (mLaunchingActivity.isHeld()) { @@ -2171,7 +2164,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D //mWindowManager.dump(); if (activityRemoved) { - resumeFocusedStackTopActivityLocked(); + resumeFocusedStacksTopActivitiesLocked(); } return r; @@ -2267,28 +2260,35 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - boolean resumeFocusedStackTopActivityLocked() { - return resumeFocusedStackTopActivityLocked(null, null, null); + boolean resumeFocusedStacksTopActivitiesLocked() { + return resumeFocusedStacksTopActivitiesLocked(null, null, null); } - boolean resumeFocusedStackTopActivityLocked( + boolean resumeFocusedStacksTopActivitiesLocked( ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) { if (!readyToResume()) { return false; } - if (targetStack != null && isTopDisplayFocusedStack(targetStack)) { + if (targetStack != null && targetStack.isTopStackOnDisplay()) { return targetStack.resumeTopActivityUncheckedLocked(target, targetOptions); } - final ActivityStack focusedStack = getTopDisplayFocusedStack(); - final ActivityRecord r = focusedStack.topRunningActivityLocked(); - if (r == null || !r.isState(RESUMED)) { - focusedStack.resumeTopActivityUncheckedLocked(null, null); - } else if (r.isState(RESUMED)) { - // Kick off any lingering app transitions form the MoveTaskToFront operation. - focusedStack.executeAppTransition(targetOptions); + // Resume all top activities in focused stacks on all displays. + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); + final ActivityStack focusedStack = display.getFocusedStack(); + if (focusedStack == null) { + continue; + } + final ActivityRecord r = focusedStack.topRunningActivityLocked(); + if (r == null || !r.isState(RESUMED)) { + focusedStack.resumeTopActivityUncheckedLocked(null, null); + } else if (r.isState(RESUMED)) { + // Kick off any lingering app transitions form the MoveTaskToFront operation. + focusedStack.executeAppTransition(targetOptions); + } } return false; @@ -2339,6 +2339,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } + /** + * This doesn't just find a task, it also moves the task to front. + */ void findTaskToMoveToFront(TaskRecord task, int flags, ActivityOptions options, String reason, boolean forceNonResizeable) { final ActivityStack currentStack = task.getStack(); @@ -2608,74 +2611,58 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents(); case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant(); } - switch (stack.getWindowingMode()) { - case WINDOWING_MODE_FULLSCREEN: return true; - case WINDOWING_MODE_FREEFORM: return r.supportsFreeform(); - case WINDOWING_MODE_PINNED: return r.supportsPictureInPicture(); - case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return r.supportsSplitScreenWindowingMode(); - case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode(); - } - - if (!stack.isOnHomeDisplay()) { - return r.canBeLaunchedOnDisplay(displayId); + // There is a 1-to-1 relationship between stack and task when not in + // primary split-windowing mode. + if (stack.getWindowingMode() != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) { + return false; + } else { + return r.supportsSplitScreenWindowingMode(); } - Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack); - return false; } /** - * Get next focusable stack in the system. This will search across displays and stacks - * in last-focused order for a focusable and visible stack, different from the target stack. + * Get next focusable stack in the system. This will search through the stack on the same + * display as the current focused stack, looking for a focusable and visible stack, different + * from the target stack. If no valid candidates will be found, it will then go through all + * displays and stacks in last-focused order. * * @param currentFocus The stack that previously had focus. * @param ignoreCurrent If we should ignore {@param currentFocus} when searching for next * candidate. - * @return Next focusable {@link ActivityStack}, null if not found. + * @return Next focusable {@link ActivityStack}, {@code null} if not found. */ - ActivityStack getNextFocusableStackLocked(ActivityStack currentFocus, boolean ignoreCurrent) { - mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); + ActivityStack getNextFocusableStackLocked(@NonNull ActivityStack currentFocus, + boolean ignoreCurrent) { + // First look for next focusable stack on the same display + final ActivityDisplay preferredDisplay = currentFocus.getDisplay(); + final ActivityStack preferredFocusableStack = preferredDisplay.getNextFocusableStack( + currentFocus, ignoreCurrent); + if (preferredFocusableStack != null) { + return preferredFocusableStack; + } - final int currentWindowingMode = currentFocus != null - ? currentFocus.getWindowingMode() : WINDOWING_MODE_UNDEFINED; - ActivityStack candidate = null; + // Now look through all displays + mWindowManager.getDisplaysInFocusOrder(mTmpOrderedDisplayIds); for (int i = mTmpOrderedDisplayIds.size() - 1; i >= 0; --i) { final int displayId = mTmpOrderedDisplayIds.get(i); + if (displayId == preferredDisplay.mDisplayId) { + // We've already checked this one + continue; + } // If a display is registered in WM, it must also be available in AM. final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId); if (display == null) { // Looks like the display no longer exists in the system... continue; } - for (int j = display.getChildCount() - 1; j >= 0; --j) { - final ActivityStack stack = display.getChildAt(j); - if (ignoreCurrent && stack == currentFocus) { - continue; - } - if (!stack.isFocusable() || !stack.shouldBeVisible(null)) { - continue; - } - - if (currentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY - && candidate == null && stack.inSplitScreenPrimaryWindowingMode()) { - // If the currently focused stack is in split-screen secondary we save off the - // top primary split-screen stack as a candidate for focus because we might - // prefer focus to move to an other stack to avoid primary split-screen stack - // overlapping with a fullscreen stack when a fullscreen stack is higher in z - // than the next split-screen stack. Assistant stack, I am looking at you... - // We only move the focus to the primary-split screen stack if there isn't a - // better alternative. - candidate = stack; - continue; - } - if (candidate != null && stack.inSplitScreenSecondaryWindowingMode()) { - // Use the candidate stack since we are now at the secondary split-screen. - return candidate; - } - return stack; + final ActivityStack nextFocusableStack = display.getNextFocusableStack(currentFocus, + ignoreCurrent); + if (nextFocusableStack != null) { + return nextFocusableStack; } } - return candidate; + return null; } /** @@ -2874,7 +2861,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS); - resumeFocusedStackTopActivityLocked(); + resumeFocusedStacksTopActivitiesLocked(); } finally { mAllowDockedStackResize = true; mWindowManager.continueSurfaceLayout(); @@ -3431,7 +3418,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // drawn signal is scheduled after the bounds animation start call on the bounds animator // thread. ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); - resumeFocusedStackTopActivityLocked(); + resumeFocusedStacksTopActivitiesLocked(); mService.getTaskChangeNotificationController().notifyActivityPinned(r); } @@ -3462,6 +3449,11 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D "moveActivityStackToFront: r=" + r); stack.moveToFront(reason, task); + // Report top activity change to tracking services and WM + if (r == getTopResumedActivity()) { + // TODO(b/111541062): Support tracking multiple resumed activities + mService.setResumedActivityUncheckLocked(r, reason); + } return true; } @@ -3618,7 +3610,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // It is possible that the display will not be awake at the time we // process the keyguard going away, which can happen before the sleep token // is released. As a result, it is important we resume the activity here. - resumeFocusedStackTopActivityLocked(); + resumeFocusedStacksTopActivitiesLocked(); } } } @@ -4724,7 +4716,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } break; case RESUME_TOP_ACTIVITY_MSG: { synchronized (mService.mGlobalLock) { - resumeFocusedStackTopActivityLocked(); + resumeFocusedStacksTopActivitiesLocked(); } } break; case SLEEP_TIMEOUT_MSG: { diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java index ca12716e7199..177e2f563a4b 100644 --- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java @@ -237,7 +237,7 @@ class ActivityStartInterceptor { (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) { return false; } - final PackageManagerInternal pmi = mService.mAm.getPackageManagerInternalLocked(); + final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked(); if (pmi == null) { return false; } @@ -318,7 +318,7 @@ class ActivityStartInterceptor { private boolean interceptHarmfulAppIfNeeded() { CharSequence harmfulAppWarning; try { - harmfulAppWarning = mService.mAm.getPackageManager() + harmfulAppWarning = mService.getPackageManager() .getHarmfulAppWarning(mAInfo.packageName, mUserId); } catch (RemoteException ex) { return false; diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 8be5adab1713..05fae837364a 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -672,7 +672,7 @@ class ActivityStarter { && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) { try { intent.addCategory(Intent.CATEGORY_VOICE); - if (!mService.mAm.getPackageManager().activitySupportsIntent( + if (!mService.getPackageManager().activitySupportsIntent( intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in current voice task does not support voice: " @@ -690,7 +690,7 @@ class ActivityStarter { // If the caller is starting a new voice session, just make sure the target // is actually allowing it to run this way. try { - if (!mService.mAm.getPackageManager().activitySupportsIntent(intent.getComponent(), + if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(), intent, resolvedType)) { Slog.w(TAG, "Activity being started in new voice task does not support: " @@ -772,7 +772,7 @@ class ActivityStarter { // launch the review activity and pass a pending intent to start the activity // we are to launching now after the review is completed. if (aInfo != null) { - if (mService.mAm.getPackageManagerInternalLocked().isPermissionsReviewRequired( + if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { IIntentSender target = mService.mAm.getIntentSenderLocked( ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, @@ -870,7 +870,7 @@ class ActivityStarter { String resolvedType, int userId) { if (auxiliaryResponse != null && auxiliaryResponse.needsPhaseTwo) { // request phase two resolution - mService.mAm.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo( + mService.getPackageManagerInternalLocked().requestInstantAppResolutionPhaseTwo( auxiliaryResponse, originalIntent, resolvedType, callingPackage, verificationBundle, userId); } @@ -970,7 +970,7 @@ class ActivityStarter { && !(Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() == null) && !Intent.ACTION_INSTALL_INSTANT_APP_PACKAGE.equals(intent.getAction()) && !Intent.ACTION_RESOLVE_INSTANT_APP_PACKAGE.equals(intent.getAction()) - && mService.mAm.getPackageManagerInternalLocked() + && mService.getPackageManagerInternalLocked() .isInstantAppInstallerComponent(intent.getComponent())) { // intercept intents targeted directly to the ephemeral installer the // ephemeral installer should never be started with a raw Intent; instead @@ -1367,7 +1367,7 @@ class ActivityStarter { // For paranoia, make sure we have correctly resumed the top activity. topStack.mLastPausedActivity = null; if (mDoResume) { - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); } ActivityOptions.abort(mOptions); if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) { @@ -1451,7 +1451,7 @@ class ActivityStarter { && !mSupervisor.isTopDisplayFocusedStack(mTargetStack)) { mTargetStack.moveToFront("startActivityUnchecked"); } - mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity, + mSupervisor.resumeFocusedStacksTopActivitiesLocked(mTargetStack, mStartActivity, mOptions); } } else if (mStartActivity != null) { @@ -2023,7 +2023,7 @@ class ActivityStarter { private void resumeTargetStackIfNeeded() { if (mDoResume) { - mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, null, mOptions); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(mTargetStack, null, mOptions); } else { ActivityOptions.abort(mOptions); } @@ -2139,7 +2139,7 @@ class ActivityStarter { // For paranoia, make sure we have correctly resumed the top activity. mTargetStack.mLastPausedActivity = null; if (mDoResume) { - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); } ActivityOptions.abort(mOptions); return START_DELIVERED_TO_TOP; @@ -2157,7 +2157,7 @@ class ActivityStarter { deliverNewIntent(top); mTargetStack.mLastPausedActivity = null; if (mDoResume) { - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); } return START_DELIVERED_TO_TOP; } diff --git a/services/core/java/com/android/server/am/ActivityTaskManagerService.java b/services/core/java/com/android/server/am/ActivityTaskManagerService.java index 20f2ece9565c..748b2d2d612a 100644 --- a/services/core/java/com/android/server/am/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/am/ActivityTaskManagerService.java @@ -19,6 +19,7 @@ package com.android.server.am; import static android.Manifest.permission.BIND_VOICE_INTERACTION; import static android.Manifest.permission.CHANGE_CONFIGURATION; import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS; +import static android.Manifest.permission.FILTER_EVENTS; import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.Manifest.permission.MANAGE_ACTIVITY_STACKS; import static android.Manifest.permission.READ_FRAME_BUFFER; @@ -36,15 +37,12 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTE import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_DATA; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_RECEIVER_EXTRAS; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; -import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW; import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; import static android.app.AppOpsManager.OP_NONE; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; @@ -74,7 +72,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATI import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IMMERSIVE; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK; -import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS; @@ -122,6 +119,8 @@ import android.app.ActivityThread; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; import android.os.IUserManager; import android.os.PowerManager; @@ -131,9 +130,9 @@ import android.os.UserManager; import android.os.WorkSource; import android.view.WindowManager; import com.android.internal.R; -import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IAppOpsService; import com.android.server.AppOpsService; +import com.android.server.SystemServiceManager; import com.android.server.pm.UserManagerService; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; @@ -181,7 +180,6 @@ import android.os.LocaleList; import android.os.Looper; import android.os.Message; import android.os.PersistableBundle; -import android.os.Process; import android.os.RemoteException; import android.os.StrictMode; import android.os.SystemClock; @@ -250,6 +248,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private static final String TAG_LOCKTASK = TAG + POSTFIX_LOCKTASK; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; + // How long we wait until we timeout on key dispatching. + private static final int KEY_DISPATCHING_TIMEOUT_MS = 5 * 1000; + // How long we wait until we timeout on key dispatching during instrumentation. + private static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS = 60 * 1000; + Context mContext; /** * This Context is themable and meant for UI display (AlertDialogs, etc.). The theme can @@ -261,6 +264,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { ActivityManagerService mAm; ActivityManagerInternal mAmInternal; UriGrantsManagerInternal mUgmInternal; + private PackageManagerInternal mPmInternal; /* Global service lock used by the package the owns this service. */ Object mGlobalLock; ActivityStackSupervisor mStackSupervisor; @@ -269,6 +273,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private AppOpsService mAppOpsService; /** All processes currently running that might have a window organized by name. */ final ProcessMap<WindowProcessController> mProcessNames = new ProcessMap<>(); + /** All processes we currently have running mapped by pid */ + final SparseArray<WindowProcessController> mPidMap = new SparseArray<>(); /** This is the process holding what we currently consider to be the "home" activity. */ WindowProcessController mHomeProcess; /** @@ -466,6 +472,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** If non-null, we are tracking the time the user spends in the currently focused app. */ AppTimeTracker mCurAppTimeTracker; + private AppWarnings mAppWarnings; + private FontScaleSettingObserver mFontScaleSettingObserver; private final class FontScaleSettingObserver extends ContentObserver { @@ -595,6 +603,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mGlobalLock = mAm; mH = new H(mAm.mHandlerThread.getLooper()); mUiHandler = new UiHandler(); + mAppWarnings = new AppWarnings( + this, mUiContext, mH, mUiHandler, SystemServiceManager.ensureSystemDir()); mTempConfig.setToDefaults(); mTempConfig.setLocales(LocaleList.getDefault()); @@ -1195,6 +1205,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } + // Explicitly dismissing the activity so reset its relaunch flag. + r.mRelaunchReason = ActivityRecord.RELAUNCH_REASON_NONE; } else { res = tr.getStack().requestFinishActivityLocked(token, resultCode, resultData, "app-request", true); @@ -1612,7 +1624,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final ActivityRecord r = stack.topRunningActivityLocked(); if (mStackSupervisor.moveFocusableActivityStackToFrontLocked( r, "setFocusedStack")) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } } finally { @@ -1633,7 +1645,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final ActivityRecord r = task.topRunningActivityLocked(); if (mStackSupervisor.moveFocusableActivityStackToFrontLocked(r, "setFocusedTask")) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } } finally { @@ -2398,10 +2410,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public boolean isTopOfTask(IBinder token) { synchronized (mGlobalLock) { ActivityRecord r = ActivityRecord.isInStackLocked(token); - if (r == null) { - throw new IllegalArgumentException(); - } - return r.getTask().getTopActivity() == r; + return r != null && r.getTask().getTopActivity() == r; } } @@ -4006,7 +4015,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { final long origId = Binder.clearCallingIdentity(); try { - mAm.mAppWarnings.alwaysShowUnsupportedCompileSdkWarning(activity); + mAppWarnings.alwaysShowUnsupportedCompileSdkWarning(activity); } finally { Binder.restoreCallingIdentity(origId); } @@ -4491,7 +4500,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0; if (isDensityChange && displayId == DEFAULT_DISPLAY) { - mAm.mAppWarnings.onDensityChanged(); + mAppWarnings.onDensityChanged(); mAm.killAllBackgroundProcessesExcept(N, ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE); @@ -4538,6 +4547,80 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { && mAmInternal.getCurrentUser().isDemo()); } + static long getInputDispatchingTimeoutLocked(ActivityRecord r) { + if (r == null || !r.hasProcess()) { + return KEY_DISPATCHING_TIMEOUT_MS; + } + return getInputDispatchingTimeoutLocked(r.app); + } + + private static long getInputDispatchingTimeoutLocked(WindowProcessController r) { + if (r != null && (r.isInstrumenting() || r.isUsingWrapper())) { + return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MS; + } + return KEY_DISPATCHING_TIMEOUT_MS; + } + + long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { + if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + FILTER_EVENTS); + } + WindowProcessController proc; + long timeout; + synchronized (mGlobalLock) { + proc = mPidMap.get(pid); + timeout = getInputDispatchingTimeoutLocked(proc); + } + + if (inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { + return -1; + } + + return timeout; + } + + /** + * Handle input dispatching timeouts. + * Returns whether input dispatching should be aborted or not. + */ + boolean inputDispatchingTimedOut(final WindowProcessController proc, + final ActivityRecord activity, final ActivityRecord parent, + final boolean aboveSystem, String reason) { + if (checkCallingPermission(FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + FILTER_EVENTS); + } + + final String annotation; + if (reason == null) { + annotation = "Input dispatching timed out"; + } else { + annotation = "Input dispatching timed out (" + reason + ")"; + } + + if (proc != null) { + synchronized (mGlobalLock) { + if (proc.isDebugging()) { + return false; + } + + if (proc.isInstrumenting()) { + Bundle info = new Bundle(); + info.putString("shortMsg", "keyDispatchingTimedOut"); + info.putString("longMsg", annotation); + mAm.finishInstrumentationLocked( + (ProcessRecord) proc.mOwner, Activity.RESULT_CANCELED, info); + return true; + } + } + mH.post(() -> { + mAm.mAppErrors.appNotResponding( + (ProcessRecord) proc.mOwner, activity, parent, aboveSystem, annotation); + }); + } + + return true; + } + /** * Decide based on the configuration whether we should show the ANR, * crash, etc dialogs. The idea is that if there is no affordance to @@ -4649,6 +4732,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { updateResumedAppTrace(r); mLastResumedActivity = r; + // TODO(b/111541062): Support multiple focused apps in WM mWindowManager.setFocusedApp(r.appToken, true); applyUpdateLockStateLocked(r); @@ -4782,6 +4866,30 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return kept; } + void scheduleAppGcsLocked() { + mH.post(() -> mAmInternal.scheduleAppGcs()); + } + + /** + * Returns the PackageManager. Used by classes hosted by {@link ActivityTaskManagerService}. The + * PackageManager could be unavailable at construction time and therefore needs to be accessed + * on demand. + */ + IPackageManager getPackageManager() { + return AppGlobals.getPackageManager(); + } + + PackageManagerInternal getPackageManagerInternalLocked() { + if (mPmInternal == null) { + mPmInternal = LocalServices.getService(PackageManagerInternal.class); + } + return mPmInternal; + } + + AppWarnings getAppWarningsLocked() { + return mAppWarnings; + } + void logAppTooSlow(WindowProcessController app, long startTime, String msg) { if (true || Build.IS_USER) { return; @@ -5039,7 +5147,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } if (mStackSupervisor.moveFocusableActivityStackToFrontLocked( r, "setFocusedActivity")) { - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } } @@ -5230,5 +5338,41 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } + + @Override + public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) { + synchronized (mGlobalLock) { + return ActivityTaskManagerService.this.inputDispatchingTimedOut( + pid, aboveSystem, reason); + } + } + + @Override + public void onProcessMapped(int pid, WindowProcessController proc) { + synchronized (mGlobalLock) { + mPidMap.put(pid, proc); + } + } + + @Override + public void onProcessUnMapped(int pid) { + synchronized (mGlobalLock) { + mPidMap.remove(pid); + } + } + + @Override + public void onPackageDataCleared(String name) { + synchronized (mGlobalLock) { + mAppWarnings.onPackageDataCleared(name); + } + } + + @Override + public void onPackageUninstalled(String name) { + synchronized (mGlobalLock) { + mAppWarnings.onPackageUninstalled(name); + } + } } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index 27567a77f1f7..3b98f373d817 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -410,6 +410,10 @@ class AppErrors { RescueParty.notePersistentAppCrash(mContext, r.uid); } + final int relaunchReason = r != null + ? r.getWindowProcessController().computeRelaunchReason() + : ActivityRecord.RELAUNCH_REASON_NONE; + AppErrorResult result = new AppErrorResult(); TaskRecord task; synchronized (mService) { @@ -422,11 +426,17 @@ class AppErrors { return; } + // Suppress crash dialog if the process is being relaunched due to a crash during a free + // resize. + if (relaunchReason == ActivityRecord.RELAUNCH_REASON_FREE_RESIZE) { + return; + } + /** * If this process was running instrumentation, finish now - it will be handled in * {@link ActivityManagerService#handleAppDiedLocked}. */ - if (r != null && r.instr != null) { + if (r != null && r.getActiveInstrumentation() != null) { return; } @@ -493,7 +503,7 @@ class AppErrors { mService.mStackSupervisor.handleAppCrashLocked(r.getWindowProcessController()); if (!r.isPersistent()) { mService.removeProcessLocked(r, false, false, "crash"); - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } finally { Binder.restoreCallingIdentity(orig); @@ -733,12 +743,12 @@ class AppErrors { // annoy the user repeatedly. Unless it is persistent, since those // processes run critical code. mService.removeProcessLocked(app, false, tryAgain, "crash"); - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); if (!showBackground) { return false; } } - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } else { final TaskRecord affectedTask = mService.mStackSupervisor.finishTopCrashedActivitiesLocked(app.getWindowProcessController(), reason); diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java index 30a384434675..a705180e48ee 100644 --- a/services/core/java/com/android/server/am/AppWarnings.java +++ b/services/core/java/com/android/server/am/AppWarnings.java @@ -17,7 +17,6 @@ package com.android.server.am; import android.annotation.UiThread; -import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; @@ -57,9 +56,9 @@ class AppWarnings { private final HashMap<String, Integer> mPackageFlags = new HashMap<>(); - private final ActivityManagerService mAms; + private final ActivityTaskManagerService mAtm; private final Context mUiContext; - private final ConfigHandler mAmsHandler; + private final ConfigHandler mHandler; private final UiHandler mUiHandler; private final AtomicFile mConfigFile; @@ -81,17 +80,17 @@ class AppWarnings { * <p> * <strong>Note:</strong> Must be called from the ActivityManagerService thread. * - * @param ams + * @param atm * @param uiContext - * @param amsHandler + * @param handler * @param uiHandler * @param systemDir */ - public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler, + public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir) { - mAms = ams; + mAtm = atm; mUiContext = uiContext; - mAmsHandler = new ConfigHandler(amsHandler.getLooper()); + mHandler = new ConfigHandler(handler.getLooper()); mUiHandler = new UiHandler(uiHandler.getLooper()); mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config"); @@ -104,7 +103,7 @@ class AppWarnings { * @param r activity record for which the warning may be displayed */ public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) { - final Configuration globalConfig = mAms.getGlobalConfiguration(); + final Configuration globalConfig = mAtm.getGlobalConfiguration(); if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) { mUiHandler.showUnsupportedDisplaySizeDialog(r); @@ -211,7 +210,7 @@ class AppWarnings { synchronized (mPackageFlags) { mPackageFlags.remove(name); - mAmsHandler.scheduleWrite(); + mHandler.scheduleWrite(); } } @@ -351,7 +350,7 @@ class AppWarnings { } else { mPackageFlags.remove(name); } - mAmsHandler.scheduleWrite(); + mHandler.scheduleWrite(); } } } @@ -430,10 +429,10 @@ class AppWarnings { } /** - * Handles messages on the ActivityManagerService thread. + * Handles messages on the ActivityTaskManagerService thread. */ private final class ConfigHandler extends Handler { - private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG; + private static final int MSG_WRITE = 1; private static final int DELAY_MSG_WRITE = 10000; diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 046cfc7820b5..b36b5d35f28e 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -1462,7 +1462,7 @@ public final class BroadcastQueue { // If the receiver app is being debugged we quietly ignore unresponsiveness, just // tidying up and moving on to the next broadcast without crashing or ANRing this // app just because it's stopped at a breakpoint. - final boolean debugging = (r.curApp != null && r.curApp.debugging); + final boolean debugging = (r.curApp != null && r.curApp.isDebugging()); Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver + ", started " + (now - r.receiverTime) + "ms ago"); diff --git a/services/core/java/com/android/server/am/CompatModeDialog.java b/services/core/java/com/android/server/am/CompatModeDialog.java deleted file mode 100644 index 202cc7cad79d..000000000000 --- a/services/core/java/com/android/server/am/CompatModeDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.app.ActivityManager; -import android.app.Dialog; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.view.Gravity; -import android.view.View; -import android.view.Window; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.Switch; - -public final class CompatModeDialog extends Dialog { - final ActivityManagerService mService; - final ApplicationInfo mAppInfo; - - final Switch mCompatEnabled; - final CheckBox mAlwaysShow; - final View mHint; - - public CompatModeDialog(ActivityManagerService service, Context context, - ApplicationInfo appInfo) { - super(context, com.android.internal.R.style.Theme_Holo_Dialog_MinWidth); - setCancelable(true); - setCanceledOnTouchOutside(true); - getWindow().requestFeature(Window.FEATURE_NO_TITLE); - getWindow().setType(WindowManager.LayoutParams.TYPE_PHONE); - getWindow().setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL); - mService = service; - mAppInfo = appInfo; - - setContentView(com.android.internal.R.layout.am_compat_mode_dialog); - mCompatEnabled = (Switch)findViewById(com.android.internal.R.id.compat_checkbox); - mCompatEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - synchronized (mService) { - mService.mCompatModePackages.setPackageScreenCompatModeLocked( - mAppInfo.packageName, - mCompatEnabled.isChecked() ? ActivityManager.COMPAT_MODE_ENABLED - : ActivityManager.COMPAT_MODE_DISABLED); - updateControls(); - } - } - }); - mAlwaysShow = (CheckBox)findViewById(com.android.internal.R.id.ask_checkbox); - mAlwaysShow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - synchronized (mService) { - mService.mCompatModePackages.setPackageAskCompatModeLocked( - mAppInfo.packageName, mAlwaysShow.isChecked()); - updateControls(); - } - } - }); - mHint = findViewById(com.android.internal.R.id.reask_hint); - - updateControls(); - } - - void updateControls() { - synchronized (mService) { - int mode = mService.mCompatModePackages.computeCompatModeLocked(mAppInfo); - mCompatEnabled.setChecked(mode == ActivityManager.COMPAT_MODE_ENABLED); - boolean ask = mService.mCompatModePackages.getPackageAskCompatModeLocked( - mAppInfo.packageName); - mAlwaysShow.setChecked(ask); - mHint.setVisibility(ask ? View.INVISIBLE : View.VISIBLE); - } - } -} diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java index e345b4d61cc1..ee4e36ff1fd1 100644 --- a/services/core/java/com/android/server/am/KeyguardController.java +++ b/services/core/java/com/android/server/am/KeyguardController.java @@ -163,7 +163,7 @@ class KeyguardController { updateKeyguardSleepToken(); // Some stack visibility might change (e.g. docked stack) - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); mStackSupervisor.addStartingWindowsForVisibleActivities(true /* taskSwitch */); mWindowManager.executeAppTransition(); diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java index 4fd01cdeaa3b..643c922ad2ca 100644 --- a/services/core/java/com/android/server/am/LockTaskController.java +++ b/services/core/java/com/android/server/am/LockTaskController.java @@ -446,7 +446,7 @@ public class LockTaskController { return; } task.performClearTaskLocked(); - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); } /** @@ -578,7 +578,7 @@ public class LockTaskController { if (andResume) { mSupervisor.findTaskToMoveToFront(task, 0, null, reason, lockTaskModeState != LOCK_TASK_MODE_NONE); - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); mWindowManager.executeAppTransition(); } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) { mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, @@ -653,7 +653,7 @@ public class LockTaskController { } if (taskChanged) { - mSupervisor.resumeFocusedStackTopActivityLocked(); + mSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index b33ce2b71019..8c552b98698a 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -187,8 +187,9 @@ final class ProcessRecord implements WindowProcessListener { int lruSeq; // Sequence id for identifying LRU update cycles CompatibilityInfo compat; // last used compatibility mode IBinder.DeathRecipient deathRecipient; // Who is watching for the death. - ActiveInstrumentation instr;// Set to currently active instrumentation running in process - boolean usingWrapper; // Set to true when process was launched with a wrapper attached + private ActiveInstrumentation mInstr; // Set to currently active instrumentation running in + // process. + private boolean mUsingWrapper; // Set to true when process was launched with a wrapper attached final ArraySet<BroadcastRecord> curReceivers = new ArraySet<BroadcastRecord>();// receivers currently running in the app long whenUnimportant; // When (uptime) the process last became unimportant long lastCpuTime; // How long proc has run CPU at last check @@ -232,7 +233,7 @@ final class ProcessRecord implements WindowProcessListener { private boolean mNotResponding; // does the app have a not responding dialog? Dialog anrDialog; // dialog being displayed due to app not resp. boolean removed; // has app package been removed from device? - boolean debugging; // was app launched for debugging? + private boolean mDebugging; // was app launched for debugging? boolean waitedForDebugger; // has process show wait for debugger dialog? Dialog waitDialog; // current wait for debugger dialog @@ -317,8 +318,8 @@ final class ProcessRecord implements WindowProcessListener { pw.println("}"); } pw.print(prefix); pw.print("compat="); pw.println(compat); - if (instr != null) { - pw.print(prefix); pw.print("instr="); pw.println(instr); + if (mInstr != null) { + pw.print(prefix); pw.print("mInstr="); pw.println(mInstr); } pw.print(prefix); pw.print("thread="); pw.println(thread); pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting="); @@ -435,9 +436,9 @@ final class ProcessRecord implements WindowProcessListener { pw.print(" killedByAm="); pw.print(killedByAm); pw.print(" waitingToKill="); pw.println(waitingToKill); } - if (debugging || mCrashing || crashDialog != null || mNotResponding + if (mDebugging || mCrashing || crashDialog != null || mNotResponding || anrDialog != null || bad) { - pw.print(prefix); pw.print("debugging="); pw.print(debugging); + pw.print(prefix); pw.print("mDebugging="); pw.print(mDebugging); pw.print(" mCrashing="); pw.print(mCrashing); pw.print(" "); pw.print(crashDialog); pw.print(" mNotResponding="); pw.print(mNotResponding); @@ -975,6 +976,33 @@ final class ProcessRecord implements WindowProcessListener { return mHasForegroundServices; } + void setDebugging(boolean debugging) { + mDebugging = debugging; + mWindowProcessController.setDebugging(debugging); + } + + boolean isDebugging() { + return mDebugging; + } + + void setUsingWrapper(boolean usingWrapper) { + mUsingWrapper = usingWrapper; + mWindowProcessController.setUsingWrapper(usingWrapper); + } + + boolean isUsingWrapper() { + return mUsingWrapper; + } + + void setActiveInstrumentation(ActiveInstrumentation instr) { + mInstr = instr; + mWindowProcessController.setInstrumenting(instr != null); + } + + ActiveInstrumentation getActiveInstrumentation() { + return mInstr; + } + @Override public void clearProfilerIfNeeded() { synchronized (mService) { diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java index f0bd8fa31478..8ce650c1a514 100644 --- a/services/core/java/com/android/server/am/ProcessStatsService.java +++ b/services/core/java/com/android/server/am/ProcessStatsService.java @@ -612,7 +612,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { stats.dumpCheckinLocked(pw, reqPackage); } else { if (dumpDetails || dumpFullDetails) { - stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); + stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpDetails, dumpAll, + activeOnly); } else { stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); } @@ -974,8 +975,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { if (checkedIn) pw.print(" (checked in)"); pw.println(":"); if (dumpDetails || dumpFullDetails) { - processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, - activeOnly); + processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpDetails, + dumpAll, activeOnly); if (dumpAll) { pw.print(" mFile="); pw.println(mFile.getBaseFile()); } @@ -1030,7 +1031,7 @@ public final class ProcessStatsService extends IProcessStats.Stub { // much crud. if (dumpFullDetails) { processStats.dumpLocked(pw, reqPackage, now, false, false, - activeOnly); + false, activeOnly); } else { processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); } @@ -1060,8 +1061,8 @@ public final class ProcessStatsService extends IProcessStats.Stub { } pw.println("CURRENT STATS:"); if (dumpDetails || dumpFullDetails) { - mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, - activeOnly); + mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpDetails, + dumpAll, activeOnly); if (dumpAll) { pw.print(" mFile="); pw.println(mFile.getBaseFile()); } diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 230810b58515..fb6b5c1f05ec 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -288,7 +288,7 @@ class RecentTasks { * @return whether the home app is also the active handler of recent tasks. */ boolean isRecentsComponentHomeActivity(int userId) { - final ComponentName defaultHomeActivity = mService.mAm.getPackageManagerInternalLocked() + final ComponentName defaultHomeActivity = mService.getPackageManagerInternalLocked() .getDefaultHomeActivity(userId); return defaultHomeActivity != null && mRecentsComponent != null && defaultHomeActivity.getPackageName().equals(mRecentsComponent.getPackageName()); diff --git a/services/core/java/com/android/server/am/RecentsAnimation.java b/services/core/java/com/android/server/am/RecentsAnimation.java index 1c7ad3f33310..c5586bbc3e82 100644 --- a/services/core/java/com/android/server/am/RecentsAnimation.java +++ b/services/core/java/com/android/server/am/RecentsAnimation.java @@ -287,7 +287,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, mWindowManager.prepareAppTransition(TRANSIT_NONE, false); mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, false); - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); // No reason to wait for the pausing activity in this case, as the hiding of // surfaces needs to be done immediately. diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 2d0c2a86b7b7..7256e2350219 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -16,9 +16,9 @@ package com.android.server.am; +import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; -import static android.app.ActivityTaskManager.INVALID_STACK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; @@ -63,6 +63,7 @@ import static com.android.server.am.ActivityStackSupervisor.ON_TOP; import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS; import static com.android.server.am.TaskRecordProto.ACTIVITIES; +import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE; import static com.android.server.am.TaskRecordProto.BOUNDS; import static com.android.server.am.TaskRecordProto.CONFIGURATION_CONTAINER; import static com.android.server.am.TaskRecordProto.FULLSCREEN; @@ -74,7 +75,6 @@ import static com.android.server.am.TaskRecordProto.ORIG_ACTIVITY; import static com.android.server.am.TaskRecordProto.REAL_ACTIVITY; import static com.android.server.am.TaskRecordProto.RESIZE_MODE; import static com.android.server.am.TaskRecordProto.STACK_ID; -import static com.android.server.am.TaskRecordProto.ACTIVITY_TYPE; import static java.lang.Integer.MAX_VALUE; @@ -87,7 +87,6 @@ import android.app.ActivityManager.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; -import android.app.IActivityManager; import android.app.TaskInfo; import android.content.ComponentName; import android.content.Intent; @@ -478,7 +477,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mResizeMode = resizeMode; mWindowContainerController.setResizeable(resizeMode); mService.mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } void setTaskDockedResizing(boolean resizing) { @@ -552,7 +551,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, preserveWindow); if (!kept) { - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } } @@ -752,7 +751,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // The task might have already been running and its visibility needs to be synchronized // with the visibility of the stack / windows. supervisor.ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow); - supervisor.resumeFocusedStackTopActivityLocked(); + supervisor.resumeFocusedStacksTopActivitiesLocked(); } // TODO: Handle incorrect request to move before the actual move, not after. @@ -1748,6 +1747,14 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return updateOverrideConfiguration(bounds, null /* insetBounds */); } + void setLastNonFullscreenBounds(Rect bounds) { + if (mLastNonFullscreenBounds == null) { + mLastNonFullscreenBounds = new Rect(bounds); + } else { + mLastNonFullscreenBounds.set(bounds); + } + } + /** * Update task's override configuration based on the bounds. * @param bounds The bounds of the task. @@ -1769,7 +1776,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi final boolean persistBounds = getWindowConfiguration().persistTaskBounds(); if (matchParentBounds) { if (!currentBounds.isEmpty() && persistBounds) { - mLastNonFullscreenBounds = currentBounds; + setLastNonFullscreenBounds(currentBounds); } setBounds(null); newConfig.unset(); @@ -1779,7 +1786,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi setBounds(mTmpRect); if (mStack == null || persistBounds) { - mLastNonFullscreenBounds = getOverrideBounds(); + setLastNonFullscreenBounds(getOverrideBounds()); } computeOverrideConfiguration(newConfig, mTmpRect, insetBounds, mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom); diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index ba604e08a061..f854df663e2e 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -104,11 +104,9 @@ import com.android.server.wm.WindowManagerService; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; /** @@ -2243,7 +2241,7 @@ class UserController implements Handler.Callback { protected void stackSupervisorResumeFocusedStackTopActivity() { synchronized (mService) { - mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); + mService.mStackSupervisor.resumeFocusedStacksTopActivitiesLocked(); } } diff --git a/services/core/java/com/android/server/am/WindowProcessController.java b/services/core/java/com/android/server/am/WindowProcessController.java index 6f3fb8eb9b72..e5551b51a093 100644 --- a/services/core/java/com/android/server/am/WindowProcessController.java +++ b/services/core/java/com/android/server/am/WindowProcessController.java @@ -93,6 +93,12 @@ public class WindowProcessController { private volatile String mRequiredAbi; // Running any services that are foreground? private volatile boolean mHasForegroundServices; + // was app launched for debugging? + private volatile boolean mDebugging; + // Active instrumentation running in process? + private volatile boolean mInstrumenting; + // Set to true when process was launched with a wrapper attached + private volatile boolean mUsingWrapper; // Thread currently set for VR scheduling int mVrThreadTid; @@ -189,6 +195,30 @@ public class WindowProcessController { return mRequiredAbi; } + public void setDebugging(boolean debugging) { + mDebugging = debugging; + } + + boolean isDebugging() { + return mDebugging; + } + + public void setUsingWrapper(boolean usingWrapper) { + mUsingWrapper = usingWrapper; + } + + boolean isUsingWrapper() { + return mUsingWrapper; + } + + public void setInstrumenting(boolean instrumenting) { + mInstrumenting = instrumenting; + } + + boolean isInstrumenting() { + return mInstrumenting; + } + public void addPackage(String packageName) { synchronized (mAtm.mGlobalLock) { mPkgList.add(packageName); @@ -436,6 +466,19 @@ public class WindowProcessController { return minTaskLayer; } + int computeRelaunchReason() { + synchronized (mAtm.mGlobalLock) { + final int activitiesSize = mActivities.size(); + for (int i = activitiesSize - 1; i >= 0; i--) { + final ActivityRecord r = mActivities.get(i); + if (r.mRelaunchReason != ActivityRecord.RELAUNCH_REASON_NONE) { + return r.mRelaunchReason; + } + } + } + return ActivityRecord.RELAUNCH_REASON_NONE; + } + void clearProfilerIfNeeded() { if (mListener == null) return; // Posting on handler so WM lock isn't held when we call into AM. diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java new file mode 100644 index 000000000000..7e7332180f8a --- /dev/null +++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java @@ -0,0 +1,86 @@ +/* + * 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.server.hdmi; + +import android.hardware.tv.cec.V1_0.SendMessageResult; + +/** + * Feature action that handles Audio Return Channel terminated by AVR devices. + */ +public class ArcTerminationActionFromAvr extends HdmiCecFeatureAction { + + // State in which waits for ARC response. + private static final int STATE_WAITING_FOR_INITIATE_ARC_RESPONSE = 1; + private static final int STATE_ARC_TERMINATED = 2; + + // the required maximum response time specified in CEC 9.2 + private static final int TIMEOUT_MS = 1000; + + ArcTerminationActionFromAvr(HdmiCecLocalDevice source) { + super(source); + } + + @Override + boolean start() { + mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE; + addTimer(mState, TIMEOUT_MS); + sendTerminateArc(); + return true; + } + + @Override + boolean processCommand(HdmiCecMessage cmd) { + if (mState != STATE_WAITING_FOR_INITIATE_ARC_RESPONSE) { + return false; + } + switch (cmd.getOpcode()) { + case Constants.MESSAGE_REPORT_ARC_TERMINATED: + mState = STATE_ARC_TERMINATED; + audioSystem().setArcStatus(false); + finish(); + return true; + } + return false; + } + + @Override + void handleTimerEvent(int state) { + if (mState != state) { + return; + } + + switch (mState) { + case STATE_WAITING_FOR_INITIATE_ARC_RESPONSE: + handleTerminateArcTimeout(); + break; + } + } + + protected void sendTerminateArc() { + sendCommand(HdmiCecMessageBuilder.buildTerminateArc(getSourceAddress(), Constants.ADDR_TV), + result -> { + if (result != SendMessageResult.SUCCESS) { + HdmiLogger.debug("Terminate ARC was not successfully sent."); + finish(); + } + }); + } + + private void handleTerminateArcTimeout() { + HdmiLogger.debug("handleTerminateArcTimeout"); + finish(); + } +} diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index ba5ee0231753..dea9309d550b 100644 --- a/services/core/java/com/android/server/hdmi/Constants.java +++ b/services/core/java/com/android/server/hdmi/Constants.java @@ -295,6 +295,13 @@ final class Constants { "persist.sys.hdmi.last_system_audio_control"; /** + * Property to indicate if device supports ARC or not + * <p>Default is true. + */ + static final String PROPERTY_ARC_SUPPORT = + "persist.sys.hdmi.property_arc_support"; + + /** * Property to save the audio port to switch to when system audio control is on. * <P>Audio system should switch to this port when cec active source is not its child in the tree * or is not itself. diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 7aab75090b19..ca85249b1f87 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -189,15 +189,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleRequestArcInitiate(HdmiCecMessage message) { assertRunOnServiceThread(); - // TODO(b/80296911): Check if ARC supported. - - // TODO(b/80296911): Check if port is ready to accept. - - // TODO(b/80296911): if both true, activate ARC functinality and - mService.sendCecCommand( - HdmiCecMessageBuilder.buildInitiateArc(mAddress, message.getSource())); - // TODO(b/80296911): else, send <Feature Abort>["Unrecongnized opcode"] - + if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { + mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + } else if (!isDirectConnectToTv()) { + HdmiLogger.debug("AVR device is not directly connected with TV"); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + } else { + addAndStartAction(new ArcInitiationActionFromAvr(this)); + } return true; } @@ -205,15 +204,14 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleRequestArcTermination(HdmiCecMessage message) { assertRunOnServiceThread(); - // TODO(b/80297105): Check if ARC supported. - - // TODO(b/80297105): Check is currently in arc. - - // TODO(b/80297105): If both true, deactivate ARC functionality and - mService.sendCecCommand( - HdmiCecMessageBuilder.buildTerminateArc(mAddress, message.getSource())); - // TODO(b/80297105): else, send <Feature Abort>["Unrecongnized opcode"] - + if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { + mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); + } else if (!isArcEnabled()) { + HdmiLogger.debug("ARC is not established between TV and AVR device"); + mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); + } else { + addAndStartAction(new ArcTerminationActionFromAvr(this)); + } return true; } @@ -377,6 +375,11 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { // TODO(b/111396634): switch input according to PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT } + protected boolean isDirectConnectToTv() { + int myPhysicalAddress = mService.getPhysicalAddress(); + return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress; + } + private void updateAudioManagerForSystemAudio(boolean on) { int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); @@ -435,4 +438,11 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice { void setTvSystemAudioModeSupport(boolean supported) { mTvSystemAudioModeSupport = supported; } + + @VisibleForTesting + protected boolean isArcEnabled() { + synchronized (mLock) { + return mArcEstablished; + } + } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java index f1cb246d0c21..649a2da1c451 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java @@ -18,6 +18,7 @@ package com.android.server.hdmi; import java.io.UnsupportedEncodingException; import java.util.Arrays; +import com.android.server.hdmi.Constants.AudioCodec; /** * A helper class to build {@link HdmiCecMessage} from various cec commands. @@ -265,6 +266,25 @@ public class HdmiCecMessageBuilder { return buildCommand(src, dest, Constants.MESSAGE_REPORT_ARC_TERMINATED); } + + /** + * Build <Request Short Audio Descriptor> command. + * + * @param src source address of command + * @param dest destination address of command + * @param audioFormats the {@link AudioCodec}s desired + * @return newly created {@link HdmiCecMessage} + */ + static HdmiCecMessage buildRequestShortAudioDescriptor(int src, int dest, + @AudioCodec int[] audioFormats) { + byte[] params = new byte[Math.min(audioFormats.length,4)] ; + for (int i = 0; i < params.length ; i++){ + params[i] = (byte) (audioFormats[i] & 0xff); + } + return buildCommand(src, dest, Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, params); + } + + /** * Build <Text View On> command. * diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java index 644f2c4d6864..e3c311f9e327 100644 --- a/services/core/java/com/android/server/job/controllers/IdleController.java +++ b/services/core/java/com/android/server/job/controllers/IdleController.java @@ -16,41 +16,33 @@ package com.android.server.job.controllers; -import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; - -import android.app.AlarmManager; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.ArraySet; -import android.util.Log; import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.util.IndentingPrintWriter; -import com.android.server.am.ActivityManagerService; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; +import com.android.server.job.controllers.idle.CarIdlenessTracker; +import com.android.server.job.controllers.idle.DeviceIdlenessTracker; +import com.android.server.job.controllers.idle.IdlenessListener; +import com.android.server.job.controllers.idle.IdlenessTracker; import java.util.function.Predicate; -public final class IdleController extends StateController { - private static final String TAG = "JobScheduler.Idle"; - private static final boolean DEBUG = JobSchedulerService.DEBUG - || Log.isLoggable(TAG, Log.DEBUG); - +public final class IdleController extends StateController implements IdlenessListener { + private static final String TAG = "JobScheduler.IdleController"; // Policy: we decide that we're "idle" if the device has been unused / // screen off or dreaming or wireless charging dock idle for at least this long - private long mInactivityIdleThreshold; - private long mIdleWindowSlop; final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); IdlenessTracker mIdleTracker; public IdleController(JobSchedulerService service) { super(service); - initIdleStateTracking(); + initIdleStateTracking(mContext); } /** @@ -74,9 +66,10 @@ public final class IdleController extends StateController { } /** - * Interaction with the task manager service + * State-change notifications from the idleness tracker */ - void reportNewIdleState(boolean isIdle) { + @Override + public void reportNewIdleState(boolean isIdle) { synchronized (mLock) { for (int i = mTrackedTasks.size()-1; i >= 0; i--) { mTrackedTasks.valueAt(i).setIdleConstraintSatisfied(isIdle); @@ -89,141 +82,22 @@ public final class IdleController extends StateController { * Idle state tracking, and messaging with the task manager when * significant state changes occur */ - private void initIdleStateTracking() { - mInactivityIdleThreshold = mContext.getResources().getInteger( - com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold); - mIdleWindowSlop = mContext.getResources().getInteger( - com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); - mIdleTracker = new IdlenessTracker(); - mIdleTracker.startTracking(); - } - - final class IdlenessTracker extends BroadcastReceiver { - private AlarmManager mAlarm; - - // After construction, mutations of idle/screen-on state will only happen - // on the main looper thread, either in onReceive() or in an alarm callback. - private boolean mIdle; - private boolean mScreenOn; - private boolean mDockIdle; - - private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { - handleIdleTrigger(); - }; - - public IdlenessTracker() { - mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); - - // At boot we presume that the user has just "interacted" with the - // device in some meaningful way. - mIdle = false; - mScreenOn = true; - mDockIdle = false; - } - - public boolean isIdle() { - return mIdle; - } - - public void startTracking() { - IntentFilter filter = new IntentFilter(); - - // Screen state - filter.addAction(Intent.ACTION_SCREEN_ON); - filter.addAction(Intent.ACTION_SCREEN_OFF); - - // Dreaming state - filter.addAction(Intent.ACTION_DREAMING_STARTED); - filter.addAction(Intent.ACTION_DREAMING_STOPPED); - - // Debugging/instrumentation - filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); - - // Wireless charging dock state - filter.addAction(Intent.ACTION_DOCK_IDLE); - filter.addAction(Intent.ACTION_DOCK_ACTIVE); - - mContext.registerReceiver(this, filter); - } - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (action.equals(Intent.ACTION_SCREEN_ON) - || action.equals(Intent.ACTION_DREAMING_STOPPED) - || action.equals(Intent.ACTION_DOCK_ACTIVE)) { - if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { - if (!mScreenOn) { - // Ignore this intent during screen off - return; - } else { - mDockIdle = false; - } - } else { - mScreenOn = true; - mDockIdle = false; - } - if (DEBUG) { - Slog.v(TAG,"exiting idle : " + action); - } - //cancel the alarm - mAlarm.cancel(mIdleAlarmListener); - if (mIdle) { - // possible transition to not-idle - mIdle = false; - reportNewIdleState(mIdle); - } - } else if (action.equals(Intent.ACTION_SCREEN_OFF) - || action.equals(Intent.ACTION_DREAMING_STARTED) - || action.equals(Intent.ACTION_DOCK_IDLE)) { - // when the screen goes off or dreaming starts or wireless charging dock in idle, - // we schedule the alarm that will tell us when we have decided the device is - // truly idle. - if (action.equals(Intent.ACTION_DOCK_IDLE)) { - if (!mScreenOn) { - // Ignore this intent during screen off - return; - } else { - mDockIdle = true; - } - } else { - mScreenOn = false; - mDockIdle = false; - } - final long nowElapsed = sElapsedRealtimeClock.millis(); - final long when = nowElapsed + mInactivityIdleThreshold; - if (DEBUG) { - Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" - + when); - } - mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, - when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); - } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { - handleIdleTrigger(); - } - } - - private void handleIdleTrigger() { - // idle time starts now. Do not set mIdle if screen is on. - if (!mIdle && (!mScreenOn || mDockIdle)) { - if (DEBUG) { - Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); - } - mIdle = true; - reportNewIdleState(mIdle); - } else { - if (DEBUG) { - Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" - + mIdle + " screen=" + mScreenOn); - } - } + private void initIdleStateTracking(Context ctx) { + final boolean isCar = mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_AUTOMOTIVE); + if (isCar) { + mIdleTracker = new CarIdlenessTracker(); + } else { + mIdleTracker = new DeviceIdlenessTracker(); } + mIdleTracker.startTracking(ctx, this); } @Override public void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { pw.println("Currently idle: " + mIdleTracker.isIdle()); + pw.println("Idleness tracker:"); mIdleTracker.dump(pw); pw.println(); for (int i = 0; i < mTrackedTasks.size(); i++) { diff --git a/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java new file mode 100644 index 000000000000..a3949a4521c2 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java @@ -0,0 +1,139 @@ +/* + * 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.server.job.controllers.idle; + +import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.util.Log; +import android.util.Slog; +import com.android.server.am.ActivityManagerService; +import com.android.server.job.JobSchedulerService; + +import java.io.PrintWriter; + +public final class CarIdlenessTracker extends BroadcastReceiver implements IdlenessTracker { + private static final String TAG = "JobScheduler.CarIdlenessTracker"; + private static final boolean DEBUG = JobSchedulerService.DEBUG + || Log.isLoggable(TAG, Log.DEBUG); + + public static final String ACTION_FORCE_IDLE = "com.android.server.ACTION_FORCE_IDLE"; + public static final String ACTION_UNFORCE_IDLE = "com.android.server.ACTION_UNFORCE_IDLE"; + + // After construction, mutations of idle/screen-on state will only happen + // on the main looper thread, either in onReceive() or in an alarm callback. + private boolean mIdle; + private boolean mScreenOn; + private IdlenessListener mIdleListener; + + public CarIdlenessTracker() { + // At boot we presume that the user has just "interacted" with the + // device in some meaningful way. + mIdle = false; + mScreenOn = true; + } + + @Override + public boolean isIdle() { + return mIdle; + } + + @Override + public void startTracking(Context context, IdlenessListener listener) { + mIdleListener = listener; + + IntentFilter filter = new IntentFilter(); + + // Screen state + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + + // Debugging/instrumentation + filter.addAction(ACTION_FORCE_IDLE); + filter.addAction(ACTION_UNFORCE_IDLE); + filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); + + context.registerReceiver(this, filter); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" mIdle: "); pw.println(mIdle); + pw.print(" mScreenOn: "); pw.println(mScreenOn); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + logIfDebug("Received action: " + action); + + // Check for forced actions + if (action.equals(ACTION_FORCE_IDLE)) { + logIfDebug("Forcing idle..."); + enterIdleState(true); + } else if (action.equals(ACTION_UNFORCE_IDLE)) { + logIfDebug("Unforcing idle..."); + exitIdleState(true); + } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { + logIfDebug("Going idle..."); + mScreenOn = false; + enterIdleState(false); + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + logIfDebug("exiting idle..."); + mScreenOn = true; + exitIdleState(true); + } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { + if (!mScreenOn) { + logIfDebug("Idle trigger fired..."); + enterIdleState(false); + } else { + logIfDebug("TRIGGER_IDLE received but not changing state; idle=" + + mIdle + " screen=" + mScreenOn); + } + } + } + + private void enterIdleState(boolean forced) { + if (!forced && mIdle) { + // Already idle and don't need to trigger callbacks since not forced + logIfDebug("Device is already considered idle"); + return; + } + mIdle = true; + mIdleListener.reportNewIdleState(mIdle); + } + + private void exitIdleState(boolean forced) { + if (!forced && !mIdle) { + // Already out of idle and don't need to trigger callbacks since not forced + logIfDebug("Device is already considered not idle"); + return; + } + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + + private void logIfDebug(String msg) { + if (DEBUG) { + Slog.v(TAG, msg); + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java new file mode 100644 index 000000000000..a85bd4066ad3 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java @@ -0,0 +1,175 @@ +/* + * 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.server.job.controllers.idle; + +import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; + +import android.app.AlarmManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import android.util.Log; +import android.util.Slog; +import com.android.server.am.ActivityManagerService; +import com.android.server.job.JobSchedulerService; + +import java.io.PrintWriter; + +public final class DeviceIdlenessTracker extends BroadcastReceiver implements IdlenessTracker { + private static final String TAG = "JobScheduler.DeviceIdlenessTracker"; + private static final boolean DEBUG = JobSchedulerService.DEBUG + || Log.isLoggable(TAG, Log.DEBUG); + + private AlarmManager mAlarm; + + // After construction, mutations of idle/screen-on state will only happen + // on the main looper thread, either in onReceive() or in an alarm callback. + private long mInactivityIdleThreshold; + private long mIdleWindowSlop; + private boolean mIdle; + private boolean mScreenOn; + private boolean mDockIdle; + private IdlenessListener mIdleListener; + + private AlarmManager.OnAlarmListener mIdleAlarmListener = () -> { + handleIdleTrigger(); + }; + + public DeviceIdlenessTracker() { + // At boot we presume that the user has just "interacted" with the + // device in some meaningful way. + mIdle = false; + mScreenOn = true; + mDockIdle = false; + } + + @Override + public boolean isIdle() { + return mIdle; + } + + @Override + public void startTracking(Context context, IdlenessListener listener) { + mIdleListener = listener; + mInactivityIdleThreshold = context.getResources().getInteger( + com.android.internal.R.integer.config_jobSchedulerInactivityIdleThreshold); + mIdleWindowSlop = context.getResources().getInteger( + com.android.internal.R.integer.config_jobSchedulerIdleWindowSlop); + mAlarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + IntentFilter filter = new IntentFilter(); + + // Screen state + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_SCREEN_OFF); + + // Dreaming state + filter.addAction(Intent.ACTION_DREAMING_STARTED); + filter.addAction(Intent.ACTION_DREAMING_STOPPED); + + // Debugging/instrumentation + filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE); + + // Wireless charging dock state + filter.addAction(Intent.ACTION_DOCK_IDLE); + filter.addAction(Intent.ACTION_DOCK_ACTIVE); + + context.registerReceiver(this, filter); + } + + @Override + public void dump(PrintWriter pw) { + pw.print(" mIdle: "); pw.println(mIdle); + pw.print(" mScreenOn: "); pw.println(mScreenOn); + pw.print(" mDockIdle: "); pw.println(mDockIdle); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(Intent.ACTION_SCREEN_ON) + || action.equals(Intent.ACTION_DREAMING_STOPPED) + || action.equals(Intent.ACTION_DOCK_ACTIVE)) { + if (action.equals(Intent.ACTION_DOCK_ACTIVE)) { + if (!mScreenOn) { + // Ignore this intent during screen off + return; + } else { + mDockIdle = false; + } + } else { + mScreenOn = true; + mDockIdle = false; + } + if (DEBUG) { + Slog.v(TAG,"exiting idle : " + action); + } + //cancel the alarm + mAlarm.cancel(mIdleAlarmListener); + if (mIdle) { + // possible transition to not-idle + mIdle = false; + mIdleListener.reportNewIdleState(mIdle); + } + } else if (action.equals(Intent.ACTION_SCREEN_OFF) + || action.equals(Intent.ACTION_DREAMING_STARTED) + || action.equals(Intent.ACTION_DOCK_IDLE)) { + // when the screen goes off or dreaming starts or wireless charging dock in idle, + // we schedule the alarm that will tell us when we have decided the device is + // truly idle. + if (action.equals(Intent.ACTION_DOCK_IDLE)) { + if (!mScreenOn) { + // Ignore this intent during screen off + return; + } else { + mDockIdle = true; + } + } else { + mScreenOn = false; + mDockIdle = false; + } + final long nowElapsed = sElapsedRealtimeClock.millis(); + final long when = nowElapsed + mInactivityIdleThreshold; + if (DEBUG) { + Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when=" + + when); + } + mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, + when, mIdleWindowSlop, "JS idleness", mIdleAlarmListener, null); + } else if (action.equals(ActivityManagerService.ACTION_TRIGGER_IDLE)) { + handleIdleTrigger(); + } + } + + private void handleIdleTrigger() { + // idle time starts now. Do not set mIdle if screen is on. + if (!mIdle && (!mScreenOn || mDockIdle)) { + if (DEBUG) { + Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis()); + } + mIdle = true; + mIdleListener.reportNewIdleState(mIdle); + } else { + if (DEBUG) { + Slog.v(TAG, "TRIGGER_IDLE received but not changing state; idle=" + + mIdle + " screen=" + mScreenOn); + } + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java new file mode 100644 index 000000000000..7ffd7cd3e2e0 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/IdlenessListener.java @@ -0,0 +1,32 @@ +/* + * 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.server.job.controllers.idle; + +/** + * Interface through which an IdlenessTracker informs the job scheduler of + * changes in the device's inactivity state. + */ +public interface IdlenessListener { + /** + * Tell the job scheduler that the device's idle state has changed. + * + * @param deviceIsIdle {@code true} to indicate that the device is now considered + * to be idle; {@code false} to indicate that the device is now being interacted with, + * so jobs with idle constraints should not be run. + */ + void reportNewIdleState(boolean deviceIsIdle); +} diff --git a/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java new file mode 100644 index 000000000000..09f01c2e4113 --- /dev/null +++ b/services/core/java/com/android/server/job/controllers/idle/IdlenessTracker.java @@ -0,0 +1,46 @@ +/* + * 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.server.job.controllers.idle; + +import android.content.Context; + +import java.io.PrintWriter; + +public interface IdlenessTracker { + /** + * One-time initialization: this method is called once, after construction of + * the IdlenessTracker instance. This is when the tracker should actually begin + * monitoring whatever signals it consumes in deciding when the device is in a + * non-interacting state. When the idle state changes thereafter, the given + * listener must be called to report the new state. + */ + void startTracking(Context context, IdlenessListener listener); + + /** + * Report whether the device is currently considered "idle" for purposes of + * running scheduled jobs with idleness constraints. + * + * @return {@code true} if the job scheduler should consider idleness + * constraints to be currently satisfied; {@code false} otherwise. + */ + boolean isIdle(); + + /** + * Dump useful information about tracked idleness-related state in plaintext. + */ + void dump(PrintWriter pw); +} diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index 38b9024ac048..06f67cd9cb9a 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -1176,15 +1176,13 @@ class InstantAppRegistry { private final class CookiePersistence extends Handler { private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */ - // In case you wonder why we stash the cookies aside, we use - // the user id for the message id and the package for the payload. - // Handler allows removing messages by id and tag where the - // tag is compared using ==. So to allow cancelling the - // pending persistence for an app under a given user we use - // the fact that package are cached by the system so the == - // comparison would match and we end up with a way to cancel - // persisting the cookie for a user and package. - private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies + // The cookies are cached per package name per user-id in this sparse + // array. The caching is so that pending persistence can be canceled within + // a short interval. To ensure we still return pending persist cookies + // for a package that uninstalled and reinstalled while the persistence + // was still pending, we use the package name as a key for + // mPendingPersistCookies, since that stays stable across reinstalls. + private final SparseArray<ArrayMap<String, SomeArgs>> mPendingPersistCookies = new SparseArray<>(); public CookiePersistence(Looper looper) { @@ -1214,10 +1212,10 @@ class InstantAppRegistry { public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg, @UserIdInt int userId) { - ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = + ArrayMap<String, SomeArgs> pendingWorkForUser = mPendingPersistCookies.get(userId); if (pendingWorkForUser != null) { - SomeArgs state = pendingWorkForUser.get(pkg); + SomeArgs state = pendingWorkForUser.get(pkg.packageName); if (state != null) { return (byte[]) state.arg1; } @@ -1237,7 +1235,7 @@ class InstantAppRegistry { private void addPendingPersistCookieLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg, @NonNull byte[] cookie, @NonNull File cookieFile) { - ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = + ArrayMap<String, SomeArgs> pendingWorkForUser = mPendingPersistCookies.get(userId); if (pendingWorkForUser == null) { pendingWorkForUser = new ArrayMap<>(); @@ -1246,16 +1244,16 @@ class InstantAppRegistry { SomeArgs args = SomeArgs.obtain(); args.arg1 = cookie; args.arg2 = cookieFile; - pendingWorkForUser.put(pkg, args); + pendingWorkForUser.put(pkg.packageName, args); } private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg, @UserIdInt int userId) { - ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser = + ArrayMap<String, SomeArgs> pendingWorkForUser = mPendingPersistCookies.get(userId); SomeArgs state = null; if (pendingWorkForUser != null) { - state = pendingWorkForUser.remove(pkg); + state = pendingWorkForUser.remove(pkg.packageName); if (pendingWorkForUser.isEmpty()) { mPendingPersistCookies.remove(userId); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 5878762e2272..3834a88d1c0c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -547,6 +547,10 @@ class PackageManagerShellCommand extends ShellCommand { case "-e": listEnabled = true; break; + case "-a": + getFlags |= PackageManager.MATCH_KNOWN_PACKAGES; + getFlags |= PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS; + break; case "-f": showSourceDir = true; break; @@ -2688,6 +2692,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" Prints all packages; optionally only those whose name contains"); pw.println(" the text in FILTER. Options are:"); pw.println(" -f: see their associated file"); + pw.println(" -a: all known packages"); pw.println(" -d: filter to only show disabled packages"); pw.println(" -e: filter to only show enabled packages"); pw.println(" -s: filter to only show system packages"); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8ef1196bf625..e46c03edc77d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -4391,17 +4391,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (isKeyguardShowingAndNotOccluded()) { // don't launch home if keyguard showing return; - } else if (mKeyguardOccluded && mKeyguardDelegate.isShowing()) { - mKeyguardDelegate.dismiss(new KeyguardDismissCallback() { - @Override - public void onDismissSucceeded() throws RemoteException { - mHandler.post(() -> { - startDockOrHome(true /*fromHomeKey*/, awakenFromDreams); - }); - } - }, null /* message */); - return; - } else if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { + } + + if (!mKeyguardOccluded && mKeyguardDelegate.isInputRestricted()) { // when in keyguard restricted mode, must first verify unlock // before launching home mKeyguardDelegate.verifyUnlock(new OnKeyguardExitResult() { diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 8a135b81e562..3291a45c94c4 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -47,7 +47,6 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.provider.Settings; import android.service.trust.TrustAgentService; import android.text.TextUtils; @@ -61,7 +60,6 @@ import android.view.IWindowManager; import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; -import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.util.DumpUtils; import com.android.internal.widget.LockPatternUtils; import com.android.server.SystemService; @@ -432,13 +430,20 @@ public class TrustManagerService extends SystemService { for (int i = 0; i < userInfos.size(); i++) { UserInfo info = userInfos.get(i); - if (info == null || info.partial || !info.isEnabled() || info.guestToRemove - || !info.supportsSwitchToByUser()) { + if (info == null || info.partial || !info.isEnabled() || info.guestToRemove) { continue; } int id = info.id; boolean secure = mLockPatternUtils.isSecure(id); + + if (!info.supportsSwitchToByUser()) { + if (info.isManagedProfile() && !secure) { + setDeviceLockedForUser(id, false); + } + continue; + } + boolean trusted = aggregateIsTrusted(id); boolean showingKeyguard = true; boolean biometricAuthenticated = false; @@ -993,7 +998,8 @@ public class TrustManagerService extends SystemService { enforceReportPermission(); final long identity = Binder.clearCallingIdentity(); try { - if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { + if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId) + && mLockPatternUtils.isSecure(userId)) { synchronized (mDeviceLockedForUser) { mDeviceLockedForUser.put(userId, locked); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9d68c63cc38c..c8bd2113ac6c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -965,7 +965,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } mPaddingChanged = false; } - if (mInfo != null && mInfo.getSupportsAmbientMode()) { + if (mInfo != null && mInfo.supportsAmbientMode()) { try { mEngine.setInAmbientMode(mInAmbientMode, false /* animated */); } catch (RemoteException e) { @@ -1777,7 +1777,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub mInAmbientMode = inAmbienMode; final WallpaperData data = mWallpaperMap.get(mCurrentUserId); if (data != null && data.connection != null && data.connection.mInfo != null - && data.connection.mInfo.getSupportsAmbientMode()) { + && data.connection.mInfo.supportsAmbientMode()) { engine = data.connection.mEngine; } else { engine = null; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index bfecd9d99cd9..83075ed24e33 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -274,4 +274,11 @@ public abstract class ActivityTaskManagerInternal { public abstract void enableScreenAfterBoot(boolean booted); public abstract boolean showStrictModeViolationDialog(); public abstract void showSystemReadyErrorDialogsIfNeeded(); + + public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason); + public abstract void onProcessMapped(int pid, WindowProcessController proc); + public abstract void onProcessUnMapped(int pid); + + public abstract void onPackageDataCleared(String name); + public abstract void onPackageUninstalled(String name); } diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java index 4b8b6074a480..b7759182fb01 100644 --- a/services/core/java/com/android/server/wm/AppWindowToken.java +++ b/services/core/java/com/android/server/wm/AppWindowToken.java @@ -78,6 +78,7 @@ import static com.android.server.wm.AppWindowTokenProto.STARTING_MOVED; import static com.android.server.wm.AppWindowTokenProto.STARTING_WINDOW; import static com.android.server.wm.AppWindowTokenProto.THUMBNAIL; import static com.android.server.wm.AppWindowTokenProto.WINDOW_TOKEN; +import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM; import android.annotation.CallSuper; import android.app.Activity; @@ -264,6 +265,12 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree */ private boolean mWillCloseOrEnterPip; + /** Layer used to constrain the animation to a token's stack bounds. */ + SurfaceControl mAnimationBoundsLayer; + + /** Whether this token needs to create mAnimationBoundsLayer for cropping animations. */ + boolean mNeedsAnimationBoundsLayer; + AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint, @@ -1720,6 +1727,20 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return !isSplitScreenPrimary || allowSplitScreenPrimaryAnimation; } + /** + * Creates a layer to apply crop to an animation. + */ + private SurfaceControl createAnimationBoundsLayer(Transaction t) { + if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.i(TAG, "Creating animation bounds layer"); + final SurfaceControl.Builder builder = makeAnimationLeash() + .setParent(getAnimationLeashParent()) + .setName(getSurfaceControl() + " - animation-bounds") + .setSize(getSurfaceWidth(), getSurfaceHeight()); + final SurfaceControl boundsLayer = builder.build(); + t.show(boundsLayer); + return boundsLayer; + } + boolean applyAnimationLocked(WindowManager.LayoutParams lp, int transit, boolean enter, boolean isVoiceInteraction) { @@ -1753,12 +1774,15 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree adapter = mService.mAppTransition.getRemoteAnimationController() .createAnimationAdapter(this, mTmpPoint, mTmpRect); } else { + final int appStackClipMode = mService.mAppTransition.getAppStackClipMode(); + mNeedsAnimationBoundsLayer = (appStackClipMode == STACK_CLIP_AFTER_ANIM); + final Animation a = loadAnimation(lp, transit, enter, isVoiceInteraction); if (a != null) { adapter = new LocalAnimationAdapter( new WindowAnimationSpec(a, mTmpPoint, mTmpRect, mService.mAppTransition.canSkipFirstFrame(), - mService.mAppTransition.getAppStackClipMode(), + appStackClipMode, true /* isAppAnimation */), mService.mSurfaceAnimationRunner); if (a.getZAdjustment() == Animation.ZORDER_TOP) { @@ -1858,6 +1882,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree @Override public void onAnimationLeashDestroyed(Transaction t) { super.onAnimationLeashDestroyed(t); + if (mAnimationBoundsLayer != null) { + t.destroy(mAnimationBoundsLayer); + mAnimationBoundsLayer = null; + } + if (mAnimatingAppWindowTokenRegistry != null) { mAnimatingAppWindowTokenRegistry.notifyFinished(this); } @@ -1908,6 +1937,26 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree if (mAnimatingAppWindowTokenRegistry != null) { mAnimatingAppWindowTokenRegistry.notifyStarting(this); } + + // If the animation needs to be cropped then an animation bounds layer is created as a child + // of the pinned stack or animation layer. The leash is then reparented to this new layer. + if (mNeedsAnimationBoundsLayer) { + final TaskStack stack = getStack(); + if (stack == null) { + return; + } + mAnimationBoundsLayer = createAnimationBoundsLayer(t); + + // Set clip rect to stack bounds. + mTmpRect.setEmpty(); + stack.getBounds(mTmpRect); + + // Crop to stack bounds. + t.setWindowCrop(mAnimationBoundsLayer, mTmpRect); + + // Reparent leash to animation bounds layer. + t.reparent(leash, mAnimationBoundsLayer.getHandle()); + } } /** @@ -1927,6 +1976,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mTransit = TRANSIT_UNSET; mTransitFlags = 0; mNeedsZBoost = false; + mNeedsAnimationBoundsLayer = false; setAppLayoutChanges(FINISH_LAYOUT_REDO_ANIM | FINISH_LAYOUT_REDO_WALLPAPER, "AppWindowToken"); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 016921df6047..b5e2f01d8fbb 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -123,17 +123,14 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal return appWindowToken.mInputDispatchingTimeoutNanos; } } else if (windowState != null) { - try { - // Notify the activity manager about the timeout and let it decide whether - // to abort dispatching or keep waiting. - long timeout = ActivityManager.getService().inputDispatchingTimedOut( - windowState.mSession.mPid, aboveSystem, reason); - if (timeout >= 0) { - // The activity manager declined to abort dispatching. - // Wait a bit longer and timeout again later. - return timeout * 1000000L; // nanoseconds - } - } catch (RemoteException ex) { + // Notify the activity manager about the timeout and let it decide whether + // to abort dispatching or keep waiting. + long timeout = mService.mAtmInternal.inputDispatchingTimedOut( + windowState.mSession.mPid, aboveSystem, reason); + if (timeout >= 0) { + // The activity manager declined to abort dispatching. + // Wait a bit longer and timeout again later. + return timeout * 1000000L; // nanoseconds } } return 0; // abort dispatching diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java index d83f28ccb31c..8b634b1bb938 100644 --- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java +++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java @@ -209,6 +209,12 @@ public class TaskWindowContainerController } } + public boolean isDragResizing() { + synchronized (mWindowMap) { + return mContainer.isDragResizing(); + } + } + void reportSnapshotChanged(TaskSnapshot snapshot) { mHandler.obtainMessage(H.REPORT_SNAPSHOT_CHANGED, snapshot).sendToTarget(); } diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java index 548e23a4d984..825255e556ff 100644 --- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java +++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java @@ -99,13 +99,7 @@ public class WindowAnimationSpec implements AnimationSpec { tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats); t.setAlpha(leash, tmp.transformation.getAlpha()); - if (mStackClipMode == STACK_CLIP_NONE) { - t.setWindowCrop(leash, tmp.transformation.getClipRect()); - } else if (mStackClipMode == STACK_CLIP_AFTER_ANIM) { - mTmpRect.set(mStackBounds); - // Offset stack bounds to stack position so the final crop is in screen space. - mTmpRect.offsetTo(mPosition.x, mPosition.y); - t.setFinalCrop(leash, mTmpRect); + if (mStackClipMode == STACK_CLIP_NONE || mStackClipMode == STACK_CLIP_AFTER_ANIM) { t.setWindowCrop(leash, tmp.transformation.getClipRect()); } else { mTmpRect.set(mStackBounds); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java index 420987d03509..41c7f1d787a4 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStartInterceptorTest.java @@ -123,7 +123,7 @@ public class ActivityStartInterceptorTest { mDevicePolicyManager); when(mDevicePolicyManager.createShowAdminSupportIntent(TEST_USER_ID, true)) .thenReturn(ADMIN_SUPPORT_INTENT); - when(mAm.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal); + when(mService.getPackageManagerInternalLocked()).thenReturn(mPackageManagerInternal); // Mock UserManager when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); @@ -136,7 +136,7 @@ public class ActivityStartInterceptorTest { thenReturn(CONFIRM_CREDENTIALS_INTENT); // Mock PackageManager - when(mAm.getPackageManager()).thenReturn(mPackageManager); + when(mService.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID)) .thenReturn(null); diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index 7e8697d95f51..a9e0aa805c37 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -32,12 +32,16 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.app.ActivityManagerInternal; import android.app.ActivityOptions; +import android.content.pm.PackageManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; +import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.DisplayWindowController; import org.junit.Rule; @@ -128,17 +132,16 @@ public class ActivityTestsBase { } ActivityManagerService setupActivityManagerService(TestActivityTaskManagerService atm) { - final ActivityManagerService am = spy(new TestActivityManagerService(mContext, atm)); + final TestActivityManagerService am = spy(new TestActivityManagerService(mContext, atm)); setupActivityManagerService(am, atm); return am; } - void setupActivityManagerService(ActivityManagerService am, ActivityTaskManagerService atm) { + void setupActivityManagerService( + TestActivityManagerService am, TestActivityTaskManagerService atm) { atm.setActivityManagerService(am); - atm.mAmInternal = am.new LocalService(); - am.mAtmInternal = atm.new LocalService(); - am.mUgmInternal = mock(UriGrantsManagerInternal.class); - atm.mUgmInternal = mock(UriGrantsManagerInternal.class); + atm.mAmInternal = am.getLocalService(); + am.mAtmInternal = atm.getLocalService(); // Makes sure the supervisor is using with the spy object. atm.mStackSupervisor.setService(atm); doReturn(mock(IPackageManager.class)).when(am).getPackageManager(); @@ -378,6 +381,8 @@ public class ActivityTestsBase { protected static class TestActivityTaskManagerService extends ActivityTaskManagerService { private LockTaskController mLockTaskController; + private ActivityTaskManagerInternal mInternal; + private PackageManagerInternal mPmInternal; TestActivityTaskManagerService(Context context) { super(context); @@ -386,6 +391,7 @@ public class ActivityTestsBase { mSupportsSplitScreenMultiWindow = true; mSupportsFreeformWindowManagement = true; mSupportsPictureInPicture = true; + mUgmInternal = mock(UriGrantsManagerInternal.class); } @Override @@ -430,16 +436,34 @@ public class ActivityTestsBase { protected ActivityStackSupervisor createTestSupervisor() { return new TestActivityStackSupervisor(this, mH.getLooper()); } + + ActivityTaskManagerInternal getLocalService() { + if (mInternal == null) { + mInternal = new ActivityTaskManagerService.LocalService(); + } + return mInternal; + } + + PackageManagerInternal getPackageManagerInternalLocked() { + if (mPmInternal == null) { + mPmInternal = mock(PackageManagerInternal.class); + doReturn(false).when(mPmInternal).isPermissionsReviewRequired(anyString(), anyInt()); + } + return mPmInternal; + } } /** * An {@link ActivityManagerService} subclass which provides a test * {@link ActivityStackSupervisor}. */ - protected static class TestActivityManagerService extends ActivityManagerService { + static class TestActivityManagerService extends ActivityManagerService { + + private ActivityManagerInternal mInternal; TestActivityManagerService(Context context, TestActivityTaskManagerService atm) { super(context, atm); + mUgmInternal = mock(UriGrantsManagerInternal.class); } @Override @@ -450,6 +474,13 @@ public class ActivityTestsBase { Configuration getGlobalConfiguration() { return mContext.getResources().getConfiguration(); } + + ActivityManagerInternal getLocalService() { + if (mInternal == null) { + mInternal = new LocalService(); + } + return mInternal; + } } /** diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java index 37de79524ee3..227a70f7a5a3 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -115,7 +115,8 @@ public class RecentTasksTest extends ActivityTestsBase { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); mService = spy(new MyTestActivityTaskManagerService(mContext)); - final ActivityManagerService am = spy(new MyTestActivityManagerService(mContext, mService)); + final TestActivityManagerService am = + spy(new MyTestActivityManagerService(mContext, mService)); setupActivityManagerService(am, mService); mRecentTasks = (TestRecentTasks) mService.getRecentTasks(); mRecentTasks.loadParametersFromResources(mContext.getResources()); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java new file mode 100644 index 000000000000..14695c526bff --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -0,0 +1,149 @@ +/* + * 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.server.hdmi; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.Nullable; +import android.hardware.hdmi.HdmiDeviceInfo; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Looper; +import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Tests for {@link ArcTerminationActionFromAvr} */ +@SmallTest +@RunWith(JUnit4.class) +public class ArcTerminationActionFromAvrTest { + + private HdmiDeviceInfo mDeviceInfoForTests; + private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem; + private ArcTerminationActionFromAvr mAction; + + private TestLooper mTestLooper = new TestLooper(); + private boolean mSendCecCommandSuccess; + private boolean mShouldDispatchReportArcTerminated; + private boolean mArcEnabled; + private boolean mSetArcStatusCalled; + + @Before + public void setUp() { + mDeviceInfoForTests = new HdmiDeviceInfo(1000, 1); + + HdmiControlService hdmiControlService = + new HdmiControlService(null) { + @Override + void sendCecCommand( + HdmiCecMessage command, @Nullable SendMessageCallback callback) { + switch (command.getOpcode()) { + case Constants.MESSAGE_TERMINATE_ARC: + if (callback != null) { + callback.onSendCompleted( + mSendCecCommandSuccess + ? SendMessageResult.SUCCESS + : SendMessageResult.NACK); + } + if (mShouldDispatchReportArcTerminated) { + mHdmiCecLocalDeviceAudioSystem.dispatchMessage( + HdmiCecMessageBuilder.buildReportArcTerminated( + Constants.ADDR_TV, + mHdmiCecLocalDeviceAudioSystem.mAddress)); + } + break; + default: + throw new IllegalArgumentException("Unexpected message"); + } + } + + @Override + boolean isPowerStandby() { + return false; + } + + @Override + boolean isAddressAllocated() { + return true; + } + + @Override + Looper getServiceLooper() { + return mTestLooper.getLooper(); + } + }; + + mHdmiCecLocalDeviceAudioSystem = + new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { + @Override + HdmiDeviceInfo getDeviceInfo() { + return mDeviceInfoForTests; + } + + @Override + void setArcStatus(boolean enabled) { + mSetArcStatusCalled = true; + mArcEnabled = enabled; + } + }; + mHdmiCecLocalDeviceAudioSystem.init(); + Looper looper = mTestLooper.getLooper(); + hdmiControlService.setIoLooper(looper); + + mArcEnabled = true; + mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem); + } + + @Test + public void testSendMessage_NotSuccess() { + mSendCecCommandSuccess = false; + mShouldDispatchReportArcTerminated = false; + mSetArcStatusCalled = false; + mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction); + + mTestLooper.dispatchAll(); + assertThat(mSetArcStatusCalled).isFalse(); + assertThat(mArcEnabled).isTrue(); + } + + @Test + public void testReportArcTerminated_NotReceived() { + mSendCecCommandSuccess = true; + mShouldDispatchReportArcTerminated = false; + mSetArcStatusCalled = false; + mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction); + + mTestLooper.moveTimeForward(1000); + mTestLooper.dispatchAll(); + assertThat(mSetArcStatusCalled).isFalse(); + assertThat(mArcEnabled).isTrue(); + } + + @Test + public void testReportArcTerminated_Received() { + mSendCecCommandSuccess = true; + mShouldDispatchReportArcTerminated = true; + mSetArcStatusCalled = false; + mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction); + + mTestLooper.moveTimeForward(1000); + mTestLooper.dispatchAll(); + assertThat(mSetArcStatusCalled).isTrue(); + assertThat(mArcEnabled).isFalse(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index d437ca10bb3a..e114e032753e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -23,16 +23,13 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDeviceAudioSystem.TvSystemAudioModeSupportedCallback; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests for {@link DetectTvSystemAudioModeSupportAction} class. - */ +/** Tests for {@link DetectTvSystemAudioModeSupportAction} class. */ @SmallTest @RunWith(JUnit4.class) public class DetectTvSystemAudioModeSupportActionTest { @@ -49,46 +46,49 @@ public class DetectTvSystemAudioModeSupportActionTest { @Before public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); - HdmiControlService hdmiControlService = new HdmiControlService(null) { + HdmiControlService hdmiControlService = + new HdmiControlService(null) { - @Override - void sendCecCommand(HdmiCecMessage command, - @Nullable SendMessageCallback callback) { - switch (command.getOpcode()) { - case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: - if (callback != null) { - callback.onSendCompleted(mSendCecCommandSuccess - ? SendMessageResult.SUCCESS : SendMessageResult.NACK); - } - if (mShouldDispatchFeatureAbort) { - mHdmiCecLocalDeviceAudioSystem.dispatchMessage( - HdmiCecMessageBuilder.buildFeatureAbortCommand( - Constants.ADDR_TV, - mHdmiCecLocalDeviceAudioSystem.mAddress, - Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, - Constants.ABORT_UNRECOGNIZED_OPCODE)); + @Override + void sendCecCommand( + HdmiCecMessage command, @Nullable SendMessageCallback callback) { + switch (command.getOpcode()) { + case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: + if (callback != null) { + callback.onSendCompleted( + mSendCecCommandSuccess + ? SendMessageResult.SUCCESS + : SendMessageResult.NACK); + } + if (mShouldDispatchFeatureAbort) { + mHdmiCecLocalDeviceAudioSystem.dispatchMessage( + HdmiCecMessageBuilder.buildFeatureAbortCommand( + Constants.ADDR_TV, + mHdmiCecLocalDeviceAudioSystem.mAddress, + Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, + Constants.ABORT_UNRECOGNIZED_OPCODE)); + } + break; + default: + throw new IllegalArgumentException("Unexpected message"); } - break; - default: - throw new IllegalArgumentException("Unexpected message"); - } - } + } - @Override - boolean isPowerStandby() { - return false; - } + @Override + boolean isPowerStandby() { + return false; + } - @Override - boolean isAddressAllocated() { - return true; - } + @Override + boolean isAddressAllocated() { + return true; + } - @Override - Looper getServiceLooper() { - return mTestLooper.getLooper(); - } - }; + @Override + Looper getServiceLooper() { + return mTestLooper.getLooper(); + } + }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override @@ -100,13 +100,14 @@ public class DetectTvSystemAudioModeSupportActionTest { Looper looper = mTestLooper.getLooper(); hdmiControlService.setIoLooper(looper); - mAction = new DetectTvSystemAudioModeSupportAction( - mHdmiCecLocalDeviceAudioSystem, - new TvSystemAudioModeSupportedCallback() { - public void onResult(boolean supported) { - mSupported = Boolean.valueOf(supported); - } - }); + mAction = + new DetectTvSystemAudioModeSupportAction( + mHdmiCecLocalDeviceAudioSystem, + new TvSystemAudioModeSupportedCallback() { + public void onResult(boolean supported) { + mSupported = Boolean.valueOf(supported); + } + }); mSupported = null; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 55099358f7fa..7484edd286f0 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -18,10 +18,10 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.MessageQueue; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.hdmi.HdmiCecController.NativeWrapper; import java.util.ArrayList; import java.util.List; -import com.android.internal.annotations.VisibleForTesting; /** Fake {@link NativeWrapper} useful for testing. */ final class FakeNativeWrapper implements NativeWrapper { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index 84e1b7e18909..6cf5f0045c2d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -15,17 +15,6 @@ */ package com.android.server.hdmi; -import android.content.Context; -import android.hardware.hdmi.HdmiPortInfo; -import android.hardware.tv.cec.V1_0.SendMessageResult; -import android.os.Looper; -import android.os.MessageQueue; -import android.os.test.TestLooper; -import android.support.test.filters.SmallTest; -import android.util.Log; -import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; -import com.android.server.hdmi.HdmiCecController.NativeWrapper; - import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_PLAYBACK; import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; @@ -37,14 +26,19 @@ import static com.android.server.hdmi.Constants.ADDR_SPECIFIC_USE; import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static junit.framework.Assert.assertEquals; + +import android.content.Context; +import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.os.Looper; +import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; +import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests for {@link com.android.server.hdmi.HdmiCecController} class. - */ +/** Tests for {@link com.android.server.hdmi.HdmiCecController} class. */ @SmallTest @RunWith(JUnit4.class) public class HdmiCecControllerTest { @@ -71,12 +65,13 @@ public class HdmiCecControllerTest { private HdmiControlService mHdmiControlService; private HdmiCecController mHdmiCecController; private int mLogicalAddress = 16; - private AllocateAddressCallback mCallback = new AllocateAddressCallback() { - @Override - public void onAllocated(int deviceType, int logicalAddress) { - mLogicalAddress = logicalAddress; - } - }; + private AllocateAddressCallback mCallback = + new AllocateAddressCallback() { + @Override + public void onAllocated(int deviceType, int logicalAddress) { + mLogicalAddress = logicalAddress; + } + }; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); @@ -86,13 +81,11 @@ public class HdmiCecControllerTest { mMyLooper = mTestLooper.getLooper(); mHdmiControlService = new MyHdmiControlService(null); mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); } - /** - * Tests for {@link HdmiCecController#allocateLogicalAddress} - */ + /** Tests for {@link HdmiCecController#allocateLogicalAddress} */ @Test public void testAllocatLogicalAddress_TvDevicePreferredNotOcupied() { mHdmiCecController.allocateLogicalAddress(DEVICE_TV, ADDR_TV, mCallback); @@ -128,7 +121,7 @@ public class HdmiCecControllerTest { @Test public void testAllocatLogicalAddress_AudioSystemNonPreferredNotOcupied() { mHdmiCecController.allocateLogicalAddress( - DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); + DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_AUDIO_SYSTEM, mLogicalAddress); } @@ -137,7 +130,7 @@ public class HdmiCecControllerTest { public void testAllocatLogicalAddress_AudioSystemNonPreferredAllOcupied() { mNativeWrapper.setPollAddressResponse(ADDR_AUDIO_SYSTEM, SendMessageResult.SUCCESS); mHdmiCecController.allocateLogicalAddress( - DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); + DEVICE_AUDIO_SYSTEM, ADDR_UNREGISTERED, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_UNREGISTERED, mLogicalAddress); } @@ -152,8 +145,7 @@ public class HdmiCecControllerTest { @Test public void testAllocatLogicalAddress_PlaybackPreferredOcuppied() { mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiCecController.allocateLogicalAddress( - DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback); + mHdmiCecController.allocateLogicalAddress(DEVICE_PLAYBACK, ADDR_PLAYBACK_1, mCallback); mTestLooper.dispatchAll(); assertEquals(ADDR_PLAYBACK_2, mLogicalAddress); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 474568305520..5e7a2522f025 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -24,11 +24,13 @@ import static com.google.common.truth.Truth.assertThat; import android.media.AudioManager; import android.os.Looper; +import android.os.SystemProperties; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; - import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; +import java.util.List; import java.util.ArrayList; +import java.util.Collections; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -36,9 +38,7 @@ import org.junit.runners.JUnit4; @SmallTest @RunWith(JUnit4.class) -/** - * Tests for {@link HdmiCecLocalDeviceAudioSystem} class. - */ +/** Tests for {@link HdmiCecLocalDeviceAudioSystem} class. */ public class HdmiCecLocalDeviceAudioSystemTest { private static final String TAG = "HdmiCecLocalDeviceAudioSystemTest"; @@ -55,66 +55,74 @@ public class HdmiCecLocalDeviceAudioSystemTest { @Before public void SetUp() { - mHdmiControlService = new HdmiControlService(null) { - @Override - AudioManager getAudioManager() { - return new AudioManager() { + mHdmiControlService = + new HdmiControlService(null) { @Override - public int getStreamVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicVolume; - default: - return 0; - } - } + AudioManager getAudioManager() { + return new AudioManager() { + @Override + public int getStreamVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicVolume; + default: + return 0; + } + } - @Override - public boolean isStreamMute(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMute; - default: - return false; - } - } + @Override + public boolean isStreamMute(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMute; + default: + return false; + } + } - @Override - public int getStreamMaxVolume(int streamType) { - switch (streamType) { - case STREAM_MUSIC: - return mMusicMaxVolume; - default: - return 100; - } - } + @Override + public int getStreamMaxVolume(int streamType) { + switch (streamType) { + case STREAM_MUSIC: + return mMusicMaxVolume; + default: + return 100; + } + } - @Override - public void adjustStreamVolume(int streamType, int direction, int flags) { - switch (streamType) { - case STREAM_MUSIC: - if (direction == AudioManager.ADJUST_UNMUTE) { - mMusicMute = false; - } else if (direction == AudioManager.ADJUST_MUTE) { - mMusicMute = true; + @Override + public void adjustStreamVolume( + int streamType, int direction, int flags) { + switch (streamType) { + case STREAM_MUSIC: + if (direction == AudioManager.ADJUST_UNMUTE) { + mMusicMute = false; + } else if (direction == AudioManager.ADJUST_MUTE) { + mMusicMute = true; + } + default: } - default: - } + } + + @Override + public void setWiredDeviceConnectionState( + int type, int state, String address, String name) { + // Do nothing. + } + }; } + + @Override + void wakeUp() {} }; - } - @Override - void wakeUp() { - } - }; mMyLooper = mTestLooper.getLooper(); mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService); mHdmiCecLocalDeviceAudioSystem.init(); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); @@ -123,105 +131,90 @@ public class HdmiCecLocalDeviceAudioSystemTest { // No TV device interacts with AVR so system audio control won't be turned on here mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mTestLooper.dispatchAll(); + SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true"); } + @Test public void handleGiveAudioStatus_volume_10_mute_true() { mMusicVolume = 10; mMusicMute = true; mMusicMaxVolume = 20; int scaledVolume = VolumeControlAction.scaleToCecVolume(10, mMusicMaxVolume); - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder.buildReportAudioStatus( - ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true); - HdmiCecMessage messageGive = HdmiCecMessageBuilder.buildGiveAudioStatus( - ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildReportAudioStatus( + ADDR_AUDIO_SYSTEM, ADDR_TV, scaledVolume, true); + HdmiCecMessage messageGive = + HdmiCecMessageBuilder.buildGiveAudioStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(messageGive)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } + @Test public void handleGiveSystemAudioModeStatus_originalOff() { - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); - HdmiCecMessage messageGive = HdmiCecMessageBuilder - .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessage messageGive = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isEqualTo(true); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - } - @Test - public void handleRequestArcInitiate() { - // TODO(b/80296911): Add tests when finishing handler impl. - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildInitiateArc(ADDR_AUDIO_SYSTEM, ADDR_TV); - HdmiCecMessage message = HdmiCecMessageBuilder - .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) - .isEqualTo(true); - mTestLooper.dispatchAll(); - assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - } - @Test - public void handleRequestArcTermination() { - // TODO(b/80297105): Add tests when finishing handler impl. - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildTerminateArc(ADDR_AUDIO_SYSTEM, ADDR_TV); - HdmiCecMessage messageRequestOff = HdmiCecMessageBuilder - .buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); - assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(messageRequestOff)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } + @Test public void handleSetSystemAudioMode_setOn_orignalOff() { mMusicMute = true; - HdmiCecMessage messageSet = HdmiCecMessageBuilder - .buildSetSystemAudioMode(ADDR_TV, ADDR_AUDIO_SYSTEM, true); - HdmiCecMessage messageGive = HdmiCecMessageBuilder - .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage messageSet = + HdmiCecMessageBuilder.buildSetSystemAudioMode(ADDR_TV, ADDR_AUDIO_SYSTEM, true); + HdmiCecMessage messageGive = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); // Check if originally off - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); // Check if correctly turned on - expectedMessage = HdmiCecMessageBuilder - .buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); + expectedMessage = + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, true); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSetSystemAudioMode(messageSet)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - assertThat(mMusicMute).isEqualTo(false); + assertThat(mMusicMute).isFalse(); } + @Test public void handleSystemAudioModeRequest_turnOffByTv() { - assertThat(mMusicMute).isEqualTo(false); + assertThat(mMusicMute).isFalse(); // Check if feature correctly turned off - HdmiCecMessage messageGive = HdmiCecMessageBuilder - .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); - HdmiCecMessage messageRequestOff = HdmiCecMessageBuilder - .buildSystemAudioModeRequest(ADDR_TV, ADDR_AUDIO_SYSTEM, 2, false); - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildSetSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); + HdmiCecMessage messageGive = + HdmiCecMessageBuilder.buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage messageRequestOff = + HdmiCecMessageBuilder.buildSystemAudioModeRequest( + ADDR_TV, ADDR_AUDIO_SYSTEM, 2, false); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleSystemAudioModeRequest(messageRequestOff)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - expectedMessage = HdmiCecMessageBuilder - .buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); + expectedMessage = + HdmiCecMessageBuilder.buildReportSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_TV, false); assertThat(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(messageGive)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - assertThat(mMusicMute).isEqualTo(true); + assertThat(mMusicMute).isTrue(); } + @Test public void onStandbyAudioSystem_currentSystemAudioControlOn() { // Set system audio control on first @@ -229,80 +222,93 @@ public class HdmiCecLocalDeviceAudioSystemTest { // Check if standby correctly turns off the feature mHdmiCecLocalDeviceAudioSystem.onStandby(false, STANDBY_SCREEN_OFF); mTestLooper.dispatchAll(); - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildSetSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); - assertThat(mMusicMute).isEqualTo(true); + assertThat(mMusicMute).isTrue(); } + @Test public void systemAudioControlOnPowerOn_alwaysOn() { - mHdmiCecLocalDeviceAudioSystem.removeAction( - SystemAudioInitiationActionFromAvr.class); + mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class); mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getActions(SystemAudioInitiationActionFromAvr.class)).isNotEmpty(); + assertThat( + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } + @Test public void systemAudioControlOnPowerOn_neverOn() { - mHdmiCecLocalDeviceAudioSystem.removeAction( - SystemAudioInitiationActionFromAvr.class); + mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class); mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.NEVER_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getActions(SystemAudioInitiationActionFromAvr.class)).isEmpty(); + assertThat( + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } + @Test public void systemAudioControlOnPowerOn_useLastState_off() { - mHdmiCecLocalDeviceAudioSystem.removeAction( - SystemAudioInitiationActionFromAvr.class); + mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class); mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, false); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getActions(SystemAudioInitiationActionFromAvr.class)).isEmpty(); + assertThat( + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isEmpty(); } + @Test public void systemAudioControlOnPowerOn_useLastState_on() { - mHdmiCecLocalDeviceAudioSystem.removeAction( - SystemAudioInitiationActionFromAvr.class); + mHdmiCecLocalDeviceAudioSystem.removeAction(SystemAudioInitiationActionFromAvr.class); mHdmiCecLocalDeviceAudioSystem.systemAudioControlOnPowerOn( Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, true); - assertThat(mHdmiCecLocalDeviceAudioSystem - .getActions(SystemAudioInitiationActionFromAvr.class)).isNotEmpty(); + assertThat( + mHdmiCecLocalDeviceAudioSystem.getActions( + SystemAudioInitiationActionFromAvr.class)) + .isNotEmpty(); } + @Test public void handleActiveSource_updateActiveSource() { - HdmiCecMessage message = HdmiCecMessageBuilder - .buildActiveSource(ADDR_TV, 0x0000); + HdmiCecMessage message = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); ActiveSource expectedActiveSource = new ActiveSource(ADDR_TV, 0x0000); assertThat(mHdmiCecLocalDeviceAudioSystem.handleActiveSource(message)) - .isEqualTo(true); + .isTrue(); mTestLooper.dispatchAll(); assertThat(mHdmiCecLocalDeviceAudioSystem.getActiveSource().equals(expectedActiveSource)) - .isEqualTo(true); + .isTrue(); } + @Test public void terminateSystemAudioMode_systemAudioModeOff() { mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(false); - assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isEqualTo(false); + assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse(); mMusicMute = false; - HdmiCecMessage message = HdmiCecMessageBuilder - .buildSetSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); + HdmiCecMessage message = + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode(); - assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isEqualTo(false); - assertThat(mMusicMute).isEqualTo(false); + assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse(); + assertThat(mMusicMute).isFalse(); assertThat(mNativeWrapper.getResultMessages()).doesNotContain(message); } + @Test public void terminateSystemAudioMode_systemAudioModeOn() { mHdmiCecLocalDeviceAudioSystem.setSystemAudioMode(true); - assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isEqualTo(true); + assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue(); mMusicMute = false; - HdmiCecMessage expectedMessage = HdmiCecMessageBuilder - .buildSetSystemAudioMode(ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); + HdmiCecMessage expectedMessage = + HdmiCecMessageBuilder.buildSetSystemAudioMode( + ADDR_AUDIO_SYSTEM, ADDR_BROADCAST, false); mHdmiCecLocalDeviceAudioSystem.terminateSystemAudioMode(); - assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isEqualTo(false); - assertThat(mMusicMute).isEqualTo(true); + assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isFalse(); + assertThat(mMusicMute).isTrue(); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } @@ -312,7 +318,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { int targetPhysicalAddress = 0x1000; mNativeWrapper.setPhysicalAddress(0x1000); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(true); + .isTrue(); } @Test @@ -320,7 +326,7 @@ public class HdmiCecLocalDeviceAudioSystemTest { int targetPhysicalAddress = 0x1100; mNativeWrapper.setPhysicalAddress(0x1000); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(true); + .isTrue(); } @Test @@ -328,21 +334,104 @@ public class HdmiCecLocalDeviceAudioSystemTest { int targetPhysicalAddress = 0x3000; mNativeWrapper.setPhysicalAddress(0x2000); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(false); + .isFalse(); targetPhysicalAddress = 0x2200; mNativeWrapper.setPhysicalAddress(0x3300); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(false); + .isFalse(); targetPhysicalAddress = 0x2213; mNativeWrapper.setPhysicalAddress(0x2212); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(false); + .isFalse(); targetPhysicalAddress = 0x2340; mNativeWrapper.setPhysicalAddress(0x2310); assertThat(mHdmiCecLocalDeviceAudioSystem.isPhysicalAddressMeOrBelow(targetPhysicalAddress)) - .isEqualTo(false); + .isFalse(); + } + + @Test + public void handleRequestArcInitiate_isNotDirectConnectedToTv() { + HdmiCecMessage message = HdmiCecMessageBuilder + .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage expectedMessage = HdmiCecMessageBuilder + .buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, ADDR_TV, + Constants.MESSAGE_REQUEST_ARC_INITIATION, + Constants.ABORT_NOT_IN_CORRECT_MODE); + mNativeWrapper.setPhysicalAddress(0x1100); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); + } + + @Test + public void handleRequestArcInitiate_startArcInitiationActionFromAvr() { + HdmiCecMessage message = HdmiCecMessageBuilder + .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); + mNativeWrapper.setPhysicalAddress(0x1000); + mHdmiCecLocalDeviceAudioSystem.removeAction( + ArcInitiationActionFromAvr.class); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isTrue(); + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getActions(ArcInitiationActionFromAvr.class)).isNotEmpty(); + } + + @Test + public void handleRequestArcTerminate_arcIsOn_startTerminationActionFromAvr() { + mHdmiCecLocalDeviceAudioSystem.setArcStatus(true); + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue(); + + HdmiCecMessage message = HdmiCecMessageBuilder + .buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); + mHdmiCecLocalDeviceAudioSystem.removeAction( + ArcTerminationActionFromAvr.class); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isTrue(); + mTestLooper.dispatchAll(); + assertThat(mHdmiCecLocalDeviceAudioSystem + .getActions(ArcTerminationActionFromAvr.class)).isNotEmpty(); + } + + @Test + public void handleRequestArcTerminate_arcIsNotOn() { + assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse(); + HdmiCecMessage message = HdmiCecMessageBuilder + .buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage expectedMessage = HdmiCecMessageBuilder + .buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, ADDR_TV, + Constants.MESSAGE_REQUEST_ARC_TERMINATION, + Constants.ABORT_NOT_IN_CORRECT_MODE); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message)) + .isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); + } + + @Test + public void handleRequestArcInit_arcIsNotSupported() { + HdmiCecMessage message = HdmiCecMessageBuilder + .buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM); + HdmiCecMessage expectedMessage = HdmiCecMessageBuilder + .buildFeatureAbortCommand( + ADDR_AUDIO_SYSTEM, ADDR_TV, + Constants.MESSAGE_REQUEST_ARC_INITIATION, + Constants.ABORT_UNRECOGNIZED_OPCODE); + SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "false"); + + assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcInitiate(message)) + .isTrue(); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 833ffa6b0c70..3cd84817bcac 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -27,13 +27,8 @@ import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; import android.hardware.hdmi.HdmiControlManager; -import android.hardware.hdmi.HdmiPortInfo; -import android.os.PowerManager; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; -import android.os.MessageQueue; -import com.android.server.hdmi.HdmiCecController.NativeWrapper; -import junit.framework.Assert; import java.util.Arrays; import org.junit.Before; import org.junit.Test; @@ -42,20 +37,19 @@ import org.junit.runners.JUnit4; @SmallTest @RunWith(JUnit4.class) -/** - * Tests for {@link HdmiCecLocalDevice} class. - */ +/** Tests for {@link HdmiCecLocalDevice} class. */ public class HdmiCecLocalDeviceTest { - private static int SendCecCommandFactory(int srcAddress, int dstAddress, byte[] body) { - switch(body[0] & 0xFF) { - /** {@link Constants#MESSAGE_GIVE_PHYSICAL_ADDRESS} */ + switch (body[0] & 0xFF) { + /** {@link Constants#MESSAGE_GIVE_PHYSICAL_ADDRESS} */ case MESSAGE_REPORT_PHYSICAL_ADDRESS: case MESSAGE_DEVICE_VENDOR_ID: - return srcAddress == mSrcAddr && - dstAddress == mDesAddr && - Arrays.equals(Arrays.copyOfRange(body, 1, body.length), param)? 0 : 1; + return srcAddress == mSrcAddr + && dstAddress == mDesAddr + && Arrays.equals(Arrays.copyOfRange(body, 1, body.length), param) + ? 0 + : 1; default: return 1; } @@ -63,15 +57,12 @@ public class HdmiCecLocalDeviceTest { private class MyHdmiCecLocalDevice extends HdmiCecLocalDevice { - protected MyHdmiCecLocalDevice(HdmiControlService service, int deviceType) { super(service, deviceType); } @Override - protected void onAddressAllocated(int logicalAddress, int reason) { - - } + protected void onAddressAllocated(int logicalAddress, int reason) {} @Override protected int getPreferredAddress() { @@ -79,9 +70,7 @@ public class HdmiCecLocalDeviceTest { } @Override - protected void setPreferredAddress(int addr) { - - } + protected void setPreferredAddress(int addr) {} } private MyHdmiCecLocalDevice mHdmiLocalDevice; @@ -100,42 +89,48 @@ public class HdmiCecLocalDeviceTest { @Before public void SetUp() { - mHdmiControlService = new HdmiControlService(null) { - @Override - boolean isControlEnabled() { - return isControlEnabled; - } - - @Override - boolean isPowerOnOrTransient() { - return mPowerStatus == HdmiControlManager.POWER_STATUS_ON - || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; - } - - @Override - void standby() { - mStandbyMessageReceived = true; - } - }; + mHdmiControlService = + new HdmiControlService(null) { + @Override + boolean isControlEnabled() { + return isControlEnabled; + } + + @Override + boolean isPowerOnOrTransient() { + return mPowerStatus == HdmiControlManager.POWER_STATUS_ON + || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; + } + + @Override + void standby() { + mStandbyMessageReceived = true; + } + }; mHdmiControlService.setIoLooper(mTestLooper.getLooper()); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, new FakeNativeWrapper()); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper( + mHdmiControlService, new FakeNativeWrapper()); mHdmiControlService.setCecController(mHdmiCecController); - mHdmiLocalDevice = new MyHdmiCecLocalDevice( - mHdmiControlService, DEVICE_TV); - mMessageValidator = new HdmiCecMessageValidator(mHdmiControlService){ - @Override - int isValid(HdmiCecMessage message) { - return HdmiCecMessageValidator.OK; - } - }; + mHdmiLocalDevice = new MyHdmiCecLocalDevice(mHdmiControlService, DEVICE_TV); + mMessageValidator = + new HdmiCecMessageValidator(mHdmiControlService) { + @Override + int isValid(HdmiCecMessage message) { + return HdmiCecMessageValidator.OK; + } + }; mHdmiControlService.setMessageValidator(mMessageValidator); } @Test public void dispatchMessage_desNotValid() { - HdmiCecMessage msg = new HdmiCecMessage( - ADDR_TV, ADDR_TV, Constants.MESSAGE_CEC_VERSION, HdmiCecMessage.EMPTY_PARAM); + HdmiCecMessage msg = + new HdmiCecMessage( + ADDR_TV, + ADDR_TV, + Constants.MESSAGE_CEC_VERSION, + HdmiCecMessage.EMPTY_PARAM); boolean handleResult = mHdmiLocalDevice.dispatchMessage(msg); assertFalse(handleResult); } @@ -144,18 +139,19 @@ public class HdmiCecLocalDeviceTest { public void handleGivePhysicalAddress_success() { mSrcAddr = ADDR_UNREGISTERED; mDesAddr = ADDR_BROADCAST; - param = new byte[] { - (byte) ((mPhysicalAddr >> 8) & 0xFF), - (byte) (mPhysicalAddr & 0xFF), - (byte) (DEVICE_TV & 0xFF) - }; + param = + new byte[] { + (byte) ((mPhysicalAddr >> 8) & 0xFF), + (byte) (mPhysicalAddr & 0xFF), + (byte) (DEVICE_TV & 0xFF) + }; callbackResult = -1; - boolean handleResult = mHdmiLocalDevice.handleGivePhysicalAddress( - (int finalResult) -> callbackResult = finalResult); + boolean handleResult = + mHdmiLocalDevice.handleGivePhysicalAddress( + (int finalResult) -> callbackResult = finalResult); mTestLooper.dispatchAll(); /** - * Test if CecMessage is sent successfully - * SendMessageResult#SUCCESS is defined in HAL as 0 + * Test if CecMessage is sent successfully SendMessageResult#SUCCESS is defined in HAL as 0 */ assertEquals(0, callbackResult); assertTrue(handleResult); @@ -166,14 +162,10 @@ public class HdmiCecLocalDeviceTest { mSrcAddr = ADDR_UNREGISTERED; mDesAddr = ADDR_BROADCAST; /** nativeGetVendorId returns 0 */ - param = new byte[] { - (byte) ((0 >> 8) & 0xFF), - (byte) (0 & 0xFF), - (byte) (0 & 0xFF) - }; + param = new byte[] {(byte) ((0 >> 8) & 0xFF), (byte) (0 & 0xFF), (byte) (0 & 0xFF)}; callbackResult = -1; mHdmiLocalDevice.handleGiveDeviceVendorId( - (int finalResult) -> callbackResult = finalResult); + (int finalResult) -> callbackResult = finalResult); mTestLooper.dispatchAll(); assertEquals(0, callbackResult); } @@ -184,7 +176,7 @@ public class HdmiCecLocalDeviceTest { isControlEnabled = true; assertFalse(mStandbyMessageReceived); mHdmiLocalDevice.handleStandby( - HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_AUDIO_SYSTEM)); + HdmiCecMessageBuilder.buildStandby(ADDR_TV, ADDR_AUDIO_SYSTEM)); assertTrue(mStandbyMessageReceived); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java index a7618bf59b70..1ca48d433624 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageBuilderTest.java @@ -15,8 +15,9 @@ */ package com.android.server.hdmi; -import static com.android.server.hdmi.Constants.ADDR_BROADCAST; +import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1; +import static com.android.server.hdmi.Constants.ADDR_TV; import static com.google.common.truth.Truth.assertThat; import android.hardware.hdmi.HdmiDeviceInfo; @@ -35,12 +36,33 @@ public class HdmiCecMessageBuilderTest { HdmiCecMessage message = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( ADDR_PLAYBACK_1, 0x1234, HdmiDeviceInfo.DEVICE_PLAYBACK); - assertThat(message) - .isEqualTo( - new HdmiCecMessage( - ADDR_PLAYBACK_1, - ADDR_BROADCAST, - Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS, - new byte[] {0x12, 0x34, 0x04})); + assertThat(message).isEqualTo(buildMessage("4f:84:12:34:04")); + } + + @Test + public void buildRequestShortAudioDescriptor() { + HdmiCecMessage message = + HdmiCecMessageBuilder.buildRequestShortAudioDescriptor( + ADDR_TV, + ADDR_AUDIO_SYSTEM, + new int[] {Constants.AUDIO_CODEC_AAC, Constants.AUDIO_CODEC_LPCM}); + assertThat(message).isEqualTo(buildMessage("05:A4:06:01")); + } + + /** + * Build a CEC message from a hex byte string with bytes separated by {@code :}. + * + * <p>This format is used by both cec-client and www.cec-o-matic.com + */ + private static HdmiCecMessage buildMessage(String message) { + String[] parts = message.split(":"); + int src = Integer.parseInt(parts[0].substring(0, 1), 16); + int dest = Integer.parseInt(parts[0].substring(1, 2), 16); + int opcode = Integer.parseInt(parts[1], 16); + byte[] params = new byte[parts.length - 2]; + for (int i = 0; i < params.length; i++) { + params[i] = Byte.parseByte(parts[i + 2], 16); + } + return new HdmiCecMessage(src, dest, opcode, params); } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 90ad34982421..7de637b2df3c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -30,9 +30,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests for {@link HdmiControlService} class. - */ +/** Tests for {@link HdmiControlService} class. */ @SmallTest @RunWith(JUnit4.class) public class HdmiControlServiceTest { @@ -48,9 +46,7 @@ public class HdmiControlServiceTest { } @Override - protected void onAddressAllocated(int logicalAddress, int reason) { - - } + protected void onAddressAllocated(int logicalAddress, int reason) {} @Override protected int getPreferredAddress() { @@ -58,9 +54,7 @@ public class HdmiControlServiceTest { } @Override - protected void setPreferredAddress(int addr) { - - } + protected void setPreferredAddress(int addr) {} @Override protected boolean canGoToStandby() { @@ -68,8 +62,8 @@ public class HdmiControlServiceTest { } @Override - protected void disableDevice(boolean initiatedByCec, - final PendingActionClearedCallback originalCallback) { + protected void disableDevice( + boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { mIsDisabled = true; originalCallback.onCleared(this); } @@ -105,26 +99,26 @@ public class HdmiControlServiceTest { @Before public void SetUp() { - mHdmiControlService = new HdmiControlService(null) { - @Override - boolean isStandbyMessageReceived() { - return mStandbyMessageReceived; - } - }; + mHdmiControlService = + new HdmiControlService(null) { + @Override + boolean isStandbyMessageReceived() { + return mStandbyMessageReceived; + } + }; mMyLooper = mTestLooper.getLooper(); - mMyAudioSystemDevice = new HdmiCecLocalDeviceMyDevice( - mHdmiControlService, DEVICE_AUDIO_SYSTEM); - mMyPlaybackDevice = new HdmiCecLocalDeviceMyDevice( - mHdmiControlService, DEVICE_PLAYBACK); + mMyAudioSystemDevice = + new HdmiCecLocalDeviceMyDevice(mHdmiControlService, DEVICE_AUDIO_SYSTEM); + mMyPlaybackDevice = new HdmiCecLocalDeviceMyDevice(mHdmiControlService, DEVICE_PLAYBACK); mMyAudioSystemDevice.init(); mMyPlaybackDevice.init(); mHdmiControlService.setIoLooper(mMyLooper); mNativeWrapper = new FakeNativeWrapper(); - mHdmiCecController = HdmiCecController.createWithNativeWrapper( - mHdmiControlService, mNativeWrapper); + mHdmiCecController = + HdmiCecController.createWithNativeWrapper(mHdmiControlService, mNativeWrapper); mHdmiControlService.setCecController(mHdmiCecController); mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService)); mHdmiControlService.setMessageValidator(new HdmiCecMessageValidator(mHdmiControlService)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index a1614f214850..e6ff1436468e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -26,15 +26,12 @@ import android.media.AudioManager; import android.os.Looper; import android.os.test.TestLooper; import android.support.test.filters.SmallTest; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -/** - * Tests for {@link SystemAudioInitiationActionFromAvr} - */ +/** Tests for {@link SystemAudioInitiationActionFromAvr} */ @SmallTest @RunWith(JUnit4.class) public class SystemAudioInitiationActionFromAvrTest { @@ -54,88 +51,86 @@ public class SystemAudioInitiationActionFromAvrTest { @Before public void SetUp() { mDeviceInfoForTests = new HdmiDeviceInfo(1001, 1234); - HdmiControlService hdmiControlService = new HdmiControlService(null) { - - @Override - void sendCecCommand(HdmiCecMessage command, - @Nullable SendMessageCallback callback) { - switch (command.getOpcode()) { - case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: - mMsgRequestActiveSourceCount++; - if (mTryCountBeforeSucceed >= mMsgRequestActiveSourceCount - && callback != null) { - callback.onSendCompleted(SendMessageResult.NACK); - break; - } - if (mShouldDispatchActiveSource) { - mHdmiCecLocalDeviceAudioSystem.dispatchMessage( - HdmiCecMessageBuilder.buildActiveSource( - Constants.ADDR_TV, 1002)); - } - break; - case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: - mMsgSetSystemAudioModeCount++; - if (mTryCountBeforeSucceed >= mMsgSetSystemAudioModeCount - && callback != null) { - callback.onSendCompleted(SendMessageResult.NACK); - } - break; - default: - throw new IllegalArgumentException("Unexpected message"); - } - } - - @Override - AudioManager getAudioManager() { - return new AudioManager() { + HdmiControlService hdmiControlService = + new HdmiControlService(null) { @Override - public int setHdmiSystemAudioSupported(boolean on) { - return 0; + void sendCecCommand( + HdmiCecMessage command, @Nullable SendMessageCallback callback) { + switch (command.getOpcode()) { + case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: + mMsgRequestActiveSourceCount++; + if (mTryCountBeforeSucceed >= mMsgRequestActiveSourceCount + && callback != null) { + callback.onSendCompleted(SendMessageResult.NACK); + break; + } + if (mShouldDispatchActiveSource) { + mHdmiCecLocalDeviceAudioSystem.dispatchMessage( + HdmiCecMessageBuilder.buildActiveSource( + Constants.ADDR_TV, 1002)); + } + break; + case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: + mMsgSetSystemAudioModeCount++; + if (mTryCountBeforeSucceed >= mMsgSetSystemAudioModeCount + && callback != null) { + callback.onSendCompleted(SendMessageResult.NACK); + } + break; + default: + throw new IllegalArgumentException("Unexpected message"); + } } @Override - public int getStreamVolume(int streamType) { - return 0; + AudioManager getAudioManager() { + return new AudioManager() { + + @Override + public int setHdmiSystemAudioSupported(boolean on) { + return 0; + } + + @Override + public int getStreamVolume(int streamType) { + return 0; + } + + @Override + public boolean isStreamMute(int streamType) { + return false; + } + + @Override + public int getStreamMaxVolume(int streamType) { + return 100; + } + + @Override + public void adjustStreamVolume( + int streamType, int direction, int flags) {} + }; } @Override - public boolean isStreamMute(int streamType) { + boolean isPowerStandby() { return false; } @Override - public int getStreamMaxVolume(int streamType) { - return 100; + boolean isAddressAllocated() { + return true; } @Override - public void adjustStreamVolume(int streamType, int direction, int flags) { + void wakeUp() {} + @Override + int getPhysicalAddress() { + return 0; } }; - } - - @Override - boolean isPowerStandby() { - return false; - } - - @Override - boolean isAddressAllocated() { - return true; - } - - @Override - void wakeUp() { - - } - - @Override - int getPhysicalAddress() { - return 0; - } - }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { @Override @@ -210,7 +205,6 @@ public class SystemAudioInitiationActionFromAvrTest { assertTrue(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()); assertThat(mHdmiCecLocalDeviceAudioSystem.mActiveSource.physicalAddress).isEqualTo(1002); - } @Test diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenAnimationTests.java new file mode 100644 index 000000000000..c3907ff8ddb4 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenAnimationTests.java @@ -0,0 +1,118 @@ +/* + * 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.server.wm; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.SurfaceControl.Transaction; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.SurfaceControl; + +import com.android.server.wm.WindowTestUtils.TestAppWindowToken; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Animation related tests for the {@link AppWindowToken} class. + * + * Build/Install/Run: + * atest FrameworksServicesTests:com.android.server.wm.AppWindowTokenAnimationTests + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AppWindowTokenAnimationTests extends WindowTestsBase { + + private TestAppWindowToken mToken; + + @Mock + private Transaction mTransaction; + @Mock + private AnimationAdapter mSpec; + + @Override + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + + mToken = createTestAppWindowToken(mDisplayContent, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD); + mToken.setPendingTransaction(mTransaction); + } + + @Test + public void clipAfterAnim_boundsLayerIsCreated() throws Exception { + mToken.mNeedsAnimationBoundsLayer = true; + + mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), + eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + verify(mTransaction).reparent(eq(mToken.mSurfaceAnimator.mLeash), + eq(mToken.mAnimationBoundsLayer.getHandle())); + } + + @Test + public void clipAfterAnim_boundsLayerIsDestroyed() throws Exception { + mToken.mNeedsAnimationBoundsLayer = true; + mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + final SurfaceControl leash = mToken.mSurfaceAnimator.mLeash; + final SurfaceControl animationBoundsLayer = mToken.mAnimationBoundsLayer; + final ArgumentCaptor<SurfaceAnimator.OnAnimationFinishedCallback> callbackCaptor = + ArgumentCaptor.forClass( + SurfaceAnimator.OnAnimationFinishedCallback.class); + verify(mSpec).startAnimation(any(), any(), callbackCaptor.capture()); + + callbackCaptor.getValue().onAnimationFinished(mSpec); + verify(mTransaction).destroy(eq(leash)); + verify(mTransaction).destroy(eq(animationBoundsLayer)); + assertThat(mToken.mNeedsAnimationBoundsLayer).isFalse(); + } + + @Test + public void clipAfterAnimCancelled_boundsLayerIsDestroyed() throws Exception { + mToken.mNeedsAnimationBoundsLayer = true; + mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + final SurfaceControl leash = mToken.mSurfaceAnimator.mLeash; + final SurfaceControl animationBoundsLayer = mToken.mAnimationBoundsLayer; + + mToken.mSurfaceAnimator.cancelAnimation(); + verify(mTransaction).destroy(eq(leash)); + verify(mTransaction).destroy(eq(animationBoundsLayer)); + assertThat(mToken.mNeedsAnimationBoundsLayer).isFalse(); + } + + @Test + public void clipNoneAnim_boundsLayerIsNotCreated() throws Exception { + mToken.mNeedsAnimationBoundsLayer = false; + + mToken.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */); + verify(mTransaction).reparent(eq(mToken.getSurfaceControl()), + eq(mToken.mSurfaceAnimator.mLeash.getHandle())); + assertThat(mToken.mAnimationBoundsLayer).isNull(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java index ee028ba4262b..e4f181be857c 100644 --- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.annotation.Nullable; @@ -159,9 +160,11 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { final WindowManagerService wm = mWmSupplier.get(); synchronized (wm.mWindowMap) { atoken = wm.mRoot.getAppWindowToken(appToken); + IWindow iWindow = mock(IWindow.class); + doReturn(mock(IBinder.class)).when(iWindow).asBinder(); window = WindowTestsBase.createWindow(null, TYPE_APPLICATION_STARTING, atoken, "Starting window", 0 /* ownerId */, false /* internalWindows */, wm, - mock(Session.class), mock(IWindow.class)); + mock(Session.class), iWindow); atoken.startingWindow = window; } if (mRunnableWhenAddingSplashScreen != null) { diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java index ca520ed76be6..9dc00251896a 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowAnimationSpecTest.java @@ -70,8 +70,6 @@ public class WindowAnimationSpecTest { true /* isAppAnimation */); windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - verify(mTransaction).setFinalCrop(eq(mSurfaceControl), - argThat(rect -> rect.equals(mStackBounds))); } @Test @@ -83,9 +81,6 @@ public class WindowAnimationSpecTest { true /* isAppAnimation */); windowAnimationSpec.apply(mTransaction, mSurfaceControl, 0); verify(mTransaction).setWindowCrop(eq(mSurfaceControl), argThat(Rect::isEmpty)); - verify(mTransaction).setFinalCrop(eq(mSurfaceControl), - argThat(rect -> rect.left == 20 && rect.top == 40 && rect.right == 30 - && rect.bottom == 50)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java index 2e4740b57255..a48a3b9f8022 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java @@ -24,6 +24,8 @@ import android.os.Binder; import android.os.IBinder; import android.view.IApplicationToken; import android.view.IWindow; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; import android.view.WindowManager; import static android.app.AppOpsManager.OP_NONE; @@ -113,6 +115,7 @@ public class WindowTestUtils { /** Used so we can gain access to some protected members of the {@link AppWindowToken} class. */ public static class TestAppWindowToken extends AppWindowToken { boolean mOnTop = false; + private Transaction mPendingTransactionOverride; private TestAppWindowToken(DisplayContent dc) { super(dc.mService, new IApplicationToken.Stub() { @@ -158,6 +161,17 @@ public class WindowTestUtils { boolean isOnTop() { return mOnTop; } + + void setPendingTransaction(Transaction transaction) { + mPendingTransactionOverride = transaction; + } + + @Override + public Transaction getPendingTransaction() { + return mPendingTransactionOverride == null + ? super.getPendingTransaction() + : mPendingTransactionOverride; + } } static TestWindowToken createTestWindowToken(int type, DisplayContent dc) { diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java index 473a287e3d9c..ef019fe5ee27 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java @@ -231,6 +231,11 @@ class WindowTestsBase { } AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) { + return createTestAppWindowToken(dc, windowingMode, activityType); + } + + WindowTestUtils.TestAppWindowToken createTestAppWindowToken(DisplayContent dc, int + windowingMode, int activityType) { final TaskStack stack = createStackControllerOnStackOnDisplay(windowingMode, activityType, dc).mContainer; final Task task = createTaskInStack(stack, 0 /* userId */); diff --git a/tests/Internal/res/xml/livewallpaper.xml b/tests/Internal/res/xml/livewallpaper.xml index dbb0e4761cba..6b5e84e8f9ad 100644 --- a/tests/Internal/res/xml/livewallpaper.xml +++ b/tests/Internal/res/xml/livewallpaper.xml @@ -16,5 +16,4 @@ --> <wallpaper xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - androidprv:supportsAmbientMode="true"/>
\ No newline at end of file + android:supportsAmbientMode="true"/>
\ No newline at end of file diff --git a/tests/Internal/src/android/app/WallpaperInfoTest.java b/tests/Internal/src/android/app/WallpaperInfoTest.java index 9d26270fb96a..98045ae98541 100644 --- a/tests/Internal/src/android/app/WallpaperInfoTest.java +++ b/tests/Internal/src/android/app/WallpaperInfoTest.java @@ -55,13 +55,13 @@ public class WallpaperInfoTest { // Defined as true in the XML assertTrue("supportsAmbientMode should be true, as defined in the XML.", - wallpaperInfo.getSupportsAmbientMode()); + wallpaperInfo.supportsAmbientMode()); Parcel parcel = Parcel.obtain(); wallpaperInfo.writeToParcel(parcel, 0 /* flags */); parcel.setDataPosition(0); WallpaperInfo fromParcel = WallpaperInfo.CREATOR.createFromParcel(parcel); assertTrue("supportsAmbientMode should have been restored from parcelable", - fromParcel.getSupportsAmbientMode()); + fromParcel.supportsAmbientMode()); parcel.recycle(); } } diff --git a/tests/SystemMemoryTest/Android.mk b/tests/SystemMemoryTest/Android.mk new file mode 100644 index 000000000000..09a1618c6ad1 --- /dev/null +++ b/tests/SystemMemoryTest/Android.mk @@ -0,0 +1,17 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(call all-subdir-makefiles) diff --git a/tests/SystemMemoryTest/README.txt b/tests/SystemMemoryTest/README.txt new file mode 100644 index 000000000000..de5042cf3732 --- /dev/null +++ b/tests/SystemMemoryTest/README.txt @@ -0,0 +1,21 @@ +This directory contains a test for system server memory use. + +Directory structure +=================== +device + - those parts of the test that run on device. + +host + - those parts of the test that run on host. + +Running the test +================ + +You can manually run the test as follows: + + make tradefed-all system-memory-test SystemMemoryTestDevice + tradefed.sh run commandAndExit template/local_min --template:map test=system-memory-test + +This installs and runs the test on device. You can see the metrics in the +tradefed output. + diff --git a/tests/SystemMemoryTest/device/Android.mk b/tests/SystemMemoryTest/device/Android.mk new file mode 100644 index 000000000000..75408df4b295 --- /dev/null +++ b/tests/SystemMemoryTest/device/Android.mk @@ -0,0 +1,24 @@ +# 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. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS) +LOCAL_PACKAGE_NAME := SystemMemoryTestDevice +LOCAL_SDK_VERSION := current +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_COMPATIBILITY_SUITE := general-tests +include $(BUILD_PACKAGE) diff --git a/tests/SystemMemoryTest/device/AndroidManifest.xml b/tests/SystemMemoryTest/device/AndroidManifest.xml new file mode 100644 index 000000000000..e5e7844f9660 --- /dev/null +++ b/tests/SystemMemoryTest/device/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.tests.sysmem.device"> + + <uses-sdk android:minSdkVersion="19" /> + + <instrumentation + android:name="com.android.tests.sysmem.device.Cujs" + android:targetPackage="com.android.tests.sysmem.device" /> + + <application + android:allowBackup="false" + android:label="System Memory Test"> + </application> +</manifest> diff --git a/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java b/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java new file mode 100644 index 000000000000..6c0c5931ed8c --- /dev/null +++ b/tests/SystemMemoryTest/device/src/com/android/tests/sysmem/device/Cujs.java @@ -0,0 +1,53 @@ +/* + * 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.sysmem.device; + +import android.app.Activity; +import android.app.Instrumentation; +import android.os.Bundle; +import android.util.Log; + +/** + * Critical user journeys used to exercise the system for test, driven from + * the device. + */ +public class Cujs extends Instrumentation { + + private static final String TAG = "SystemMemoryTest"; + + @Override + public void onCreate(Bundle arguments) { + start(); + } + + @Override + public void onStart() { + // TODO: Exercise the system in more interesting ways. + // Mostly what matters is that whatever we do here is sustainable: it + // can be repeated indefinitely on the system without causing + // problems. For example, launching activities is fine. Installing + // applications is only fine if we also uninstall them as part of the + // test. + try { + Log.i(TAG, "Sleeping for 10 seconds..."); + Thread.sleep(10 * 1000); + } catch (InterruptedException ignored) { + } + + finish(Activity.RESULT_OK, null); + } +} diff --git a/tests/SystemMemoryTest/host/Android.mk b/tests/SystemMemoryTest/host/Android.mk new file mode 100644 index 000000000000..a516e38adfec --- /dev/null +++ b/tests/SystemMemoryTest/host/Android.mk @@ -0,0 +1,23 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := $(call all-java-files-under, src) +LOCAL_MODULE := system-memory-test +LOCAL_MODULE_TAGS := optional +LOCAL_JAVA_LIBRARIES := tradefed +LOCAL_COMPATIBILITY_SUITE := general-tests +include $(BUILD_HOST_JAVA_LIBRARY) diff --git a/tests/SystemMemoryTest/host/AndroidTest.xml b/tests/SystemMemoryTest/host/AndroidTest.xml new file mode 100644 index 000000000000..6d2c95f3094a --- /dev/null +++ b/tests/SystemMemoryTest/host/AndroidTest.xml @@ -0,0 +1,25 @@ +<?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. +--> +<configuration description="Runs the system memory tests"> + <option name="test-suite-tag" value="system-memory-test" /> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true" /> + <option name="test-file-name" value="SystemMemoryTestDevice.apk" /> + </target_preparer> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.sysmem.host.MemoryTest" /> + </test> +</configuration> diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java new file mode 100644 index 000000000000..579d9723977f --- /dev/null +++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Cujs.java @@ -0,0 +1,43 @@ +/* + * 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.sysmem.host; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; + +/** + * Critical user journeys with which to exercise the system, driven from the + * host. + */ +public class Cujs { + private ITestDevice device; + + public Cujs(ITestDevice device) { + this.device = device; + } + + /** + * Runs the critical user journeys. + */ + public void run() throws DeviceNotAvailableException { + // Invoke the Device Cujs instrumentation to run the cujs. + // TODO: Consider exercising the system in other interesting ways as + // well. + String command = "am instrument -w com.android.tests.sysmem.device/.Cujs"; + device.executeShellCommand(command); + } +} diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java new file mode 100644 index 000000000000..bbec0654b7ea --- /dev/null +++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/MemoryTest.java @@ -0,0 +1,79 @@ +/* + * 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.sysmem.host; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics; +import com.android.tradefed.testtype.IDeviceTest; +import java.io.IOException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(DeviceJUnit4ClassRunner.class) +public class MemoryTest implements IDeviceTest { + + @Rule public TestMetrics testMetrics = new TestMetrics(); + @Rule public TestLogData testLogs = new TestLogData(); + + private ITestDevice testDevice; + private int iterations = 0; // Number of times cujs have been run. + private Metrics metrics; + private Cujs cujs; + + @Override + public void setDevice(ITestDevice device) { + testDevice = device; + metrics = new Metrics(device, testMetrics, testLogs); + cujs = new Cujs(device); + } + + @Override + public ITestDevice getDevice() { + return testDevice; + } + + // Invoke a single iteration of running the cujs. + private void runCujs() throws DeviceNotAvailableException { + cujs.run(); + iterations++; + } + + // Sample desired memory. + private void sample() + throws DeviceNotAvailableException, IOException, Metrics.MetricsException { + metrics.sample(String.format("%03d", iterations)); + } + + @Test + public void run() throws Exception { + sample(); // Sample before running cujs + runCujs(); + sample(); // Sample after first iteration of cujs + + // Run cujs in a loop to highlight memory leaks. + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 5; j++) { + runCujs(); + } + sample(); + } + } +} diff --git a/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java new file mode 100644 index 000000000000..7de092a973ad --- /dev/null +++ b/tests/SystemMemoryTest/host/src/com/android/tests/sysmem/host/Metrics.java @@ -0,0 +1,148 @@ +/* + * 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.sysmem.host; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.result.FileInputStreamSource; +import com.android.tradefed.result.LogDataType; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.InputMismatchException; +import java.util.Scanner; + +/** + * Utilities for sampling and reporting memory metrics. + */ +class Metrics { + + private ITestDevice device; + private TestMetrics metrics; + private TestLogData logs; + + /** + * Exception thrown in case of error sampling metrics. + */ + public static class MetricsException extends Exception { + public MetricsException(String msg) { + super(msg); + } + + MetricsException(String msg, Exception cause) { + super(msg, cause); + } + } + + /** + * Constructs a metrics instance that will output high level metrics and + * more detailed breakdowns using the given <code>metrics</code> and + * <code>logs</code> objects. + * + * @param device the device to sample metrics from + * @param metrics where to log the high level metrics when taking a sample + * @param logs where to log detailed breakdowns when taking a sample + */ + public Metrics(ITestDevice device, TestMetrics metrics, TestLogData logs) { + this.device = device; + this.metrics = metrics; + this.logs = logs; + } + + /** + * Returns the pid for the process with the given name. + */ + private int getPidForProcess(String name) + throws DeviceNotAvailableException, IOException, MetricsException { + String psout = device.executeShellCommand("ps -A -o PID,CMD"); + Scanner sc = new Scanner(psout); + try { + // ps output is of the form: + // PID CMD + // 1 init + // 2 kthreadd + // ... + // 9693 ps + sc.nextLine(); + while (sc.hasNextLine()) { + int pid = sc.nextInt(); + String cmd = sc.next(); + + if (name.equals(cmd)) { + return pid; + } + } + } catch (InputMismatchException e) { + throw new MetricsException("unexpected ps output format: " + psout, e); + } + + throw new MetricsException("failed to get pid for process " + name); + } + + /** + * Samples the current memory use on the system. Outputs high level test + * metrics and detailed breakdowns to the TestMetrics and TestLogData + * objects provided when constructing this Metrics instance. The metrics + * and log names are prefixed with the given label. + * + * @param label prefix to use for metrics and logs output for this sample. + */ + void sample(String label) throws DeviceNotAvailableException, IOException, MetricsException { + // adb root access is required to get showmap + device.enableAdbRoot(); + + int pid = getPidForProcess("system_server"); + + // Read showmap for system server and add it as a test log + String showmap = device.executeShellCommand("showmap " + pid); + String showmapLabel = label + ".system_server.showmap"; + File file = File.createTempFile(showmapLabel, "txt"); + PrintStream ps = new PrintStream(file); + ps.print(showmap); + try (FileInputStreamSource dataStream = new FileInputStreamSource(file)) { + logs.addTestLog(showmapLabel, LogDataType.TEXT, dataStream); + } + + // Extract VSS, PSS and RSS from the showmap and output them as metrics. + // The last lines of the showmap output looks something like: + // virtual shared shared private private + // size RSS PSS clean dirty clean dirty swap swapPSS # object + //-------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------ + // 928480 113016 24860 87348 7916 3632 14120 1968 1968 1900 TOTAL + try { + int pos = showmap.lastIndexOf("----"); + Scanner sc = new Scanner(showmap.substring(pos)); + sc.next(); + long vss = sc.nextLong(); + long rss = sc.nextLong(); + long pss = sc.nextLong(); + + metrics.addTestMetric(String.format("%s.system_server.vss", label), Long.toString(vss)); + metrics.addTestMetric(String.format("%s.system_server.rss", label), Long.toString(rss)); + metrics.addTestMetric(String.format("%s.system_server.pss", label), Long.toString(pss)); + } catch (InputMismatchException e) { + throw new MetricsException("unexpected showmap format", e); + } + + // TODO: Experiment with other additional metrics. + + // TODO: Consider launching an instrumentation to collect metrics from + // within the device itself. + } +} diff --git a/tests/net/java/android/net/ip/IpClientTest.java b/tests/net/java/android/net/ip/IpClientTest.java index 89453e0b13b7..5a8d2cd0c5a2 100644 --- a/tests/net/java/android/net/ip/IpClientTest.java +++ b/tests/net/java/android/net/ip/IpClientTest.java @@ -32,7 +32,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AlarmManager; -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.net.INetd; @@ -62,8 +61,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import java.net.Inet4Address; -import java.net.Inet6Address; import java.net.InetAddress; import java.util.Arrays; import java.util.List; @@ -84,6 +81,7 @@ public class IpClientTest { private static final int TEST_IFINDEX = 1001; // See RFC 7042#section-2.1.2 for EUI-48 documentation values. private static final MacAddress TEST_MAC = MacAddress.fromString("00:00:5E:00:53:01"); + private static final int TEST_TIMEOUT_MS = 200; @Mock private Context mContext; @Mock private INetworkManagementService mNMService; @@ -126,8 +124,8 @@ public class IpClientTest { private IpClient makeIpClient(String ifname) throws Exception { setTestInterfaceParams(ifname); final IpClient ipc = new IpClient(mContext, ifname, mCb, mDependecies); - verify(mNMService, timeout(100).times(1)).disableIpv6(ifname); - verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(ifname); + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(ifname); + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(ifname); ArgumentCaptor<BaseNetworkObserver> arg = ArgumentCaptor.forClass(BaseNetworkObserver.class); verify(mNMService, times(1)).registerObserver(arg.capture()); @@ -200,13 +198,13 @@ public class IpClientTest { ipc.startProvisioning(config); verify(mCb, times(1)).setNeighborDiscoveryOffload(true); - verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false); verify(mCb, never()).onProvisioningFailure(any()); ipc.shutdown(); - verify(mNMService, timeout(100).times(1)).disableIpv6(iface); - verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface); - verify(mCb, timeout(100).times(1)) + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface); + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); } @@ -230,12 +228,12 @@ public class IpClientTest { ipc.startProvisioning(config); verify(mCb, times(1)).setNeighborDiscoveryOffload(true); - verify(mCb, timeout(100).times(1)).setFallbackMulticastFilter(false); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).setFallbackMulticastFilter(false); verify(mCb, never()).onProvisioningFailure(any()); for (String addr : addresses) { String[] parts = addr.split("/"); - verify(mNetd, timeout(100).times(1)) + verify(mNetd, timeout(TEST_TIMEOUT_MS).times(1)) .interfaceAddAddress(iface, parts[0], Integer.parseInt(parts[1])); } @@ -244,7 +242,7 @@ public class IpClientTest { // Add N - 1 addresses for (int i = 0; i < lastAddr; i++) { mObserver.addressUpdated(iface, new LinkAddress(addresses[i])); - verify(mCb, timeout(100)).onLinkPropertiesChange(any()); + verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(any()); reset(mCb); } @@ -252,12 +250,12 @@ public class IpClientTest { mObserver.addressUpdated(iface, new LinkAddress(addresses[lastAddr])); LinkProperties want = linkproperties(links(addresses), routes(prefixes)); want.setInterfaceName(iface); - verify(mCb, timeout(100).times(1)).onProvisioningSuccess(eq(want)); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)).onProvisioningSuccess(eq(want)); ipc.shutdown(); - verify(mNMService, timeout(100).times(1)).disableIpv6(iface); - verify(mNMService, timeout(100).times(1)).clearInterfaceAddresses(iface); - verify(mCb, timeout(100).times(1)) + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).disableIpv6(iface); + verify(mNMService, timeout(TEST_TIMEOUT_MS).times(1)).clearInterfaceAddresses(iface); + verify(mCb, timeout(TEST_TIMEOUT_MS).times(1)) .onLinkPropertiesChange(eq(makeEmptyLinkProperties(iface))); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 002953b714a2..142c88b2f67d 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -198,6 +198,9 @@ public class ConnectivityServiceTest { // (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are // supposedly fired, and the time we call expectCallback. private final static int TEST_CALLBACK_TIMEOUT_MS = 200; + // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to + // complete before callbacks are verified. + private final static int TEST_REQUEST_TIMEOUT_MS = 150; private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; @@ -3212,12 +3215,12 @@ public class ConnectivityServiceTest { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); - final int timeoutMs = 150; - mCm.requestNetwork(nr, networkCallback, timeoutMs); + mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, timeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, + TEST_CALLBACK_TIMEOUT_MS); // pass timeout and validate that UNAVAILABLE is not called networkCallback.assertNoCallback(); @@ -3232,13 +3235,12 @@ public class ConnectivityServiceTest { NetworkRequest nr = new NetworkRequest.Builder().addTransportType( NetworkCapabilities.TRANSPORT_WIFI).build(); final TestNetworkCallback networkCallback = new TestNetworkCallback(); - final int requestTimeoutMs = 50; - mCm.requestNetwork(nr, networkCallback, requestTimeoutMs); + mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - final int assertTimeoutMs = 100; - networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, assertTimeoutMs); + networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, + TEST_CALLBACK_TIMEOUT_MS); mWiFiNetworkAgent.disconnect(); networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); diff --git a/wifi/java/android/net/wifi/ITrafficStateCallback.aidl b/wifi/java/android/net/wifi/ITrafficStateCallback.aidl new file mode 100644 index 000000000000..0c8e777f67ba --- /dev/null +++ b/wifi/java/android/net/wifi/ITrafficStateCallback.aidl @@ -0,0 +1,34 @@ +/* + * 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.net.wifi; + +/** + * Interface for Traffic state callback. + * + * @hide + */ +oneway interface ITrafficStateCallback +{ + /** + * Callback invoked to inform clients about the current traffic state. + * + * @param state One of the values: {@link #DATA_ACTIVITY_NONE}, {@link #DATA_ACTIVITY_IN}, + * {@link #DATA_ACTIVITY_OUT} & {@link #DATA_ACTIVITY_INOUT}. + * @hide + */ + void onStateChanged(int state); +} diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index af44b7e6cd16..5631919038a2 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -26,6 +26,7 @@ import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.DhcpInfo; import android.net.Network; import android.net.wifi.ISoftApCallback; +import android.net.wifi.ITrafficStateCallback; import android.net.wifi.PasspointManagementObjectDefinition; import android.net.wifi.ScanResult; import android.net.wifi.WifiActivityEnergyInfo; @@ -180,5 +181,9 @@ interface IWifiManager void registerSoftApCallback(in IBinder binder, in ISoftApCallback callback, int callbackIdentifier); void unregisterSoftApCallback(int callbackIdentifier); + + void registerTrafficStateCallback(in IBinder binder, in ITrafficStateCallback callback, int callbackIdentifier); + + void unregisterTrafficStateCallback(int callbackIdentifier); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 922e02529962..43f0e50f5ae1 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -21,7 +21,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; -import android.annotation.SuppressLint; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; @@ -32,9 +31,9 @@ import android.net.DhcpInfo; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; -import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.hotspot2.ProvisioningCallback; import android.os.Binder; import android.os.Build; @@ -920,22 +919,6 @@ public class WifiManager { */ public static final int WIFI_FREQUENCY_BAND_2GHZ = 2; - /** List of asyncronous notifications - * @hide - */ - public static final int DATA_ACTIVITY_NOTIFICATION = 1; - - //Lowest bit indicates data reception and the second lowest - //bit indicates data transmitted - /** @hide */ - public static final int DATA_ACTIVITY_NONE = 0x00; - /** @hide */ - public static final int DATA_ACTIVITY_IN = 0x01; - /** @hide */ - public static final int DATA_ACTIVITY_OUT = 0x02; - /** @hide */ - public static final int DATA_ACTIVITY_INOUT = 0x03; - /** @hide */ public static final boolean DEFAULT_POOR_NETWORK_AVOIDANCE_ENABLED = false; @@ -2460,7 +2443,7 @@ public class WifiManager { } @Override - public void onStateChanged(int state, int failureReason) throws RemoteException { + public void onStateChanged(int state, int failureReason) { Log.v(TAG, "SoftApCallbackProxy: onStateChanged: state=" + state + ", failureReason=" + failureReason); mHandler.post(() -> { @@ -2469,7 +2452,7 @@ public class WifiManager { } @Override - public void onNumClientsChanged(int numClients) throws RemoteException { + public void onNumClientsChanged(int numClients) { Log.v(TAG, "SoftApCallbackProxy: onNumClientsChanged: numClients=" + numClients); mHandler.post(() -> { mCallback.onNumClientsChanged(numClients); @@ -3102,9 +3085,8 @@ public class WifiManager { * an AsyncChannel communication with WifiService * * @return Messenger pointing to the WifiService handler - * @hide */ - public Messenger getWifiServiceMessenger() { + private Messenger getWifiServiceMessenger() { try { return mService.getWifiServiceMessenger(mContext.getOpPackageName()); } catch (RemoteException e) { @@ -3718,4 +3700,107 @@ public class WifiManager { }); } } + + /** + * Base class for Traffic state callback. Should be extended by applications and set when + * calling {@link WifiManager#registerTrafficStateCallback(TrafficStateCallback, Handler)}. + * @hide + */ + public interface TrafficStateCallback { + /** + * Lowest bit indicates data reception and the second lowest + * bit indicates data transmitted + */ + /** @hide */ + int DATA_ACTIVITY_NONE = 0x00; + /** @hide */ + int DATA_ACTIVITY_IN = 0x01; + /** @hide */ + int DATA_ACTIVITY_OUT = 0x02; + /** @hide */ + int DATA_ACTIVITY_INOUT = 0x03; + + /** + * Callback invoked to inform clients about the current traffic state. + * + * @param state One of the values: {@link #DATA_ACTIVITY_NONE}, {@link #DATA_ACTIVITY_IN}, + * {@link #DATA_ACTIVITY_OUT} & {@link #DATA_ACTIVITY_INOUT}. + * @hide + */ + void onStateChanged(int state); + } + + /** + * Callback proxy for TrafficStateCallback objects. + * + * @hide + */ + private static class TrafficStateCallbackProxy extends ITrafficStateCallback.Stub { + private final Handler mHandler; + private final TrafficStateCallback mCallback; + + TrafficStateCallbackProxy(Looper looper, TrafficStateCallback callback) { + mHandler = new Handler(looper); + mCallback = callback; + } + + @Override + public void onStateChanged(int state) { + Log.v(TAG, "TrafficStateCallbackProxy: onStateChanged state=" + state); + mHandler.post(() -> { + mCallback.onStateChanged(state); + }); + } + } + + /** + * Registers a callback for monitoring traffic state. See {@link TrafficStateCallback}. These + * callbacks will be invoked periodically by platform to inform clients about the current + * traffic state. Caller can unregister a previously registered callback using + * {@link #unregisterTrafficStateCallback(TrafficStateCallback)} + * <p> + * Applications should have the + * {@link android.Manifest.permission#NETWORK_SETTINGS NETWORK_SETTINGS} permission. Callers + * without the permission will trigger a {@link java.lang.SecurityException}. + * <p> + * + * @param callback Callback for traffic state events + * @param handler The Handler on whose thread to execute the callbacks of the {@code callback} + * object. If null, then the application's main thread will be used. + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void registerTrafficStateCallback(@NonNull TrafficStateCallback callback, + @Nullable Handler handler) { + if (callback == null) throw new IllegalArgumentException("callback cannot be null"); + Log.v(TAG, "registerTrafficStateCallback: callback=" + callback + ", handler=" + handler); + + Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); + Binder binder = new Binder(); + try { + mService.registerTrafficStateCallback( + binder, new TrafficStateCallbackProxy(looper, callback), callback.hashCode()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Allow callers to unregister a previously registered callback. After calling this method, + * applications will no longer receive traffic state notifications. + * + * @param callback Callback to unregister for traffic state events + * @hide + */ + @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) + public void unregisterTrafficStateCallback(@NonNull TrafficStateCallback callback) { + if (callback == null) throw new IllegalArgumentException("callback cannot be null"); + Log.v(TAG, "unregisterTrafficStateCallback: callback=" + callback); + + try { + mService.unregisterTrafficStateCallback(callback.hashCode()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 20e49cd9e6a2..50580800c271 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -25,9 +25,6 @@ import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHA import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_TETHERING_DISALLOWED; import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.REQUEST_REGISTERED; import static android.net.wifi.WifiManager.SAP_START_FAILURE_GENERAL; -import static android.net.wifi.WifiManager.SAP_START_FAILURE_NO_CHANNEL; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; -import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING; import static android.net.wifi.WifiManager.WIFI_AP_STATE_FAILED; @@ -47,6 +44,7 @@ import android.net.wifi.WifiManager.LocalOnlyHotspotObserver; import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; import android.net.wifi.WifiManager.LocalOnlyHotspotSubscription; import android.net.wifi.WifiManager.SoftApCallback; +import android.net.wifi.WifiManager.TrafficStateCallback; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -57,6 +55,7 @@ import android.support.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -77,6 +76,7 @@ public class WifiManagerTest { @Mock WifiConfiguration mApConfig; @Mock IBinder mAppBinder; @Mock SoftApCallback mSoftApCallback; + @Mock TrafficStateCallback mTrafficStateCallback; private Handler mHandler; private TestLooper mLooper; @@ -702,7 +702,7 @@ public class WifiManagerTest { } /* - * Verify client provided callback is being called through callback proxy + * Verify client-provided callback is being called through callback proxy */ @Test public void softApCallbackProxyCallsOnStateChanged() throws Exception { @@ -718,7 +718,7 @@ public class WifiManagerTest { } /* - * Verify client provided callback is being called through callback proxy + * Verify client-provided callback is being called through callback proxy */ @Test public void softApCallbackProxyCallsOnNumClientsChanged() throws Exception { @@ -735,7 +735,7 @@ public class WifiManagerTest { } /* - * Verify client provided callback is being called through callback proxy on multiple events + * Verify client-provided callback is being called through callback proxy on multiple events */ @Test public void softApCallbackProxyCallsOnMultipleUpdates() throws Exception { @@ -757,7 +757,7 @@ public class WifiManagerTest { } /* - * Verify client provided callback is being called on the correct thread + * Verify client-provided callback is being called on the correct thread */ @Test public void softApCallbackIsCalledOnCorrectThread() throws Exception { @@ -1082,4 +1082,84 @@ i * Verify that a call to cancel WPS immediately returns a failure. when(mWifiService.startScan(TEST_PACKAGE_NAME)).thenReturn(false); assertFalse(mWifiManager.startScan()); } + + /** + * Verify main looper is used when handler is not provided. + */ + @Test + public void registerTrafficStateCallbackUsesMainLooperOnNullArgumentForHandler() + throws Exception { + when(mContext.getMainLooper()).thenReturn(mLooper.getLooper()); + ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor = + ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class); + mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, null); + verify(mWifiService).registerTrafficStateCallback( + any(IBinder.class), callbackCaptor.capture(), anyInt()); + + assertEquals(0, mLooper.dispatchAll()); + callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT); + assertEquals(1, mLooper.dispatchAll()); + verify(mTrafficStateCallback).onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT); + } + + /** + * Verify the call to unregisterTrafficStateCallback goes to WifiServiceImpl. + */ + @Test + public void unregisterTrafficStateCallbackCallGoesToWifiServiceImpl() throws Exception { + ArgumentCaptor<Integer> callbackIdentifier = ArgumentCaptor.forClass(Integer.class); + mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, mHandler); + verify(mWifiService).registerTrafficStateCallback(any(IBinder.class), + any(ITrafficStateCallback.Stub.class), callbackIdentifier.capture()); + + mWifiManager.unregisterTrafficStateCallback(mTrafficStateCallback); + verify(mWifiService).unregisterTrafficStateCallback( + eq((int) callbackIdentifier.getValue())); + } + + /* + * Verify client-provided callback is being called through callback proxy on multiple events + */ + @Test + public void trafficStateCallbackProxyCallsOnMultipleUpdates() throws Exception { + ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor = + ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class); + mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, mHandler); + verify(mWifiService).registerTrafficStateCallback( + any(IBinder.class), callbackCaptor.capture(), anyInt()); + + InOrder inOrder = inOrder(mTrafficStateCallback); + + callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN); + callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT); + callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT); + + mLooper.dispatchAll(); + inOrder.verify(mTrafficStateCallback).onStateChanged( + TrafficStateCallback.DATA_ACTIVITY_IN); + inOrder.verify(mTrafficStateCallback).onStateChanged( + TrafficStateCallback.DATA_ACTIVITY_INOUT); + inOrder.verify(mTrafficStateCallback).onStateChanged( + TrafficStateCallback.DATA_ACTIVITY_OUT); + } + + /* + * Verify client-provided callback is being called on the correct thread + */ + @Test + public void trafficStateCallbackIsCalledOnCorrectThread() throws Exception { + ArgumentCaptor<ITrafficStateCallback.Stub> callbackCaptor = + ArgumentCaptor.forClass(ITrafficStateCallback.Stub.class); + TestLooper altLooper = new TestLooper(); + Handler altHandler = new Handler(altLooper.getLooper()); + mWifiManager.registerTrafficStateCallback(mTrafficStateCallback, altHandler); + verify(mContext, never()).getMainLooper(); + verify(mWifiService).registerTrafficStateCallback( + any(IBinder.class), callbackCaptor.capture(), anyInt()); + + assertEquals(0, altLooper.dispatchAll()); + callbackCaptor.getValue().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT); + assertEquals(1, altLooper.dispatchAll()); + verify(mTrafficStateCallback).onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT); + } } |