diff options
267 files changed, 9532 insertions, 2703 deletions
diff --git a/Android.mk b/Android.mk index b1bb1bbe175e..7a3c46dc8e41 100644 --- a/Android.mk +++ b/Android.mk @@ -559,6 +559,7 @@ LOCAL_SRC_FILES += \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ wifi/java/android/net/wifi/rtt/IRttCallback.aidl \ wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \ + wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl \ wifi/java/android/net/wifi/IWifiScanner.aidl \ wifi/java/android/net/wifi/IRttManager.aidl \ packages/services/PacProcessor/com/android/net/IProxyService.aidl \ @@ -914,7 +915,7 @@ packages_to_document := \ # Search through the base framework dirs for these packages. # The result will be relative to frameworks/base. fwbase_dirs_to_document := \ - legacy-test/src \ + test-base/src \ $(patsubst $(LOCAL_PATH)/%,%, \ $(wildcard \ $(foreach dir, $(FRAMEWORKS_BASE_JAVA_SRC_DIRS), \ @@ -1058,7 +1059,7 @@ framework_docs_LOCAL_DROIDDOC_OPTIONS := \ -since $(SRC_API_DIR)/25.txt 25 \ -since $(SRC_API_DIR)/26.txt 26 \ -since $(SRC_API_DIR)/27.txt 27 \ - -werror -lerror -hide 111 -hide 113 -hide 121 -hide 125 -hide 126 -hide 127 -hide 128 \ + -werror -lerror -hide 111 -hide 113 -hide 125 -hide 126 -hide 127 -hide 128 \ -overview $(LOCAL_PATH)/core/java/overview.html \ framework_docs_LOCAL_API_CHECK_ADDITIONAL_JAVA_DIR:= \ diff --git a/api/current.txt b/api/current.txt index 93b6b424458f..8329aca26b2a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1119,6 +1119,7 @@ package android { field public static final int scheme = 16842791; // 0x1010027 field public static final int screenDensity = 16843467; // 0x10102cb field public static final int screenOrientation = 16842782; // 0x101001e + field public static final int screenReaderFocusable = 16844148; // 0x1010574 field public static final int screenSize = 16843466; // 0x10102ca field public static final int scrollHorizontally = 16843099; // 0x101015b field public static final int scrollIndicators = 16844006; // 0x10104e6 @@ -2313,7 +2314,9 @@ package android { field public static final int Widget_DeviceDefault_AutoCompleteTextView = 16974151; // 0x1030147 field public static final int Widget_DeviceDefault_Button = 16974145; // 0x1030141 field public static final int Widget_DeviceDefault_Button_Borderless = 16974188; // 0x103016c + field public static final int Widget_DeviceDefault_Button_Borderless_Colored = 16974561; // 0x10302e1 field public static final int Widget_DeviceDefault_Button_Borderless_Small = 16974149; // 0x1030145 + field public static final int Widget_DeviceDefault_Button_Colored = 16974560; // 0x10302e0 field public static final int Widget_DeviceDefault_Button_Inset = 16974147; // 0x1030143 field public static final int Widget_DeviceDefault_Button_Small = 16974146; // 0x1030142 field public static final int Widget_DeviceDefault_Button_Toggle = 16974148; // 0x1030144 @@ -6588,6 +6591,7 @@ package android.app.admin { public abstract class NetworkEvent implements android.os.Parcelable { method public int describeContents(); + method public long getId(); method public java.lang.String getPackageName(); method public long getTimestamp(); field public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR; @@ -37556,6 +37560,7 @@ package android.service.autofill { public final class Validators { method public static android.service.autofill.Validator and(android.service.autofill.Validator...); + method public static android.service.autofill.Validator not(android.service.autofill.Validator); method public static android.service.autofill.Validator or(android.service.autofill.Validator...); } @@ -41769,8 +41774,6 @@ package android.test.suitebuilder { public deprecated class TestSuiteBuilder { ctor public TestSuiteBuilder(java.lang.Class); ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader); - method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>); - method public final android.test.suitebuilder.TestSuiteBuilder addRequirements(com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>...); method public final junit.framework.TestSuite build(); method public android.test.suitebuilder.TestSuiteBuilder excludePackages(java.lang.String...); method protected java.lang.String getSuiteName(); @@ -46331,6 +46334,7 @@ package android.view { method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); + method public boolean isScreenReaderFocusable(); method public boolean isScrollContainer(); method public boolean isScrollbarFadingEnabled(); method public boolean isSelected(); @@ -46550,6 +46554,7 @@ package android.view { method public void setSaveFromParentEnabled(boolean); method public void setScaleX(float); method public void setScaleY(float); + method public void setScreenReaderFocusable(boolean); method public void setScrollBarDefaultDelayBeforeFade(int); method public void setScrollBarFadeDuration(int); method public void setScrollBarSize(int); @@ -47968,6 +47973,7 @@ package android.view.accessibility { method public boolean isLongClickable(); method public boolean isMultiLine(); method public boolean isPassword(); + method public boolean isScreenReaderFocusable(); method public boolean isScrollable(); method public boolean isSelected(); method public boolean isShowingHintText(); @@ -48023,6 +48029,7 @@ package android.view.accessibility { method public void setParent(android.view.View, int); method public void setPassword(boolean); method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo); + method public void setScreenReaderFocusable(boolean); method public void setScrollable(boolean); method public void setSelected(boolean); method public void setShowingHintText(boolean); diff --git a/api/removed.txt b/api/removed.txt index 2934846b480e..be4d5be1e1a3 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -210,10 +210,6 @@ package android.media { package android.media.tv { - public final class TvInputManager { - method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); - } - public class TvView extends android.view.ViewGroup { method public void requestUnblockContent(android.media.tv.TvContentRating); } diff --git a/api/system-current.txt b/api/system-current.txt index 990df9be9b29..95f28e4773cb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -751,6 +751,20 @@ package android.content.pm { field public java.lang.String credentialProtectedDataDir; } + public final class InstantAppInfo implements android.os.Parcelable { + ctor public InstantAppInfo(android.content.pm.ApplicationInfo, java.lang.String[], java.lang.String[]); + ctor public InstantAppInfo(java.lang.String, java.lang.CharSequence, java.lang.String[], java.lang.String[]); + method public int describeContents(); + method public android.content.pm.ApplicationInfo getApplicationInfo(); + method public java.lang.String[] getGrantedPermissions(); + method public java.lang.String getPackageName(); + method public java.lang.String[] getRequestedPermissions(); + method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager); + method public java.lang.CharSequence loadLabel(android.content.pm.PackageManager); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.content.pm.InstantAppInfo> CREATOR; + } + public final class InstantAppIntentFilter implements android.os.Parcelable { ctor public InstantAppIntentFilter(java.lang.String, java.util.List<android.content.IntentFilter>); method public int describeContents(); diff --git a/api/system-removed.txt b/api/system-removed.txt index 396bcd640894..f98d011faf77 100644 --- a/api/system-removed.txt +++ b/api/system-removed.txt @@ -103,6 +103,10 @@ package android.content.pm { package android.media.tv { + public final class TvInputManager { + method public android.media.tv.TvInputManager.Hardware acquireTvInputHardware(int, android.media.tv.TvInputManager.HardwareCallback, android.media.tv.TvInputInfo); + } + public static final class TvInputManager.Hardware { method public boolean dispatchKeyEventToHdmi(android.view.KeyEvent); } @@ -111,6 +115,13 @@ package android.media.tv { package android.net.wifi { + public deprecated class BatchedScanResult implements android.os.Parcelable { + ctor public BatchedScanResult(); + ctor public BatchedScanResult(android.net.wifi.BatchedScanResult); + field public final java.util.List<android.net.wifi.ScanResult> scanResults; + field public boolean truncated; + } + public class ScanResult implements android.os.Parcelable { field public boolean untrusted; } diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp index 6ded24648353..31722815276c 100644 --- a/cmds/screencap/screencap.cpp +++ b/cmds/screencap/screencap.cpp @@ -159,7 +159,7 @@ int main(int argc, char** argv) void const* mapbase = MAP_FAILED; ssize_t mapsize = -1; - void const* base = NULL; + void* base = NULL; uint32_t w, s, h, f; android_dataspace d; size_t size = 0; @@ -179,7 +179,6 @@ int main(int argc, char** argv) ProcessState::self()->setThreadPoolMaxThreadCount(0); ProcessState::self()->startThreadPool(); - ScreenshotClient screenshot; sp<IBinder> display = SurfaceComposerClient::getBuiltInDisplay(displayId); if (display == NULL) { fprintf(stderr, "Unable to get handle for display %d\n", displayId); @@ -199,51 +198,57 @@ int main(int argc, char** argv) uint8_t displayOrientation = configs[activeConfig].orientation; uint32_t captureOrientation = ORIENTATION_MAP[displayOrientation]; - status_t result = screenshot.update(display, Rect(), - 0 /* reqWidth */, 0 /* reqHeight */, - INT32_MIN, INT32_MAX, /* all layers */ - false, captureOrientation); - if (result == NO_ERROR) { - base = screenshot.getPixels(); - w = screenshot.getWidth(); - h = screenshot.getHeight(); - s = screenshot.getStride(); - f = screenshot.getFormat(); - d = screenshot.getDataSpace(); - size = screenshot.getSize(); + sp<GraphicBuffer> outBuffer; + status_t result = ScreenshotClient::capture(display, Rect(), 0 /* reqWidth */, + 0 /* reqHeight */, INT32_MIN, INT32_MAX, /* all layers */ false, captureOrientation, + &outBuffer); + if (result != NO_ERROR) { + close(fd); + _exit(1); + } + + result = outBuffer->lock(GraphicBuffer::USAGE_SW_READ_OFTEN, &base); + + if (base == NULL) { + close(fd); + _exit(1); } - if (base != NULL) { - if (png) { - const SkImageInfo info = - SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType, - dataSpaceToColorSpace(d)); - SkPixmap pixmap(info, base, s * bytesPerPixel(f)); - struct FDWStream final : public SkWStream { - size_t fBytesWritten = 0; - int fFd; - FDWStream(int f) : fFd(f) {} - size_t bytesWritten() const override { return fBytesWritten; } - bool write(const void* buffer, size_t size) override { - fBytesWritten += size; - return size == 0 || ::write(fFd, buffer, size) > 0; - } - } fdStream(fd); - (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100); - if (fn != NULL) { - notifyMediaScanner(fn); - } - } else { - uint32_t c = dataSpaceToInt(d); - write(fd, &w, 4); - write(fd, &h, 4); - write(fd, &f, 4); - write(fd, &c, 4); - size_t Bpp = bytesPerPixel(f); - for (size_t y=0 ; y<h ; y++) { - write(fd, base, w*Bpp); - base = (void *)((char *)base + s*Bpp); - } + w = outBuffer->getWidth(); + h = outBuffer->getHeight(); + s = outBuffer->getStride(); + f = outBuffer->getPixelFormat(); + d = HAL_DATASPACE_UNKNOWN; + size = s * h * bytesPerPixel(f); + + if (png) { + const SkImageInfo info = + SkImageInfo::Make(w, h, flinger2skia(f), kPremul_SkAlphaType, dataSpaceToColorSpace(d)); + SkPixmap pixmap(info, base, s * bytesPerPixel(f)); + struct FDWStream final : public SkWStream { + size_t fBytesWritten = 0; + int fFd; + FDWStream(int f) : fFd(f) {} + size_t bytesWritten() const override { return fBytesWritten; } + bool write(const void* buffer, size_t size) override { + fBytesWritten += size; + return size == 0 || ::write(fFd, buffer, size) > 0; + } + } fdStream(fd); + (void)SkEncodeImage(&fdStream, pixmap, SkEncodedImageFormat::kPNG, 100); + if (fn != NULL) { + notifyMediaScanner(fn); + } + } else { + uint32_t c = dataSpaceToInt(d); + write(fd, &w, 4); + write(fd, &h, 4); + write(fd, &f, 4); + write(fd, &c, 4); + size_t Bpp = bytesPerPixel(f); + for (size_t y=0 ; y<h ; y++) { + write(fd, base, w*Bpp); + base = (void *)((char *)base + s*Bpp); } } close(fd); @@ -253,4 +258,4 @@ int main(int argc, char** argv) // b/36066697: Avoid running static destructors. _exit(0); -} +}
\ No newline at end of file diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp index 4779bc0b55af..57b4fc1e24b3 100644 --- a/cmds/statsd/src/StatsLogProcessor.cpp +++ b/cmds/statsd/src/StatsLogProcessor.cpp @@ -199,6 +199,7 @@ void StatsLogProcessor::onDumpReport(const ConfigKey& key, vector<uint8_t>* outD iter.rp()->move(toRead); } } + StatsdStats::getInstance().noteMetricsReportSent(key); } void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) { @@ -234,7 +235,7 @@ void StatsLogProcessor::flushIfNecessary(uint64_t timestampNs, } else if (totalBytes > kMaxSerializedBytes) { // Too late. We need to start clearing data. // We ignore the return value so we force each metric producer to clear its contents. metricsManager->onDumpReport(); - StatsdStats::getInstance().noteDataDrop(key); + StatsdStats::getInstance().noteDataDropped(key); VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str()); } } diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 31405b8eba33..618e03d655da 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -488,7 +488,7 @@ status_t StatsService::cmd_print_stats(FILE* out, const Vector<String8>& args) { if (args.size() > 1) { reset = strtol(args[1].string(), NULL, 10); } - vector<int8_t> output; + vector<uint8_t> output; statsdStats.dumpStats(&output, reset); return NO_ERROR; } diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp index c2bf233c103b..7bacb441ee48 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp +++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp @@ -19,6 +19,9 @@ #include "AnomalyTracker.h" +#include <android/os/IIncidentManager.h> +#include <android/os/IncidentReportArgs.h> +#include <binder/IServiceManager.h> #include <time.h> namespace android { @@ -213,7 +216,7 @@ void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs) { // TODO: Can construct a name based on the criteria (and/or relay the criteria). ALOGW("An anomaly (nameless) has occurred! Informing incidentd."); } - // TODO: informIncidentd(); + informIncidentd(); } else { ALOGW("An anomaly has occurred! (But informing incidentd not requested.)"); } @@ -314,6 +317,29 @@ void AnomalyTracker::informAlarmsFired(const uint64_t& timestampNs, } } +void AnomalyTracker::informIncidentd() { + VLOG("informIncidentd called."); + if (!mAlert.has_incidentd_details()) { + ALOGE("Attempted to call incidentd without any incidentd_details."); + return; + } + sp<IIncidentManager> service = interface_cast<IIncidentManager>( + defaultServiceManager()->getService(android::String16("incident"))); + if (service == NULL) { + ALOGW("Couldn't get the incident service."); + return; + } + + IncidentReportArgs incidentReport; + const Alert::IncidentdDetails& details = mAlert.incidentd_details(); + for (int i = 0; i < details.section_size(); i++) { + incidentReport.addSection(details.section(i)); + } + // TODO: Pass in mAlert.name() into the addHeader? + + service->reportIncident(incidentReport); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h index afa6fee99c6b..49e83235f73b 100644 --- a/cmds/statsd/src/anomaly/AnomalyTracker.h +++ b/cmds/statsd/src/anomaly/AnomalyTracker.h @@ -148,6 +148,9 @@ protected: // Resets all bucket data. For use when all the data gets stale. void resetStorage(); + // Informs the incident service that an anomaly has occurred. + void informIncidentd(); + FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets); FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets); FRIEND_TEST(GaugeMetricProducerTest, TestAnomalyDetection); diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp index ac2192cdb3c5..445ee59947e9 100644 --- a/cmds/statsd/src/config/ConfigManager.cpp +++ b/cmds/statsd/src/config/ConfigManager.cpp @@ -210,6 +210,9 @@ StatsdConfig build_fake_config() { alert->set_number_of_buckets(6); alert->set_trigger_if_sum_gt(10); alert->set_refractory_period_secs(30); + Alert::IncidentdDetails* details = alert->mutable_incidentd_details(); + details->add_section(12); + details->add_section(13); // Count process state changes, slice by uid. metric = config.add_count_metric(); @@ -226,6 +229,9 @@ StatsdConfig build_fake_config() { alert->set_number_of_buckets(4); alert->set_trigger_if_sum_gt(30); alert->set_refractory_period_secs(20); + details = alert->mutable_incidentd_details(); + details->add_section(14); + details->add_section(15); // Count process state changes, slice by uid, while SCREEN_IS_OFF metric = config.add_count_metric(); diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp index 815e03f6d3f1..2ab1146208ee 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.cpp +++ b/cmds/statsd/src/guardrail/StatsdStats.cpp @@ -61,7 +61,7 @@ const int FIELD_ID_ATOM_STATS_COUNT = 2; // TODO: add stats for pulled atoms. StatsdStats::StatsdStats() { mPushedAtomStats.resize(android::util::kMaxPushedAtomId + 1); - mStartTime = time(nullptr); + mStartTimeSec = time(nullptr); } StatsdStats& StatsdStats::getInstance() { @@ -107,6 +107,7 @@ void StatsdStats::noteConfigRemovedInternalLocked(const ConfigKey& key) { mMetricsStats.erase(key); mConditionStats.erase(key); mIceBox.push_back(it->second); + mConfigStats.erase(it); } } @@ -126,7 +127,7 @@ void StatsdStats::noteBroadcastSent(const ConfigKey& key) { it->second.add_broadcast_sent_time_sec(time(nullptr)); } -void StatsdStats::noteDataDrop(const ConfigKey& key) { +void StatsdStats::noteDataDropped(const ConfigKey& key) { lock_guard<std::mutex> lock(mLock); auto it = mConfigStats.find(key); if (it == mConfigStats.end()) { @@ -137,6 +138,17 @@ void StatsdStats::noteDataDrop(const ConfigKey& key) { it->second.add_data_drop_time_sec(time(nullptr)); } +void StatsdStats::noteMetricsReportSent(const ConfigKey& key) { + lock_guard<std::mutex> lock(mLock); + auto it = mConfigStats.find(key); + if (it == mConfigStats.end()) { + ALOGE("Config key %s not found!", key.ToString().c_str()); + return; + } + + it->second.add_dump_report_time_sec(time(nullptr)); +} + void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) { lock_guard<std::mutex> lock(mLock); // if name doesn't exist before, it will create the key with count 0. @@ -164,7 +176,7 @@ void StatsdStats::noteMatcherMatched(const ConfigKey& key, const string& name) { void StatsdStats::noteAtomLogged(int atomId, int32_t timeSec) { lock_guard<std::mutex> lock(mLock); - if (timeSec < mStartTime) { + if (timeSec < mStartTimeSec) { return; } @@ -183,7 +195,7 @@ void StatsdStats::reset() { void StatsdStats::resetInternalLocked() { // Reset the historical data, but keep the active ConfigStats - mStartTime = time(nullptr); + mStartTimeSec = time(nullptr); mIceBox.clear(); mConditionStats.clear(); mMetricsStats.clear(); @@ -225,11 +237,11 @@ void StatsdStats::addSubStatsToConfig(const ConfigKey& key, } } -void StatsdStats::dumpStats(std::vector<int8_t>* output, bool reset) { +void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) { lock_guard<std::mutex> lock(mLock); if (DEBUG) { - time_t t = time(nullptr); + time_t t = mStartTimeSec; struct tm* tm = localtime(&t); char timeBuffer[80]; strftime(timeBuffer, sizeof(timeBuffer), "%Y-%m-%d %I:%M%p", tm); @@ -237,7 +249,7 @@ void StatsdStats::dumpStats(std::vector<int8_t>* output, bool reset) { VLOG("Stats collection start second: %s", timeBuffer); } ProtoOutputStream proto; - proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTime); + proto.write(FIELD_TYPE_INT32 | FIELD_ID_BEGIN_TIME, mStartTimeSec); proto.write(FIELD_TYPE_INT32 | FIELD_ID_END_TIME, (int32_t)time(nullptr)); VLOG("%lu Config in icebox: ", (unsigned long)mIceBox.size()); @@ -286,6 +298,10 @@ void StatsdStats::dumpStats(std::vector<int8_t>* output, bool reset) { for (const auto& dataDropTime : configStats.data_drop_time_sec()) { VLOG("\tdata drop time: %d", dataDropTime); } + + for (const auto& dumpTime : configStats.dump_report_time_sec()) { + VLOG("\tdump report time: %d", dumpTime); + } } addSubStatsToConfig(pair.first, configStats); diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h index 73ce2798cd6c..6fd9e4b6cd33 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -18,6 +18,7 @@ #include "config/ConfigKey.h" #include "frameworks/base/cmds/statsd/src/stats_log.pb.h" +#include <gtest/gtest_prod.h> #include <mutex> #include <string> #include <vector> @@ -63,7 +64,14 @@ public: /** * Report a config's metrics data has been dropped. */ - void noteDataDrop(const ConfigKey& key); + void noteDataDropped(const ConfigKey& key); + + /** + * Report metrics data report has been sent. + * + * The report may be requested via StatsManager API, or through adb cmd. + */ + void noteMetricsReportSent(const ConfigKey& key); /** * Report the size of output tuple of a condition. @@ -114,14 +122,14 @@ public: * * [reset]: whether to clear the historical stats after the call. */ - void dumpStats(std::vector<int8_t>* buffer, bool reset); + void dumpStats(std::vector<uint8_t>* buffer, bool reset); private: StatsdStats(); mutable std::mutex mLock; - int32_t mStartTime; + int32_t mStartTimeSec; // The stats about the configs that are still in use. std::map<const ConfigKey, StatsdStatsReport_ConfigStats> mConfigStats; @@ -153,6 +161,12 @@ private: void resetInternalLocked(); void addSubStatsToConfig(const ConfigKey& key, StatsdStatsReport_ConfigStats& configStats); + + FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd); + FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd); + FRIEND_TEST(StatsdStatsTest, TestConfigRemove); + FRIEND_TEST(StatsdStatsTest, TestSubStats); + FRIEND_TEST(StatsdStatsTest, TestAtomLog); }; } // namespace statsd diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp index 00642400b3e8..4338399bf675 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.cpp +++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp @@ -94,6 +94,8 @@ CountMetricProducer::~CountMetricProducer() { } void CountMetricProducer::startNewProtoOutputStream(long long startTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + mProto = std::make_unique<ProtoOutputStream>(); mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name()); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime); @@ -107,13 +109,8 @@ void CountMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str()); } -std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() { - long long endTime = time(nullptr) * NS_PER_SEC; - - // Dump current bucket if it's stale. - // If current bucket is still on-going, don't force dump current bucket. - // In finish(), We can force dump current bucket. - flushIfNeeded(endTime); +void CountMetricProducer::serializeBuckets() { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); VLOG("metric %s dump report now...", mMetric.name().c_str()); for (const auto& counter : mPastBuckets) { @@ -159,28 +156,40 @@ std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() { } mProto->end(wrapperToken); } - mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); + mPastBuckets.clear(); + // TODO: Clear mDimensionKeyMap once the report is dumped. +} + +std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() { + long long endTime = time(nullptr) * NS_PER_SEC; VLOG("metric %s dump report now...", mMetric.name().c_str()); + // Dump current bucket if it's stale. + // If current bucket is still on-going, don't force dump current bucket. + // In finish(), We can force dump current bucket. + flushIfNeeded(endTime); + + // TODO(yanglu): merge these three functions to one to avoid three locks. + serializeBuckets(); + std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(endTime); - mPastBuckets.clear(); return buffer; - - // TODO: Clear mDimensionKeyMap once the report is dumped. } void CountMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) { VLOG("Metric %s onConditionChanged", mMetric.name().c_str()); + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mCondition = conditionMet; } bool CountMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) { return false; } @@ -208,38 +217,40 @@ void CountMetricProducer::onMatchedLogEventInternal( flushIfNeeded(eventTimeNs); - if (condition == false) { + // ===========GuardRail============== + if (hitGuardRail(eventKey)) { return; } - auto it = mCurrentSlicedCounter->find(eventKey); - - if (it == mCurrentSlicedCounter->end()) { - // ===========GuardRail============== - if (hitGuardRail(eventKey)) { + // TODO(yanglu): move the following logic to a seperate function to make it lockable. + { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + if (condition == false) { return; } - // create a counter for the new key - (*mCurrentSlicedCounter)[eventKey] = 1; - } else { - // increment the existing value - auto& count = it->second; - count++; - } - - for (auto& tracker : mAnomalyTrackers) { - tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, - mCurrentSlicedCounter->find(eventKey)->second); + auto it = mCurrentSlicedCounter->find(eventKey); + if (it == mCurrentSlicedCounter->end()) { + // create a counter for the new key + mCurrentSlicedCounter->insert({eventKey, 1}); + } else { + // increment the existing value + auto& count = it->second; + count++; + } + const int64_t& count = mCurrentSlicedCounter->find(eventKey)->second; + for (auto& tracker : mAnomalyTrackers) { + tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, count); + } + VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(), (long long)(count)); } - - VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(), - (long long)(*mCurrentSlicedCounter)[eventKey]); } // When a new matched event comes in, we check if event falls into the current // bucket. If not, flush the old counter to past buckets and initialize the new bucket. void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) { return; } @@ -273,6 +284,7 @@ void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { // greater than actual data size as it contains each dimension of // CountMetricData is duplicated. size_t CountMetricProducer::byteSize() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); size_t totalSize = 0; for (const auto& pair : mPastBuckets) { totalSize += pair.second.size() * kBucketSize; diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h index f78a199de103..164dfb25c01e 100644 --- a/cmds/statsd/src/metrics/CountMetricProducer.h +++ b/cmds/statsd/src/metrics/CountMetricProducer.h @@ -75,6 +75,8 @@ protected: void startNewProtoOutputStream(long long timestamp) override; private: + void serializeBuckets(); + const CountMetric mMetric; // TODO: Add a lock to mPastBuckets. diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp index a0374c0ba67c..3b49b9aadcde 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -104,6 +104,7 @@ DurationMetricProducer::~DurationMetricProducer() { } void DurationMetricProducer::startNewProtoOutputStream(long long startTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mProto = std::make_unique<ProtoOutputStream>(); mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name()); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime); @@ -111,7 +112,7 @@ void DurationMetricProducer::startNewProtoOutputStream(long long startTime) { } unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker( - const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) { + const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) const { switch (mMetric.aggregation_type()) { case DurationMetric_AggregationType_SUM: return make_unique<OringDurationTracker>( @@ -130,6 +131,7 @@ void DurationMetricProducer::finish() { } void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str()); flushIfNeeded(eventTime); // Now for each of the on-going event, check if the condition has changed for them. @@ -139,6 +141,7 @@ void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime } void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); VLOG("Metric %s onConditionChanged", mMetric.name().c_str()); mCondition = conditionMet; flushIfNeeded(eventTime); @@ -149,15 +152,8 @@ void DurationMetricProducer::onConditionChanged(const bool conditionMet, const u } } -std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { - long long endTime = time(nullptr) * NS_PER_SEC; - - // Dump current bucket if it's stale. - // If current bucket is still on-going, don't force dump current bucket. - // In finish(), We can force dump current bucket. - flushIfNeeded(endTime); - VLOG("metric %s dump report now...", mMetric.name().c_str()); - +void DurationMetricProducer::SerializeBuckets() { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; VLOG(" dimension key %s", hashableKey.c_str()); @@ -214,13 +210,29 @@ std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); +} + +std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() { + VLOG("metric %s dump report now...", mMetric.name().c_str()); + + long long endTime = time(nullptr) * NS_PER_SEC; + + // Dump current bucket if it's stale. + // If current bucket is still on-going, don't force dump current bucket. + // In finish(), We can force dump current bucket. + flushIfNeeded(endTime); + + SerializeBuckets(); + std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); + startNewProtoOutputStream(endTime); // TODO: Properly clear the old buckets. return buffer; } void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) { return; } @@ -240,6 +252,7 @@ void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) { } bool DurationMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); // the key is not new, we are good. if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) { return false; @@ -265,32 +278,37 @@ void DurationMetricProducer::onMatchedLogEventInternal( const LogEvent& event, bool scheduledPull) { flushIfNeeded(event.GetTimestampNs()); - if (matcherIndex == mStopAllIndex) { - for (auto& pair : mCurrentSlicedDuration) { - pair.second->noteStopAll(event.GetTimestampNs()); + // TODO(yanglu): move the following logic to a seperate function to make it lockable. + { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + if (matcherIndex == mStopAllIndex) { + for (auto& pair : mCurrentSlicedDuration) { + pair.second->noteStopAll(event.GetTimestampNs()); + } + return; } - return; - } - HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension)); + HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension)); - if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { - if (hitGuardRail(eventKey)) { - return; + if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) { + if (hitGuardRail(eventKey)) { + return; + } + mCurrentSlicedDuration[eventKey] = + createDurationTracker(eventKey, mPastBuckets[eventKey]); } - mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]); - } + auto it = mCurrentSlicedDuration.find(eventKey); - auto it = mCurrentSlicedDuration.find(eventKey); - - if (matcherIndex == mStartIndex) { - it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys); - } else if (matcherIndex == mStopIndex) { - it->second->noteStop(atomKey, event.GetTimestampNs(), false); + if (matcherIndex == mStartIndex) { + it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys); + } else if (matcherIndex == mStopIndex) { + it->second->noteStop(atomKey, event.GetTimestampNs(), false); + } } } size_t DurationMetricProducer::byteSize() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); size_t totalSize = 0; for (const auto& pair : mPastBuckets) { totalSize += pair.second.size() * kBucketSize; diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h index 5b5373ec9aeb..68fff484b64d 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -71,6 +71,8 @@ protected: void startNewProtoOutputStream(long long timestamp) override; private: + void SerializeBuckets(); + const DurationMetric mMetric; // Index of the SimpleLogEntryMatcher which defines the start. @@ -96,8 +98,8 @@ private: std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>> mCurrentSlicedDuration; - std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey, - std::vector<DurationBucket>& bucket); + std::unique_ptr<DurationTracker> createDurationTracker( + const HashableDimensionKey& eventKey, std::vector<DurationBucket>& bucket) const; bool hitGuardRail(const HashableDimensionKey& newKey); static const size_t kBucketSize = sizeof(DurationBucket{}); diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp index 95a18f7241f0..53f112a507ce 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -73,6 +73,7 @@ EventMetricProducer::~EventMetricProducer() { } void EventMetricProducer::startNewProtoOutputStream(long long startTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mProto = std::make_unique<ProtoOutputStream>(); // TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData, // and StatsEvent. @@ -89,11 +90,16 @@ void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { std::unique_ptr<std::vector<uint8_t>> EventMetricProducer::onDumpReport() { long long endTime = time(nullptr) * NS_PER_SEC; - mProto->end(mProtoToken); - mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime); + // TODO(yanglu): make this section to an util function. + { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + mProto->end(mProtoToken); + mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime); + + size_t bufferSize = mProto->size(); + VLOG("metric %s dump report now... proto size: %zu ", mMetric.name().c_str(), bufferSize); + } - size_t bufferSize = mProto->size(); - VLOG("metric %s dump report now... proto size: %zu ", mMetric.name().c_str(), bufferSize); std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(endTime); @@ -103,6 +109,7 @@ std::unique_ptr<std::vector<uint8_t>> EventMetricProducer::onDumpReport() { void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) { VLOG("Metric %s onConditionChanged", mMetric.name().c_str()); + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mCondition = conditionMet; } @@ -110,6 +117,7 @@ void EventMetricProducer::onMatchedLogEventInternal( const size_t matcherIndex, const HashableDimensionKey& eventKey, const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition, const LogEvent& event, bool scheduledPull) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); if (!condition) { return; } @@ -124,6 +132,7 @@ void EventMetricProducer::onMatchedLogEventInternal( } size_t EventMetricProducer::byteSize() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); return mProto->bytesWritten(); } diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp index 1791654ba7cc..ed4c76083303 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -102,6 +102,7 @@ GaugeMetricProducer::~GaugeMetricProducer() { } void GaugeMetricProducer::startNewProtoOutputStream(long long startTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mProto = std::make_unique<ProtoOutputStream>(); mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name()); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime); @@ -111,14 +112,8 @@ void GaugeMetricProducer::startNewProtoOutputStream(long long startTime) { void GaugeMetricProducer::finish() { } -std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() { - VLOG("gauge metric %s dump report now...", mMetric.name().c_str()); - - // Dump current bucket if it's stale. - // If current bucket is still on-going, don't force dump current bucket. - // In finish(), We can force dump current bucket. - flushIfNeeded(time(nullptr) * NS_PER_SEC); - +void GaugeMetricProducer::SerializeBuckets() { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; auto it = mDimensionKeyMap.find(hashableKey); @@ -166,50 +161,69 @@ std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() { mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); + mPastBuckets.clear(); +} + +std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() { + VLOG("gauge metric %s dump report now...", mMetric.name().c_str()); + + // Dump current bucket if it's stale. + // If current bucket is still on-going, don't force dump current bucket. + // In finish(), We can force dump current bucket. + flushIfNeeded(time(nullptr) * NS_PER_SEC); + + SerializeBuckets(); std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(time(nullptr) * NS_PER_SEC); - mPastBuckets.clear(); - return buffer; // TODO: Clear mDimensionKeyMap once the report is dumped. } void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) { - AutoMutex _l(mLock); VLOG("Metric %s onConditionChanged", mMetric.name().c_str()); + + // flushIfNeeded holds the write lock and is thread-safe. flushIfNeeded(eventTime); - mCondition = conditionMet; - // Push mode. No need to proactively pull the gauge data. - if (mPullTagId == -1) { - return; - } - if (!mCondition) { - return; - } - // Already have gauge metric for the current bucket, do not do it again. - if (mCurrentSlicedBucket->size() > 0) { - return; - } vector<std::shared_ptr<LogEvent>> allData; - if (!mStatsPullerManager.Pull(mPullTagId, &allData)) { - ALOGE("Stats puller failed for tag: %d", mPullTagId); - return; + // The following section is to update the condition and re-pull the gauge. + // TODO(yanglu): make it a seperate lockable function. + { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + + mCondition = conditionMet; + + // Push mode. No need to proactively pull the gauge data. + if (mPullTagId == -1) { + return; + } + if (!mCondition) { + return; + } + // Already have gauge metric for the current bucket, do not do it again. + if (mCurrentSlicedBucket->size() > 0) { + return; + } + if (!mStatsPullerManager.Pull(mPullTagId, &allData)) { + ALOGE("Stats puller failed for tag: %d", mPullTagId); + return; + } } + + // onMatchedLogEventInternal holds the write lock and is thread-safe. for (const auto& data : allData) { onMatchedLogEvent(0, *data, false /*scheduledPull*/); } - flushIfNeeded(eventTime); } void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str()); } -int64_t GaugeMetricProducer::getGauge(const LogEvent& event) { +int64_t GaugeMetricProducer::getGauge(const LogEvent& event) const { status_t err = NO_ERROR; int64_t val = event.GetLong(mMetric.gauge_field(), &err); if (err == NO_ERROR) { @@ -221,13 +235,14 @@ int64_t GaugeMetricProducer::getGauge(const LogEvent& event) { } void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { - AutoMutex mutex(mLock); + // onMatchedLogEventInternal holds the write lock and is thread-safe. for (const auto& data : allData) { onMatchedLogEvent(0, *data, true /*scheduledPull*/); } } bool GaugeMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) { return false; } @@ -251,32 +266,32 @@ void GaugeMetricProducer::onMatchedLogEventInternal( const size_t matcherIndex, const HashableDimensionKey& eventKey, const map<string, HashableDimensionKey>& conditionKey, bool condition, const LogEvent& event, bool scheduledPull) { + uint64_t eventTimeNs = event.GetTimestampNs(); + flushIfNeeded(eventTimeNs); + if (condition == false) { return; } - uint64_t eventTimeNs = event.GetTimestampNs(); + const long gauge = getGauge(event); + if (gauge < 0) { + return; + } + if (hitGuardRail(eventKey)) { + return; + } + + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); if (eventTimeNs < mCurrentBucketStartTimeNs) { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); return; } - // When the event happens in a new bucket, flush the old buckets. - if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) { - flushIfNeeded(eventTimeNs); - } - // For gauge metric, we just simply use the first gauge in the given bucket. if (!mCurrentSlicedBucket->empty()) { return; } - const long gauge = getGauge(event); - if (gauge >= 0) { - if (hitGuardRail(eventKey)) { - return; - } (*mCurrentSlicedBucket)[eventKey] = gauge; - } for (auto& tracker : mAnomalyTrackers) { tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge); } @@ -288,6 +303,7 @@ void GaugeMetricProducer::onMatchedLogEventInternal( // if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside // the GaugeMetricProducer while holding the lock. void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) { return; } @@ -321,6 +337,7 @@ void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { } size_t GaugeMetricProducer::byteSize() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); size_t totalSize = 0; for (const auto& pair : mPastBuckets) { totalSize += pair.second.size() * kBucketSize; diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index f344303179e8..8df61117d75d 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -81,6 +81,8 @@ protected: void startNewProtoOutputStream(long long timestamp) override; private: + void SerializeBuckets(); + // The default bucket size for gauge metric is 1 second. static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000; const GaugeMetric mMetric; @@ -89,8 +91,6 @@ private: // tagId for pulled data. -1 if this is not pulled const int mPullTagId; - Mutex mLock; - // Save the past buckets and we can clear when the StatsLogReport is dumped. // TODO: Add a lock to mPastBuckets. std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets; @@ -98,7 +98,7 @@ private: // The current bucket. std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>(); - int64_t getGauge(const LogEvent& event); + int64_t getGauge(const LogEvent& event) const; bool hitGuardRail(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 62fb632faaaf..7542a9439032 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -23,6 +23,7 @@ using std::map; void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event, bool scheduledPull) { + std::unique_lock<std::shared_timed_mutex> writeLock(mRWMutex); uint64_t eventTimeNs = event.GetTimestampNs(); // this is old event, maybe statsd restarted? if (eventTimeNs < mStartTimeNs) { @@ -59,12 +60,16 @@ void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent } else { condition = mCondition; } + // Unlock as onMatchedLogEventInternal is threadsafe. + writeLock.unlock(); onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event, scheduledPull); } std::unique_ptr<std::vector<uint8_t>> MetricProducer::serializeProto() { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + size_t bufferSize = mProto->size(); std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize)); diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index b22ff6f3348c..27343ad6215a 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -17,6 +17,8 @@ #ifndef METRIC_PRODUCER_H #define METRIC_PRODUCER_H +#include <shared_mutex> + #include "anomaly/AnomalyTracker.h" #include "condition/ConditionWizard.h" #include "config/ConfigKey.h" @@ -137,6 +139,10 @@ protected: long long mProtoToken; + // Read/Write mutex to make the producer thread-safe. + // TODO(yanglu): replace with std::shared_mutex when available in libc++. + mutable std::shared_timed_mutex mRWMutex; + virtual void startNewProtoOutputStream(long long timestamp) = 0; std::unique_ptr<std::vector<uint8_t>> serializeProto(); diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp index 66c8419de662..eed7841ab435 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -121,6 +121,7 @@ ValueMetricProducer::~ValueMetricProducer() { } void ValueMetricProducer::startNewProtoOutputStream(long long startTime) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); mProto = std::make_unique<ProtoOutputStream>(); mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name()); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime); @@ -136,9 +137,8 @@ void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) { VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str()); } -std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() { - VLOG("metric %s dump report now...", mMetric.name().c_str()); - +void ValueMetricProducer::SerializeBuckets() { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); for (const auto& pair : mPastBuckets) { const HashableDimensionKey& hashableKey = pair.first; VLOG(" dimension key %s", hashableKey.c_str()); @@ -185,47 +185,63 @@ std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() { mProto->end(mProtoToken); mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, (long long)mCurrentBucketStartTimeNs); + mPastBuckets.clear(); +} +std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() { VLOG("metric %s dump report now...", mMetric.name().c_str()); + + SerializeBuckets(); std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto(); startNewProtoOutputStream(time(nullptr) * NS_PER_SEC); - mPastBuckets.clear(); return buffer; - // TODO: Clear mDimensionKeyMap once the report is dumped. } void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) { - AutoMutex _l(mLock); - mCondition = condition; + vector<shared_ptr<LogEvent>> allData; + + // TODO(yanglu): move the following logic to a seperate function to make it lockable. + { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + mCondition = condition; + + if (mPullTagId == -1) { + return; + } - if (mPullTagId != -1) { if (mCondition == true) { mStatsPullerManager->RegisterReceiver(mPullTagId, this, mMetric.bucket().bucket_size_millis()); } else if (mCondition == false) { mStatsPullerManager->UnRegisterReceiver(mPullTagId, this); } - - vector<shared_ptr<LogEvent>> allData; - if (mStatsPullerManager->Pull(mPullTagId, &allData)) { - if (allData.size() == 0) { - return; - } - for (const auto& data : allData) { - onMatchedLogEvent(0, *data, false); - } - flushIfNeeded(eventTime); + if (!mStatsPullerManager->Pull(mPullTagId, &allData)) { + return; } + } + + if (allData.size() == 0) { return; } + + // onMatchedLogEventInternal holds the write lock and is thread-safe. + for (const auto& data : allData) { + onMatchedLogEvent(0, *data, false); + } + // flushIfNeeded holds the write lock and is thread-safe. + flushIfNeeded(eventTime); +} + +bool ValueMetricProducer::IsConditionMet() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); + return mCondition == true || !mMetric.has_condition(); } void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) { - AutoMutex _l(mLock); - if (mCondition == true || !mMetric.has_condition()) { + if (IsConditionMet()) { if (allData.size() == 0) { return; } @@ -242,6 +258,7 @@ void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEven } bool ValueMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); // ===========GuardRail============== // 1. Report the tuple count if the tuple count > soft limit if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) { @@ -262,58 +279,75 @@ bool ValueMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) { return false; } -void ValueMetricProducer::onMatchedLogEventInternal( - const size_t matcherIndex, const HashableDimensionKey& eventKey, - const map<string, HashableDimensionKey>& conditionKey, bool condition, - const LogEvent& event, bool scheduledPull) { - uint64_t eventTimeNs = event.GetTimestampNs(); +void ValueMetricProducer::onMatchedLogEventInternal_pull(const uint64_t& eventTimeNs, + const HashableDimensionKey& eventKey, + const long& value, bool scheduledPull) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + if (eventTimeNs < mCurrentBucketStartTimeNs) { VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, (long long)mCurrentBucketStartTimeNs); return; } + Interval& interval = mCurrentSlicedBucket[eventKey]; + if (scheduledPull) { + // scheduled pull always sets beginning of current bucket and end + // of next bucket + if (interval.raw.size() > 0) { + interval.raw.back().second = value; + } else { + interval.raw.push_back(make_pair(value, value)); + } + Interval& nextInterval = mNextSlicedBucket[eventKey]; + if (nextInterval.raw.size() == 0) { + nextInterval.raw.push_back(make_pair(value, 0)); + } else { + nextInterval.raw.front().first = value; + } + } else { + if (mCondition == true) { + interval.raw.push_back(make_pair(value, 0)); + } else { + if (interval.raw.size() != 0) { + interval.raw.back().second = value; + } else { + interval.tainted = true; + VLOG("Data on condition true missing!"); + } + } + } +} - if (hitGuardRail(eventKey)) { +void ValueMetricProducer::onMatchedLogEventInternal_push(const uint64_t& eventTimeNs, + const HashableDimensionKey& eventKey, + const long& value) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); + if (eventTimeNs < mCurrentBucketStartTimeNs) { + VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs, + (long long)mCurrentBucketStartTimeNs); return; } - Interval& interval = mCurrentSlicedBucket[eventKey]; + mCurrentSlicedBucket[eventKey].raw.push_back(make_pair(value, 0)); +} +void ValueMetricProducer::onMatchedLogEventInternal( + const size_t matcherIndex, const HashableDimensionKey& eventKey, + const map<string, HashableDimensionKey>& conditionKey, bool condition, + const LogEvent& event, bool scheduledPull) { + uint64_t eventTimeNs = event.GetTimestampNs(); long value = get_value(event); - + if (hitGuardRail(eventKey)) { + return; + } if (mPullTagId != -1) { - if (scheduledPull) { - // scheduled pull always sets beginning of current bucket and end - // of next bucket - if (interval.raw.size() > 0) { - interval.raw.back().second = value; - } else { - interval.raw.push_back(make_pair(value, value)); - } - Interval& nextInterval = mNextSlicedBucket[eventKey]; - if (nextInterval.raw.size() == 0) { - nextInterval.raw.push_back(make_pair(value, 0)); - } else { - nextInterval.raw.front().first = value; - } - } else { - if (mCondition == true) { - interval.raw.push_back(make_pair(value, 0)); - } else { - if (interval.raw.size() != 0) { - interval.raw.back().second = value; - } else { - interval.tainted = true; - VLOG("Data on condition true missing!"); - } - } - } + onMatchedLogEventInternal_pull(eventTimeNs, eventKey, value, scheduledPull); } else { flushIfNeeded(eventTimeNs); - interval.raw.push_back(make_pair(value, 0)); + onMatchedLogEventInternal_push(eventTimeNs, eventKey, value); } } -long ValueMetricProducer::get_value(const LogEvent& event) { +long ValueMetricProducer::get_value(const LogEvent& event) const { status_t err = NO_ERROR; long val = event.GetLong(mMetric.value_field(), &err); if (err == NO_ERROR) { @@ -325,6 +359,7 @@ long ValueMetricProducer::get_value(const LogEvent& event) { } void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { + std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex); if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) { VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs, (long long)(mCurrentBucketStartTimeNs + mBucketSizeNs)); @@ -373,6 +408,7 @@ void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) { } size_t ValueMetricProducer::byteSize() const { + std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex); size_t totalSize = 0; for (const auto& pair : mPastBuckets) { totalSize += pair.second.size() * kBucketSize; diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index a024bd804d83..e87e9dabcec2 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -72,6 +72,16 @@ protected: void startNewProtoOutputStream(long long timestamp) override; private: + void onMatchedLogEventInternal_pull(const uint64_t& eventTimeNs, + const HashableDimensionKey& eventKey, const long& value, + bool scheduledPull); + void onMatchedLogEventInternal_push(const uint64_t& eventTimeNs, + const HashableDimensionKey& eventKey, const long& value); + + void SerializeBuckets(); + + bool IsConditionMet() const; + const ValueMetric mMetric; std::shared_ptr<StatsPullerManager> mStatsPullerManager; @@ -82,8 +92,6 @@ private: const int pullTagId, const uint64_t startTimeNs, std::shared_ptr<StatsPullerManager> statsPullerManager); - Mutex mLock; - // tagId for pulled data. -1 if this is not pulled const int mPullTagId; @@ -102,7 +110,7 @@ private: // TODO: Add a lock to mPastBuckets. std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets; - long get_value(const LogEvent& event); + long get_value(const LogEvent& event) const; bool hitGuardRail(const HashableDimensionKey& newKey); diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto index 81f8eb619d35..e7e1d43974af 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -206,9 +206,10 @@ message StatsdStatsReport { repeated int32 broadcast_sent_time_sec = 10; repeated int32 data_drop_time_sec = 11; - repeated MatcherStats matcher_stats = 12; - repeated ConditionStats condition_stats = 13; - repeated MetricStats metric_stats = 14; + repeated int32 dump_report_time_sec = 12; + repeated MatcherStats matcher_stats = 13; + repeated ConditionStats condition_stats = 14; + repeated MetricStats metric_stats = 15; } repeated ConfigStats config_stats = 3; diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp index 286f6bd75442..b14b52cf595c 100644 --- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp +++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp @@ -13,8 +13,10 @@ // limitations under the License. #include "src/guardrail/StatsdStats.h" +#include "statslog.h" #include <gtest/gtest.h> +#include <vector> #ifdef __ANDROID__ @@ -22,24 +24,204 @@ namespace android { namespace os { namespace statsd { -TEST(StatsdStatsTest, TestConfigAdd) { - // TODO: implement +using std::vector; + +TEST(StatsdStatsTest, TestValidConfigAdd) { + StatsdStats stats; + string name = "StatsdTest"; + ConfigKey key(0, name); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, + true /*valid config*/); + vector<uint8_t> output; + stats.dumpStats(&output, false /*reset stats*/); + + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + EXPECT_EQ(0, configReport.uid()); + EXPECT_EQ(name, configReport.name()); + EXPECT_EQ(metricsCount, configReport.metric_count()); + EXPECT_EQ(conditionsCount, configReport.condition_count()); + EXPECT_EQ(matchersCount, configReport.matcher_count()); + EXPECT_EQ(alertsCount, configReport.alert_count()); + EXPECT_EQ(true, configReport.is_valid()); + EXPECT_FALSE(configReport.has_deletion_time_sec()); } -TEST(StatsdStatsTest, TestConfigRemove) { - // TODO: implement +TEST(StatsdStatsTest, TestInvalidConfigAdd) { + StatsdStats stats; + string name = "StatsdTest"; + ConfigKey key(0, name); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, + false /*bad config*/); + vector<uint8_t> output; + stats.dumpStats(&output, false); + + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + // The invalid config should be put into icebox with a deletion time. + EXPECT_TRUE(configReport.has_deletion_time_sec()); } -TEST(StatsdStatsTest, TestMatcherReport) { - // TODO: implement +TEST(StatsdStatsTest, TestConfigRemove) { + StatsdStats stats; + string name = "StatsdTest"; + ConfigKey key(0, name); + const int metricsCount = 10; + const int conditionsCount = 20; + const int matchersCount = 30; + const int alertsCount = 10; + stats.noteConfigReceived(key, metricsCount, conditionsCount, matchersCount, alertsCount, true); + vector<uint8_t> output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + EXPECT_FALSE(configReport.has_deletion_time_sec()); + + stats.noteConfigRemoved(key); + stats.dumpStats(&output, false); + good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport2 = report.config_stats(0); + EXPECT_TRUE(configReport2.has_deletion_time_sec()); } -TEST(StatsdStatsTest, TestConditionReport) { - // TODO: implement +TEST(StatsdStatsTest, TestSubStats) { + StatsdStats stats; + ConfigKey key(0, "test"); + stats.noteConfigReceived(key, 2, 3, 4, 5, true); + + stats.noteMatcherMatched(key, "matcher1"); + stats.noteMatcherMatched(key, "matcher1"); + stats.noteMatcherMatched(key, "matcher2"); + + stats.noteConditionDimensionSize(key, "condition1", 250); + stats.noteConditionDimensionSize(key, "condition1", 240); + + stats.noteMetricDimensionSize(key, "metric1", 201); + stats.noteMetricDimensionSize(key, "metric1", 202); + + // broadcast-> 2 + stats.noteBroadcastSent(key); + stats.noteBroadcastSent(key); + + // data drop -> 1 + stats.noteDataDropped(key); + + // dump report -> 3 + stats.noteMetricsReportSent(key); + stats.noteMetricsReportSent(key); + stats.noteMetricsReportSent(key); + + vector<uint8_t> output; + stats.dumpStats(&output, true); // Dump and reset stats + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport = report.config_stats(0); + EXPECT_EQ(2, configReport.broadcast_sent_time_sec_size()); + EXPECT_EQ(1, configReport.data_drop_time_sec_size()); + EXPECT_EQ(3, configReport.dump_report_time_sec_size()); + + EXPECT_EQ(2, configReport.matcher_stats_size()); + + // matcher1 is the first in the list + if (!configReport.matcher_stats(0).name().compare("matcher1")) { + EXPECT_EQ(2, configReport.matcher_stats(0).matched_times()); + EXPECT_EQ(1, configReport.matcher_stats(1).matched_times()); + EXPECT_EQ("matcher2", configReport.matcher_stats(1).name()); + } else { + // matcher1 is the second in the list. + EXPECT_EQ(1, configReport.matcher_stats(0).matched_times()); + EXPECT_EQ("matcher2", configReport.matcher_stats(0).name()); + + EXPECT_EQ(2, configReport.matcher_stats(1).matched_times()); + EXPECT_EQ("matcher1", configReport.matcher_stats(1).name()); + } + + EXPECT_EQ(1, configReport.condition_stats_size()); + EXPECT_EQ("condition1", configReport.condition_stats(0).name()); + EXPECT_EQ(250, configReport.condition_stats(0).max_tuple_counts()); + + EXPECT_EQ(1, configReport.metric_stats_size()); + EXPECT_EQ("metric1", configReport.metric_stats(0).name()); + EXPECT_EQ(202, configReport.metric_stats(0).max_tuple_counts()); + + // after resetting the stats, some new events come + stats.noteMatcherMatched(key, "matcher99"); + stats.noteConditionDimensionSize(key, "condition99", 300); + stats.noteMetricDimensionSize(key, "metric99", 270); + + // now the config stats should only contain the stats about the new event. + stats.dumpStats(&output, false); + good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + EXPECT_EQ(1, report.config_stats_size()); + const auto& configReport2 = report.config_stats(0); + EXPECT_EQ(1, configReport2.matcher_stats_size()); + EXPECT_EQ("matcher99", configReport2.matcher_stats(0).name()); + EXPECT_EQ(1, configReport2.matcher_stats(0).matched_times()); + + EXPECT_EQ(1, configReport2.condition_stats_size()); + EXPECT_EQ("condition99", configReport2.condition_stats(0).name()); + EXPECT_EQ(300, configReport2.condition_stats(0).max_tuple_counts()); + + EXPECT_EQ(1, configReport2.metric_stats_size()); + EXPECT_EQ("metric99", configReport2.metric_stats(0).name()); + EXPECT_EQ(270, configReport2.metric_stats(0).max_tuple_counts()); } TEST(StatsdStatsTest, TestAtomLog) { - // TODO: implement + StatsdStats stats; + time_t now = time(nullptr); + // old event, we get it from the stats buffer. should be ignored. + stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, 1000); + + stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 1); + stats.noteAtomLogged(android::util::SENSOR_STATE_CHANGED, now + 2); + stats.noteAtomLogged(android::util::DROPBOX_ERROR_CHANGED, now + 3); + // pulled event, should ignore + stats.noteAtomLogged(android::util::WIFI_BYTES_TRANSFERRED, now + 4); + + vector<uint8_t> output; + stats.dumpStats(&output, false); + StatsdStatsReport report; + bool good = report.ParseFromArray(&output[0], output.size()); + EXPECT_TRUE(good); + + EXPECT_EQ(2, report.atom_stats_size()); + bool sensorAtomGood = false; + bool dropboxAtomGood = false; + + for (const auto& atomStats : report.atom_stats()) { + if (atomStats.tag() == android::util::SENSOR_STATE_CHANGED && atomStats.count() == 2) { + sensorAtomGood = true; + } + if (atomStats.tag() == android::util::DROPBOX_ERROR_CHANGED && atomStats.count() == 1) { + dropboxAtomGood = true; + } + } + + EXPECT_TRUE(dropboxAtomGood); + EXPECT_TRUE(sensorAtomGood); } } // namespace statsd diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 0266131a6c0d..d7efa91fb28b 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -294,4 +294,9 @@ public abstract class ActivityManagerInternal { * @param userId The user it is allowed for. */ public abstract void setAllowAppSwitches(@NonNull String type, int uid, int userId); + + /** + * @return true if runtime was restarted, false if it's normal boot + */ + public abstract boolean isRuntimeRestarted(); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 95c5fb53a014..ffd012d9502f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -23,6 +23,8 @@ import android.annotation.Nullable; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; +import android.app.servertransaction.ActivityResultItem; +import android.app.servertransaction.ClientTransaction; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; @@ -174,7 +176,7 @@ final class RemoteServiceException extends AndroidRuntimeException { * * {@hide} */ -public final class ActivityThread { +public final class ActivityThread extends ClientTransactionHandler { /** @hide */ public static final String TAG = "ActivityThread"; private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565; @@ -401,8 +403,8 @@ public final class ActivityThread { throw new IllegalStateException( "Received config update for non-existing activity"); } - activity.mMainThread.handleActivityConfigurationChanged( - new ActivityConfigChangeData(token, overrideConfig), newDisplayId); + activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig, + newDisplayId); }; } @@ -469,16 +471,6 @@ public final class ActivityThread { } } - static final class NewIntentData { - List<ReferrerIntent> intents; - IBinder token; - boolean andPause; - public String toString() { - return "NewIntentData{intents=" + intents + " token=" + token - + " andPause=" + andPause +"}"; - } - } - static final class ReceiverData extends BroadcastReceiver.PendingResult { public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras, boolean ordered, boolean sticky, IBinder token, int sendingUser) { @@ -644,14 +636,6 @@ public final class ActivityThread { String[] args; } - static final class ResultData { - IBinder token; - List<ResultInfo> results; - public String toString() { - return "ResultData{token=" + token + " results" + results + "}"; - } - } - static final class ContextCleanupInfo { ContextImpl context; String what; @@ -679,15 +663,6 @@ public final class ActivityThread { int flags; } - static final class ActivityConfigChangeData { - final IBinder activityToken; - final Configuration overrideConfig; - public ActivityConfigChangeData(IBinder token, Configuration config) { - activityToken = token; - overrideConfig = config; - } - } - private class ApplicationThread extends IApplicationThread.Stub { private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s"; @@ -702,93 +677,10 @@ public final class ActivityThread { } } - public final void schedulePauseActivity(IBinder token, boolean finished, - boolean userLeaving, int configChanges, boolean dontReport) { - int seq = getLifecycleSeq(); - if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this - + " operation received seq: " + seq); - sendMessage( - finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY, - token, - (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0), - configChanges, - seq); - } - - public final void scheduleStopActivity(IBinder token, boolean showWindow, - int configChanges) { - int seq = getLifecycleSeq(); - if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this - + " operation received seq: " + seq); - sendMessage( - showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE, - token, 0, configChanges, seq); - } - - public final void scheduleWindowVisibility(IBinder token, boolean showWindow) { - sendMessage( - showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW, - token); - } - public final void scheduleSleeping(IBinder token, boolean sleeping) { sendMessage(H.SLEEPING, token, sleeping ? 1 : 0); } - public final void scheduleResumeActivity(IBinder token, int processState, - boolean isForward, Bundle resumeArgs) { - int seq = getLifecycleSeq(); - if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this - + " operation received seq: " + seq); - updateProcessState(processState, false); - sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq); - } - - public final void scheduleSendResult(IBinder token, List<ResultInfo> results) { - ResultData res = new ResultData(); - res.token = token; - res.results = results; - sendMessage(H.SEND_RESULT, res); - } - - // we use token to identify this activity without having to send the - // activity itself back to the activity manager. (matters more with ipc) - @Override - public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, - ActivityInfo info, Configuration curConfig, Configuration overrideConfig, - CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, - int procState, Bundle state, PersistableBundle persistentState, - List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, - boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { - - updateProcessState(procState, false); - - ActivityClientRecord r = new ActivityClientRecord(); - - r.token = token; - r.ident = ident; - r.intent = intent; - r.referrer = referrer; - r.voiceInteractor = voiceInteractor; - r.activityInfo = info; - r.compatInfo = compatInfo; - r.state = state; - r.persistentState = persistentState; - - r.pendingResults = pendingResults; - r.pendingIntents = pendingNewIntents; - - r.startsNotResumed = notResumed; - r.isForward = isForward; - - r.profilerInfo = profilerInfo; - - r.overrideConfig = overrideConfig; - updatePendingConfiguration(curConfig); - - sendMessage(H.LAUNCH_ACTIVITY, r); - } - @Override public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, @@ -798,22 +690,6 @@ public final class ActivityThread { configChanges, notResumed, config, overrideConfig, true, preserveWindow); } - public final void scheduleNewIntent( - List<ReferrerIntent> intents, IBinder token, boolean andPause) { - NewIntentData data = new NewIntentData(); - data.intents = intents; - data.token = token; - data.andPause = andPause; - - sendMessage(H.NEW_INTENT, data); - } - - public final void scheduleDestroyActivity(IBinder token, boolean finishing, - int configChanges) { - sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0, - configChanges); - } - public final void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, boolean sync, int sendingUser, int processState) { @@ -949,11 +825,6 @@ public final class ActivityThread { sendMessage(H.SUICIDE, null); } - public void scheduleConfigurationChanged(Configuration config) { - updatePendingConfiguration(config); - sendMessage(H.CONFIGURATION_CHANGED, config); - } - public void scheduleApplicationInfoChanged(ApplicationInfo ai) { sendMessage(H.APPLICATION_INFO_CHANGED, ai); } @@ -1016,20 +887,6 @@ public final class ActivityThread { } @Override - public void scheduleActivityConfigurationChanged( - IBinder token, Configuration overrideConfig) { - sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, - new ActivityConfigChangeData(token, overrideConfig)); - } - - @Override - public void scheduleActivityMovedToDisplay(IBinder token, int displayId, - Configuration overrideConfig) { - sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY, - new ActivityConfigChangeData(token, overrideConfig), displayId); - } - - @Override public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) { sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType); } @@ -1427,26 +1284,6 @@ public final class ActivityThread { } @Override - public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, - Configuration overrideConfig) throws RemoteException { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = token; - args.arg2 = overrideConfig; - args.argi1 = isInMultiWindowMode ? 1 : 0; - sendMessage(H.MULTI_WINDOW_MODE_CHANGED, args); - } - - @Override - public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode, - Configuration overrideConfig) throws RemoteException { - SomeArgs args = SomeArgs.obtain(); - args.arg1 = token; - args.arg2 = overrideConfig; - args.argi1 = isInPipMode ? 1 : 0; - sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, args); - } - - @Override public void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor) throws RemoteException { SomeArgs args = SomeArgs.obtain(); @@ -1459,28 +1296,33 @@ public final class ActivityThread { public void handleTrustStorageUpdate() { NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate(); } + + @Override + public void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + ActivityThread.this.scheduleTransaction(transaction); + } + } + + @Override + public void updatePendingConfiguration(Configuration config) { + mAppThread.updatePendingConfiguration(config); + } + + @Override + public void updateProcessState(int processState, boolean fromIpc) { + mAppThread.updateProcessState(processState, fromIpc); } - private int getLifecycleSeq() { + @Override + public int getLifecycleSeq() { synchronized (mResourcesManager) { return mLifecycleSeq++; } } - private class H extends Handler { - public static final int LAUNCH_ACTIVITY = 100; - public static final int PAUSE_ACTIVITY = 101; - public static final int PAUSE_ACTIVITY_FINISHING= 102; - public static final int STOP_ACTIVITY_SHOW = 103; - public static final int STOP_ACTIVITY_HIDE = 104; - public static final int SHOW_WINDOW = 105; - public static final int HIDE_WINDOW = 106; - public static final int RESUME_ACTIVITY = 107; - public static final int SEND_RESULT = 108; - public static final int DESTROY_ACTIVITY = 109; + class H extends Handler { public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; - public static final int NEW_INTENT = 112; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int SERVICE_ARGS = 115; @@ -1493,7 +1335,6 @@ public final class ActivityThread { public static final int UNBIND_SERVICE = 122; public static final int DUMP_SERVICE = 123; public static final int LOW_MEMORY = 124; - public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; public static final int CREATE_BACKUP_AGENT = 128; @@ -1518,30 +1359,17 @@ public final class ActivityThread { public static final int ENTER_ANIMATION_COMPLETE = 149; public static final int START_BINDER_TRACKING = 150; public static final int STOP_BINDER_TRACKING_AND_DUMP = 151; - public static final int MULTI_WINDOW_MODE_CHANGED = 152; - public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153; public static final int LOCAL_VOICE_INTERACTION_STARTED = 154; public static final int ATTACH_AGENT = 155; public static final int APPLICATION_INFO_CHANGED = 156; - public static final int ACTIVITY_MOVED_TO_DISPLAY = 157; public static final int RUN_ISOLATED_ENTRY_POINT = 158; + public static final int EXECUTE_TRANSACTION = 159; String codeToString(int code) { if (DEBUG_MESSAGES) { switch (code) { - case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY"; - case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY"; - case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING"; - case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW"; - case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; - case SHOW_WINDOW: return "SHOW_WINDOW"; - case HIDE_WINDOW: return "HIDE_WINDOW"; - case RESUME_ACTIVITY: return "RESUME_ACTIVITY"; - case SEND_RESULT: return "SEND_RESULT"; - case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY"; case BIND_APPLICATION: return "BIND_APPLICATION"; case EXIT_APPLICATION: return "EXIT_APPLICATION"; - case NEW_INTENT: return "NEW_INTENT"; case RECEIVER: return "RECEIVER"; case CREATE_SERVICE: return "CREATE_SERVICE"; case SERVICE_ARGS: return "SERVICE_ARGS"; @@ -1553,8 +1381,6 @@ public final class ActivityThread { case UNBIND_SERVICE: return "UNBIND_SERVICE"; case DUMP_SERVICE: return "DUMP_SERVICE"; case LOW_MEMORY: return "LOW_MEMORY"; - case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; - case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY"; case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; case PROFILER_CONTROL: return "PROFILER_CONTROL"; case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; @@ -1577,12 +1403,11 @@ public final class ActivityThread { case INSTALL_PROVIDER: return "INSTALL_PROVIDER"; case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS"; case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE"; - case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED"; - case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED"; case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED"; case ATTACH_AGENT: return "ATTACH_AGENT"; case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED"; case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT"; + case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION"; } } return Integer.toString(code); @@ -1590,76 +1415,12 @@ public final class ActivityThread { public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { - case LAUNCH_ACTIVITY: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - final ActivityClientRecord r = (ActivityClientRecord) msg.obj; - - r.packageInfo = getPackageInfoNoCheck( - r.activityInfo.applicationInfo, r.compatInfo); - handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; case RELAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart"); ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; - case PAUSE_ACTIVITY: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - SomeArgs args = (SomeArgs) msg.obj; - handlePauseActivity((IBinder) args.arg1, false, - (args.argi1 & USER_LEAVING) != 0, args.argi2, - (args.argi1 & DONT_REPORT) != 0, args.argi3); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; - case PAUSE_ACTIVITY_FINISHING: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); - SomeArgs args = (SomeArgs) msg.obj; - handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0, - args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; - case STOP_ACTIVITY_SHOW: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - SomeArgs args = (SomeArgs) msg.obj; - handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; - case STOP_ACTIVITY_HIDE: { - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); - SomeArgs args = (SomeArgs) msg.obj; - handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - } break; - case SHOW_WINDOW: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); - handleWindowVisibility((IBinder)msg.obj, true); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case HIDE_WINDOW: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow"); - handleWindowVisibility((IBinder)msg.obj, false); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case RESUME_ACTIVITY: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); - SomeArgs args = (SomeArgs) msg.obj; - handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true, - args.argi3, "RESUME_ACTIVITY"); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case SEND_RESULT: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); - handleSendResult((ResultData)msg.obj); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case DESTROY_ACTIVITY: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); - handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0, - msg.arg2, false); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; @@ -1672,11 +1433,6 @@ public final class ActivityThread { } Looper.myLooper().quit(); break; - case NEW_INTENT: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); - handleNewIntent((NewIntentData)msg.obj); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; case RECEIVER: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp"); handleReceiver((ReceiverData)msg.obj); @@ -1708,15 +1464,7 @@ public final class ActivityThread { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case CONFIGURATION_CHANGED: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); - mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; - mUpdatingSystemConfig = true; - try { - handleConfigurationChanged((Configuration) msg.obj, null); - } finally { - mUpdatingSystemConfig = false; - } - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + handleConfigurationChanged((Configuration) msg.obj); break; case CLEAN_UP_CONTEXT: ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; @@ -1733,18 +1481,6 @@ public final class ActivityThread { handleLowMemory(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; - case ACTIVITY_CONFIGURATION_CHANGED: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); - handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj, - INVALID_DISPLAY); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; - case ACTIVITY_MOVED_TO_DISPLAY: - Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay"); - handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj, - msg.arg1 /* displayId */); - Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); - break; case PROFILER_CONTROL: handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2); break; @@ -1828,16 +1564,6 @@ public final class ActivityThread { case STOP_BINDER_TRACKING_AND_DUMP: handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj); break; - case MULTI_WINDOW_MODE_CHANGED: - handleMultiWindowModeChanged((IBinder) ((SomeArgs) msg.obj).arg1, - ((SomeArgs) msg.obj).argi1 == 1, - (Configuration) ((SomeArgs) msg.obj).arg2); - break; - case PICTURE_IN_PICTURE_MODE_CHANGED: - handlePictureInPictureModeChanged((IBinder) ((SomeArgs) msg.obj).arg1, - ((SomeArgs) msg.obj).argi1 == 1, - (Configuration) ((SomeArgs) msg.obj).arg2); - break; case LOCAL_VOICE_INTERACTION_STARTED: handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1, (IVoiceInteractor) ((SomeArgs) msg.obj).arg2); @@ -1857,6 +1583,9 @@ public final class ActivityThread { handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1, (String[]) ((SomeArgs) msg.obj).arg2); break; + case EXECUTE_TRANSACTION: + ((ClientTransaction) msg.obj).execute(ActivityThread.this); + break; } Object obj = msg.obj; if (obj instanceof SomeArgs) { @@ -2601,10 +2330,16 @@ public final class ActivityThread { + " req=" + requestCode + " res=" + resultCode + " data=" + data); ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); list.add(new ResultInfo(id, requestCode, resultCode, data)); - mAppThread.scheduleSendResult(token, list); + final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token); + clientTransaction.addCallback(new ActivityResultItem(list)); + try { + mAppThread.scheduleTransaction(clientTransaction); + } catch (RemoteException e) { + // Local scheduling + } } - private void sendMessage(int what, Object obj) { + void sendMessage(int what, Object obj) { sendMessage(what, obj, 0, 0, false); } @@ -2844,6 +2579,37 @@ public final class ActivityThread { return appContext; } + @Override + public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info, + Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, + IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState, + List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, + boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { + ActivityClientRecord r = new ActivityClientRecord(); + + r.token = token; + r.ident = ident; + r.intent = intent; + r.referrer = referrer; + r.voiceInteractor = voiceInteractor; + r.activityInfo = info; + r.compatInfo = compatInfo; + r.state = state; + r.persistentState = persistentState; + + r.pendingResults = pendingResults; + r.pendingIntents = pendingNewIntents; + + r.startsNotResumed = notResumed; + r.isForward = isForward; + + r.profilerInfo = profilerInfo; + + r.overrideConfig = overrideConfig; + r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo); + handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY"); + } + private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. @@ -2974,8 +2740,9 @@ public final class ActivityThread { } } - private void handleNewIntent(NewIntentData data) { - performNewIntents(data.token, data.intents, data.andPause); + @Override + public void handleNewIntent(IBinder token, List<ReferrerIntent> intents, boolean andPause) { + performNewIntents(token, intents, andPause); } public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) { @@ -3096,7 +2863,8 @@ public final class ActivityThread { } } - private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, + @Override + public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, Configuration overrideConfig) { final ActivityClientRecord r = mActivities.get(token); if (r != null) { @@ -3108,7 +2876,8 @@ public final class ActivityThread { } } - private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode, + @Override + public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode, Configuration overrideConfig) { final ActivityClientRecord r = mActivities.get(token); if (r != null) { @@ -3619,8 +3388,9 @@ public final class ActivityThread { r.mPendingRemoveWindowManager = null; } - final void handleResumeActivity(IBinder token, - boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { + @Override + public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) { return; @@ -3823,7 +3593,8 @@ public final class ActivityThread { return thumbnail; } - private void handlePauseActivity(IBinder token, boolean finished, + @Override + public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges, boolean dontReport, int seq) { ActivityClientRecord r = mActivities.get(token); if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq); @@ -4087,7 +3858,8 @@ public final class ActivityThread { } } - private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) { + @Override + public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) { ActivityClientRecord r = mActivities.get(token); if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) { return; @@ -4142,7 +3914,8 @@ public final class ActivityThread { } } - private void handleWindowVisibility(IBinder token, boolean show) { + @Override + public void handleWindowVisibility(IBinder token, boolean show) { ActivityClientRecord r = mActivities.get(token); if (r == null) { @@ -4288,8 +4061,9 @@ public final class ActivityThread { } } - private void handleSendResult(ResultData res) { - ActivityClientRecord r = mActivities.get(res.token); + @Override + public void handleSendResult(IBinder token, List<ResultInfo> results) { + ActivityClientRecord r = mActivities.get(token); if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r); if (r != null) { final boolean resumed = !r.paused; @@ -4323,7 +4097,7 @@ public final class ActivityThread { } } checkAndBlockForNetworkAccess(); - deliverResults(r, res.results); + deliverResults(r, results); if (resumed) { r.activity.performResume(); r.activity.mTemporaryPause = false; @@ -4410,8 +4184,9 @@ public final class ActivityThread { return component == null ? "[Unknown]" : component.toShortString(); } - private void handleDestroyActivity(IBinder token, boolean finishing, - int configChanges, boolean getNonConfigInstance) { + @Override + public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, + boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { @@ -4982,7 +4757,20 @@ public final class ActivityThread { return config; } - final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { + @Override + public void handleConfigurationChanged(Configuration config) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); + mCurDefaultDisplayDpi = config.densityDpi; + mUpdatingSystemConfig = true; + try { + handleConfigurationChanged(config, null /* compat */); + } finally { + mUpdatingSystemConfig = false; + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { int configDiff = 0; @@ -5113,12 +4901,15 @@ public final class ActivityThread { /** * Handle new activity configuration and/or move to a different display. - * @param data Configuration update data. + * @param activityToken Target activity token. + * @param overrideConfig Activity override config. * @param displayId Id of the display where activity was moved to, -1 if there was no move and * value didn't change. */ - void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) { - ActivityClientRecord r = mActivities.get(data.activityToken); + @Override + public void handleActivityConfigurationChanged(IBinder activityToken, + Configuration overrideConfig, int displayId) { + ActivityClientRecord r = mActivities.get(activityToken); // Check input params. if (r == null || r.activity == null) { if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r); @@ -5128,14 +4919,14 @@ public final class ActivityThread { && displayId != r.activity.getDisplay().getDisplayId(); // Perform updates. - r.overrideConfig = data.overrideConfig; + r.overrideConfig = overrideConfig; final ViewRootImpl viewRoot = r.activity.mDecor != null ? r.activity.mDecor.getViewRootImpl() : null; if (movedToDifferentDisplay) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:" + r.activityInfo.name + ", displayId=" + displayId - + ", config=" + data.overrideConfig); + + ", config=" + overrideConfig); final Configuration reportedConfig = performConfigurationChangedForActivity(r, mCompatConfiguration, displayId, true /* movedToDifferentDisplay */); @@ -5144,7 +4935,7 @@ public final class ActivityThread { } } else { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: " - + r.activityInfo.name + ", config=" + data.overrideConfig); + + r.activityInfo.name + ", config=" + overrideConfig); performConfigurationChangedForActivity(r, mCompatConfiguration); } // Notify the ViewRootImpl instance about configuration changes. It may have initiated this diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java new file mode 100644 index 000000000000..f7f4c716d0b7 --- /dev/null +++ b/core/java/android/app/ClientTransactionHandler.java @@ -0,0 +1,114 @@ +/* + * Copyright 2017 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.app; + +import android.app.servertransaction.ClientTransaction; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PersistableBundle; + +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.ReferrerIntent; + +import java.util.List; + +/** + * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items + * can perform on client. + * @hide + */ +public abstract class ClientTransactionHandler { + + // Schedule phase related logic and handlers. + + /** Prepare and schedule transaction for execution. */ + void scheduleTransaction(ClientTransaction transaction) { + transaction.prepare(this); + sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); + } + + abstract void sendMessage(int what, Object obj); + + + // Prepare phase related logic and handlers. Methods that inform about about pending changes or + // do other internal bookkeeping. + + /** Get current lifecycle request number to maintain correct ordering. */ + public abstract int getLifecycleSeq(); + + /** Set pending config in case it will be updated by other transaction item. */ + public abstract void updatePendingConfiguration(Configuration config); + + /** Set current process state. */ + public abstract void updateProcessState(int processState, boolean fromIpc); + + + // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions + // and deliver callbacks. + + /** Destroy the activity. */ + public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, + boolean getNonConfigInstance); + + /** Pause the activity. */ + public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, + int configChanges, boolean dontReport, int seq); + + /** Resume the activity. */ + public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, + boolean reallyResume, int seq, String reason); + + /** Stop the activity. */ + public abstract void handleStopActivity(IBinder token, boolean show, int configChanges, + int seq); + + /** Deliver activity (override) configuration change. */ + public abstract void handleActivityConfigurationChanged(IBinder activityToken, + Configuration overrideConfig, int displayId); + + /** Deliver result from another activity. */ + public abstract void handleSendResult(IBinder token, List<ResultInfo> results); + + /** Deliver multi-window mode change notification. */ + public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, + Configuration overrideConfig); + + /** Deliver new intent. */ + public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents, + boolean andPause); + + /** Deliver picture-in-picture mode change notification. */ + public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode, + Configuration overrideConfig); + + /** Update window visibility. */ + public abstract void handleWindowVisibility(IBinder token, boolean show); + + /** Perform activity launch. */ + public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident, + ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo, + String referrer, IVoiceInteractor voiceInteractor, Bundle state, + PersistableBundle persistentState, List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, + ProfilerInfo profilerInfo); + + /** Deliver app configuration change notification. */ + public abstract void handleConfigurationChanged(Configuration config); +} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 5f3432264ca0..b0d020a7e328 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -59,11 +59,10 @@ import android.os.IBinder; import android.os.Looper; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.IStorageManager; +import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -2457,7 +2456,8 @@ class ContextImpl extends Context { * unable to create, they are filtered by replacing with {@code null}. */ private File[] ensureExternalDirsExistOrFilter(File[] dirs) { - File[] result = new File[dirs.length]; + final StorageManager sm = getSystemService(StorageManager.class); + final File[] result = new File[dirs.length]; for (int i = 0; i < dirs.length; i++) { File dir = dirs[i]; if (!dir.exists()) { @@ -2466,15 +2466,8 @@ class ContextImpl extends Context { if (!dir.exists()) { // Failing to mkdir() may be okay, since we might not have // enough permissions; ask vold to create on our behalf. - final IStorageManager storageManager = IStorageManager.Stub.asInterface( - ServiceManager.getService("mount")); try { - final int res = storageManager.mkdirs( - getPackageName(), dir.getAbsolutePath()); - if (res != 0) { - Log.w(TAG, "Failed to ensure " + dir + ": " + res); - dir = null; - } + sm.mkdirs(dir); } catch (Exception e) { Log.w(TAG, "Failed to ensure " + dir + ": " + e); dir = null; diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 487a94a05f7a..b25d7782652f 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -20,6 +20,7 @@ import android.app.IInstrumentationWatcher; import android.app.IUiAutomationConnection; import android.app.ProfilerInfo; import android.app.ResultInfo; +import android.app.servertransaction.ClientTransaction; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; @@ -52,24 +53,6 @@ import java.util.Map; * {@hide} */ oneway interface IApplicationThread { - void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, - int configChanges, boolean dontReport); - void scheduleStopActivity(IBinder token, boolean showWindow, - int configChanges); - void scheduleWindowVisibility(IBinder token, boolean showWindow); - void scheduleResumeActivity(IBinder token, int procState, boolean isForward, - in Bundle resumeArgs); - void scheduleSendResult(IBinder token, in List<ResultInfo> results); - void scheduleLaunchActivity(in Intent intent, IBinder token, int ident, - in ActivityInfo info, in Configuration curConfig, in Configuration overrideConfig, - in CompatibilityInfo compatInfo, in String referrer, IVoiceInteractor voiceInteractor, - int procState, in Bundle state, in PersistableBundle persistentState, - in List<ResultInfo> pendingResults, in List<ReferrerIntent> pendingNewIntents, - boolean notResumed, boolean isForward, in ProfilerInfo profilerInfo); - void scheduleNewIntent( - in List<ReferrerIntent> intent, IBinder token, boolean andPause); - void scheduleDestroyActivity(IBinder token, boolean finished, - int configChanges); void scheduleReceiver(in Intent intent, in ActivityInfo info, in CompatibilityInfo compatInfo, int resultCode, in String data, in Bundle extras, boolean sync, @@ -87,7 +70,6 @@ oneway interface IApplicationThread { in Bundle coreSettings, in String buildSerial); void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs); void scheduleExit(); - void scheduleConfigurationChanged(in Configuration config); void scheduleServiceArgs(IBinder token, in ParceledListSlice args); void updateTimeZone(); void processInBackground(); @@ -101,9 +83,6 @@ oneway interface IApplicationThread { int resultCode, in String data, in Bundle extras, boolean ordered, boolean sticky, int sendingUser, int processState); void scheduleLowMemory(); - void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig); - void scheduleActivityMovedToDisplay(IBinder token, int displayId, - in Configuration overrideConfig); void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults, in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed, in Configuration config, in Configuration overrideConfig, boolean preserveWindow); @@ -146,14 +125,11 @@ oneway interface IApplicationThread { void notifyCleartextNetwork(in byte[] firstPacket); void startBinderTracking(); void stopBinderTrackingAndDump(in ParcelFileDescriptor fd); - void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode, - in Configuration newConfig); - void schedulePictureInPictureModeChanged(IBinder token, boolean isInPictureInPictureMode, - in Configuration newConfig); void scheduleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor voiceInteractor); void handleTrustStorageUpdate(); void attachAgent(String path); void scheduleApplicationInfoChanged(in ApplicationInfo ai); void setNetworkBlockSeq(long procStateSeq); + void scheduleTransaction(in ClientTransaction transaction); } diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java index fad4798e3a3e..d5234278da7d 100644 --- a/core/java/android/app/ProfilerInfo.java +++ b/core/java/android/app/ProfilerInfo.java @@ -22,6 +22,7 @@ import android.os.Parcelable; import android.util.Slog; import java.io.IOException; +import java.util.Objects; /** * System private API for passing profiler settings. @@ -132,4 +133,32 @@ public class ProfilerInfo implements Parcelable { streamingOutput = in.readInt() != 0; agent = in.readString(); } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ProfilerInfo other = (ProfilerInfo) o; + // TODO: Also check #profileFd for equality. + return Objects.equals(profileFile, other.profileFile) + && autoStopProfiler == other.autoStopProfiler + && samplingInterval == other.samplingInterval + && streamingOutput == other.streamingOutput + && Objects.equals(agent, other.agent); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(profileFile); + result = 31 * result + samplingInterval; + result = 31 * result + (autoStopProfiler ? 1 : 0); + result = 31 * result + (streamingOutput ? 1 : 0); + result = 31 * result + Objects.hashCode(agent); + return result; + } } diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java index 5e0867c3607e..d5af08a655d8 100644 --- a/core/java/android/app/ResultInfo.java +++ b/core/java/android/app/ResultInfo.java @@ -20,6 +20,8 @@ import android.content.Intent; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * {@hide} */ @@ -79,4 +81,29 @@ public class ResultInfo implements Parcelable { mData = null; } } + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ResultInfo)) { + return false; + } + final ResultInfo other = (ResultInfo) obj; + final boolean intentsEqual = mData == null ? (other.mData == null) + : mData.filterEquals(other.mData); + return intentsEqual && Objects.equals(mResultWho, other.mResultWho) + && mResultCode == other.mResultCode + && mRequestCode == other.mRequestCode; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mRequestCode; + result = 31 * result + mResultCode; + result = 31 * result + Objects.hashCode(mResultWho); + if (mData != null) { + result = 31 * result + mData.filterHashCode(); + } + return result; + } } diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java index ffd38e2b8760..f06a9257b7f8 100644 --- a/core/java/android/app/admin/ConnectEvent.java +++ b/core/java/android/app/admin/ConnectEvent.java @@ -32,29 +32,30 @@ import java.net.UnknownHostException; public final class ConnectEvent extends NetworkEvent implements Parcelable { /** The destination IP address. */ - private final String ipAddress; + private final String mIpAddress; /** The destination port number. */ - private final int port; + private final int mPort; /** @hide */ public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) { super(packageName, timestamp); - this.ipAddress = ipAddress; - this.port = port; + this.mIpAddress = ipAddress; + this.mPort = port; } private ConnectEvent(Parcel in) { - this.ipAddress = in.readString(); - this.port = in.readInt(); - this.packageName = in.readString(); - this.timestamp = in.readLong(); + this.mIpAddress = in.readString(); + this.mPort = in.readInt(); + this.mPackageName = in.readString(); + this.mTimestamp = in.readLong(); + this.mId = in.readLong(); } public InetAddress getInetAddress() { try { // ipAddress is already an address, not a host name, no DNS resolution will happen. - return InetAddress.getByName(ipAddress); + return InetAddress.getByName(mIpAddress); } catch (UnknownHostException e) { // Should never happen as we aren't passing a host name. return InetAddress.getLoopbackAddress(); @@ -62,13 +63,13 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { } public int getPort() { - return port; + return mPort; } @Override public String toString() { - return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp, - packageName); + return String.format("ConnectEvent(%s, %d, %d, %s)", mIpAddress, mPort, mTimestamp, + mPackageName); } public static final Parcelable.Creator<ConnectEvent> CREATOR @@ -96,10 +97,10 @@ public final class ConnectEvent extends NetworkEvent implements Parcelable { public void writeToParcel(Parcel out, int flags) { // write parcel token first out.writeInt(PARCEL_TOKEN_CONNECT_EVENT); - out.writeString(ipAddress); - out.writeInt(port); - out.writeString(packageName); - out.writeLong(timestamp); + out.writeString(mIpAddress); + out.writeInt(mPort); + out.writeString(mPackageName); + out.writeLong(mTimestamp); + out.writeLong(mId); } } - diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java index f84c5b00a135..4ddf13e07344 100644 --- a/core/java/android/app/admin/DnsEvent.java +++ b/core/java/android/app/admin/DnsEvent.java @@ -34,46 +34,47 @@ import java.util.List; public final class DnsEvent extends NetworkEvent implements Parcelable { /** The hostname that was looked up. */ - private final String hostname; + private final String mHostname; /** Contains (possibly a subset of) the IP addresses returned. */ - private final String[] ipAddresses; + private final String[] mIpAddresses; /** * The number of IP addresses returned from the DNS lookup event. May be different from the * length of ipAddresses if there were too many addresses to log. */ - private final int ipAddressesCount; + private final int mIpAddressesCount; /** @hide */ public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount, String packageName, long timestamp) { super(packageName, timestamp); - this.hostname = hostname; - this.ipAddresses = ipAddresses; - this.ipAddressesCount = ipAddressesCount; + this.mHostname = hostname; + this.mIpAddresses = ipAddresses; + this.mIpAddressesCount = ipAddressesCount; } private DnsEvent(Parcel in) { - this.hostname = in.readString(); - this.ipAddresses = in.createStringArray(); - this.ipAddressesCount = in.readInt(); - this.packageName = in.readString(); - this.timestamp = in.readLong(); + this.mHostname = in.readString(); + this.mIpAddresses = in.createStringArray(); + this.mIpAddressesCount = in.readInt(); + this.mPackageName = in.readString(); + this.mTimestamp = in.readLong(); + this.mId = in.readLong(); } /** Returns the hostname that was looked up. */ public String getHostname() { - return hostname; + return mHostname; } /** Returns (possibly a subset of) the IP addresses returned. */ public List<InetAddress> getInetAddresses() { - if (ipAddresses == null || ipAddresses.length == 0) { + if (mIpAddresses == null || mIpAddresses.length == 0) { return Collections.emptyList(); } - final List<InetAddress> inetAddresses = new ArrayList<>(ipAddresses.length); - for (final String ipAddress : ipAddresses) { + final List<InetAddress> inetAddresses = new ArrayList<>(mIpAddresses.length); + for (final String ipAddress : mIpAddresses) { try { // ipAddress is already an address, not a host name, no DNS resolution will happen. inetAddresses.add(InetAddress.getByName(ipAddress)); @@ -90,14 +91,14 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { * addresses to log. */ public int getTotalResolvedAddressCount() { - return ipAddressesCount; + return mIpAddressesCount; } @Override public String toString() { - return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname, - (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses), - ipAddressesCount, timestamp, packageName); + return String.format("DnsEvent(%s, %s, %d, %d, %s)", mHostname, + (mIpAddresses == null) ? "NONE" : String.join(" ", mIpAddresses), + mIpAddressesCount, mTimestamp, mPackageName); } public static final Parcelable.Creator<DnsEvent> CREATOR @@ -125,11 +126,11 @@ public final class DnsEvent extends NetworkEvent implements Parcelable { public void writeToParcel(Parcel out, int flags) { // write parcel token first out.writeInt(PARCEL_TOKEN_DNS_EVENT); - out.writeString(hostname); - out.writeStringArray(ipAddresses); - out.writeInt(ipAddressesCount); - out.writeString(packageName); - out.writeLong(timestamp); + out.writeString(mHostname); + out.writeStringArray(mIpAddresses); + out.writeInt(mIpAddressesCount); + out.writeString(mPackageName); + out.writeLong(mTimestamp); + out.writeLong(mId); } } - diff --git a/core/java/android/app/admin/NetworkEvent.java b/core/java/android/app/admin/NetworkEvent.java index 2646c3fdba27..947e4fedbb79 100644 --- a/core/java/android/app/admin/NetworkEvent.java +++ b/core/java/android/app/admin/NetworkEvent.java @@ -18,8 +18,8 @@ package android.app.admin; import android.content.pm.PackageManager; import android.os.Parcel; -import android.os.Parcelable; import android.os.ParcelFormatException; +import android.os.Parcelable; /** * An abstract class that represents a network event. @@ -32,10 +32,13 @@ public abstract class NetworkEvent implements Parcelable { static final int PARCEL_TOKEN_CONNECT_EVENT = 2; /** The package name of the UID that performed the query. */ - String packageName; + String mPackageName; /** The timestamp of the event being reported in milliseconds. */ - long timestamp; + long mTimestamp; + + /** The id of the event. */ + long mId; /** @hide */ NetworkEvent() { @@ -44,8 +47,8 @@ public abstract class NetworkEvent implements Parcelable { /** @hide */ NetworkEvent(String packageName, long timestamp) { - this.packageName = packageName; - this.timestamp = timestamp; + this.mPackageName = packageName; + this.mTimestamp = timestamp; } /** @@ -53,7 +56,7 @@ public abstract class NetworkEvent implements Parcelable { * {@link PackageManager#getNameForUid}. */ public String getPackageName() { - return packageName; + return mPackageName; } /** @@ -61,7 +64,20 @@ public abstract class NetworkEvent implements Parcelable { * the time the event was reported and midnight, January 1, 1970 UTC. */ public long getTimestamp() { - return timestamp; + return mTimestamp; + } + + /** @hide */ + public void setId(long id) { + this.mId = id; + } + + /** + * Returns the id of the event, where the id monotonically increases for each event. The id + * is reset when the device reboots, and when network logging is enabled. + */ + public long getId() { + return this.mId; } @Override @@ -95,4 +111,3 @@ public abstract class NetworkEvent implements Parcelable { @Override public abstract void writeToParcel(Parcel out, int flags); } - diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java new file mode 100644 index 000000000000..07001e2b0ad7 --- /dev/null +++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; +import static android.view.Display.INVALID_DISPLAY; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; + +/** + * Activity configuration changed callback. + * @hide + */ +public class ActivityConfigurationChangeItem extends ClientTransactionItem { + + private final Configuration mConfiguration; + + public ActivityConfigurationChangeItem(Configuration configuration) { + mConfiguration = configuration; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here. + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged"); + client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedObject(mConfiguration, flags); + } + + /** Read from Parcel. */ + private ActivityConfigurationChangeItem(Parcel in) { + mConfiguration = in.readTypedObject(Configuration.CREATOR); + } + + public static final Creator<ActivityConfigurationChangeItem> CREATOR = + new Creator<ActivityConfigurationChangeItem>() { + public ActivityConfigurationChangeItem createFromParcel(Parcel in) { + return new ActivityConfigurationChangeItem(in); + } + + public ActivityConfigurationChangeItem[] newArray(int size) { + return new ActivityConfigurationChangeItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o; + return mConfiguration.equals(other.mConfiguration); + } + + @Override + public int hashCode() { + return mConfiguration.hashCode(); + } +} diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java new file mode 100644 index 000000000000..a64108db7c21 --- /dev/null +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -0,0 +1,44 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Request for lifecycle state that an activity should reach. + * @hide + */ +public abstract class ActivityLifecycleItem extends ClientTransactionItem { + + static final boolean DEBUG_ORDER = false; + + @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED}) + @Retention(RetentionPolicy.SOURCE) + @interface LifecycleState{} + public static final int UNDEFINED = -1; + public static final int RESUMED = 0; + public static final int PAUSED = 1; + public static final int STOPPED = 2; + public static final int DESTROYED = 3; + + /** A final lifecycle state that an activity should reach. */ + @LifecycleState + public abstract int getTargetState(); +} diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java new file mode 100644 index 000000000000..76664d8e59e6 --- /dev/null +++ b/core/java/android/app/servertransaction/ActivityResultItem.java @@ -0,0 +1,95 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.app.servertransaction.ActivityLifecycleItem.PAUSED; +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ResultInfo; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Trace; + +import java.util.List; + +/** + * Activity result delivery callback. + * @hide + */ +public class ActivityResultItem extends ClientTransactionItem { + + private final List<ResultInfo> mResultInfoList; + + public ActivityResultItem(List<ResultInfo> resultInfos) { + mResultInfoList = resultInfos; + } + + @Override + public int getPreExecutionState() { + return PAUSED; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult"); + client.handleSendResult(token, mResultInfoList); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedList(mResultInfoList, flags); + } + + /** Read from Parcel. */ + private ActivityResultItem(Parcel in) { + mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR); + } + + public static final Parcelable.Creator<ActivityResultItem> CREATOR = + new Parcelable.Creator<ActivityResultItem>() { + public ActivityResultItem createFromParcel(Parcel in) { + return new ActivityResultItem(in); + } + + public ActivityResultItem[] newArray(int size) { + return new ActivityResultItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ActivityResultItem other = (ActivityResultItem) o; + return mResultInfoList.equals(other.mResultInfoList); + } + + @Override + public int hashCode() { + return mResultInfoList.hashCode(); + } +} diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java new file mode 100644 index 000000000000..4bd01afb5061 --- /dev/null +++ b/core/java/android/app/servertransaction/BaseClientRequest.java @@ -0,0 +1,45 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; + +/** + * Base interface for individual requests from server to client. + * Each of them can be prepared before scheduling and, eventually, executed. + * @hide + */ +public interface BaseClientRequest { + + /** + * Prepare the client request before scheduling. + * An example of this might be informing about pending updates for some values. + * + * @param client Target client handler. + * @param token Target activity token. + */ + default void prepare(ClientTransactionHandler client, IBinder token) { + } + + /** + * Execute the request. + * @param client Target client handler. + * @param token Target activity token. + */ + void execute(ClientTransactionHandler client, IBinder token); +} diff --git a/core/java/android/app/servertransaction/ClientTransaction.aidl b/core/java/android/app/servertransaction/ClientTransaction.aidl new file mode 100644 index 000000000000..ad8bcbf6eb04 --- /dev/null +++ b/core/java/android/app/servertransaction/ClientTransaction.aidl @@ -0,0 +1,20 @@ +/* + * Copyright 2017 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.app.servertransaction; + +/** @hide */ +parcelable ClientTransaction;
\ No newline at end of file diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java new file mode 100644 index 000000000000..d2289ba0d745 --- /dev/null +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -0,0 +1,201 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.app.ClientTransactionHandler; +import android.app.IApplicationThread; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * A container that holds a sequence of messages, which may be sent to a client. + * This includes a list of callbacks and a final lifecycle state. + * + * @see com.android.server.am.ClientLifecycleManager + * @see ClientTransactionItem + * @see ActivityLifecycleItem + * @hide + */ +public class ClientTransaction implements Parcelable { + + /** A list of individual callbacks to a client. */ + private List<ClientTransactionItem> mActivityCallbacks; + + /** + * Final lifecycle state in which the client activity should be after the transaction is + * executed. + */ + private ActivityLifecycleItem mLifecycleStateRequest; + + /** Target client. */ + private IApplicationThread mClient; + + /** Target client activity. Might be null if the entire transaction is targeting an app. */ + private IBinder mActivityToken; + + public ClientTransaction(IApplicationThread client, IBinder activityToken) { + mClient = client; + mActivityToken = activityToken; + } + + /** + * Add a message to the end of the sequence of callbacks. + * @param activityCallback A single message that can contain a lifecycle request/callback. + */ + public void addCallback(ClientTransactionItem activityCallback) { + if (mActivityCallbacks == null) { + mActivityCallbacks = new ArrayList<>(); + } + mActivityCallbacks.add(activityCallback); + } + + /** + * Set the lifecycle state in which the client should be after executing the transaction. + * @param stateRequest A lifecycle request initialized with right parameters. + */ + public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) { + mLifecycleStateRequest = stateRequest; + } + + /** + * Do what needs to be done while the transaction is being scheduled on the client side. + * @param clientTransactionHandler Handler on the client side that will executed all operations + * requested by transaction items. + */ + public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) { + if (mActivityCallbacks != null) { + final int size = mActivityCallbacks.size(); + for (int i = 0; i < size; ++i) { + mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken); + } + } + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken); + } + } + + /** + * Execute the transaction. + * @param clientTransactionHandler Handler on the client side that will execute all operations + * requested by transaction items. + */ + public void execute(android.app.ClientTransactionHandler clientTransactionHandler) { + if (mActivityCallbacks != null) { + final int size = mActivityCallbacks.size(); + for (int i = 0; i < size; ++i) { + mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken); + } + } + if (mLifecycleStateRequest != null) { + mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken); + } + } + + /** + * Schedule the transaction after it was initialized. It will be send to client and all its + * individual parts will be applied in the following sequence: + * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that + * needs to be done before actually scheduling the transaction for callbacks and lifecycle + * state request. + * 2. The transaction message is scheduled. + * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks + * and necessary lifecycle transitions. + */ + public void schedule() throws RemoteException { + mClient.scheduleTransaction(this); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mClient.asBinder()); + final boolean writeActivityToken = mActivityToken != null; + dest.writeBoolean(writeActivityToken); + if (writeActivityToken) { + dest.writeStrongBinder(mActivityToken); + } + dest.writeParcelable(mLifecycleStateRequest, flags); + final boolean writeActivityCallbacks = mActivityCallbacks != null; + dest.writeBoolean(writeActivityCallbacks); + if (writeActivityCallbacks) { + dest.writeParcelableList(mActivityCallbacks, flags); + } + } + + /** Read from Parcel. */ + private ClientTransaction(Parcel in) { + mClient = (IApplicationThread) in.readStrongBinder(); + final boolean readActivityToken = in.readBoolean(); + if (readActivityToken) { + mActivityToken = in.readStrongBinder(); + } + mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader()); + final boolean readActivityCallbacks = in.readBoolean(); + if (readActivityCallbacks) { + mActivityCallbacks = new ArrayList<>(); + in.readParcelableList(mActivityCallbacks, getClass().getClassLoader()); + } + } + + public static final Creator<ClientTransaction> CREATOR = + new Creator<ClientTransaction>() { + public ClientTransaction createFromParcel(Parcel in) { + return new ClientTransaction(in); + } + + public ClientTransaction[] newArray(int size) { + return new ClientTransaction[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ClientTransaction other = (ClientTransaction) o; + return Objects.equals(mActivityCallbacks, other.mActivityCallbacks) + && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest) + && mClient == other.mClient + && mActivityToken == other.mActivityToken; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + Objects.hashCode(mActivityCallbacks); + result = 31 * result + Objects.hashCode(mLifecycleStateRequest); + return result; + } +} diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java new file mode 100644 index 000000000000..6f2cc007ac27 --- /dev/null +++ b/core/java/android/app/servertransaction/ClientTransactionItem.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState; +import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED; + +import android.os.Parcelable; + +/** + * A callback message to a client that can be scheduled and executed. + * Examples of these might be activity configuration change, multi-window mode change, activity + * result delivery etc. + * + * @see ClientTransaction + * @see com.android.server.am.ClientLifecycleManager + * @hide + */ +public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable { + + /** Get the state in which this callback can be executed. */ + @LifecycleState + public int getPreExecutionState() { + return UNDEFINED; + } + + /** Get the state that must follow this callback. */ + @LifecycleState + public int getPostExecutionState() { + return UNDEFINED; + } + + + // Parcelable + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java new file mode 100644 index 000000000000..055923ec8efd --- /dev/null +++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; + +/** + * App configuration change message. + * @hide + */ +public class ConfigurationChangeItem extends ClientTransactionItem { + + private final Configuration mConfiguration; + + public ConfigurationChangeItem(Configuration configuration) { + mConfiguration = new Configuration(configuration); + } + + @Override + public void prepare(android.app.ClientTransactionHandler client, IBinder token) { + client.updatePendingConfiguration(mConfiguration); + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + client.handleConfigurationChanged(mConfiguration); + } + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedObject(mConfiguration, flags); + } + + /** Read from Parcel. */ + private ConfigurationChangeItem(Parcel in) { + mConfiguration = in.readTypedObject(Configuration.CREATOR); + } + + public static final Creator<ConfigurationChangeItem> CREATOR = + new Creator<ConfigurationChangeItem>() { + public ConfigurationChangeItem createFromParcel(Parcel in) { + return new ConfigurationChangeItem(in); + } + + public ConfigurationChangeItem[] newArray(int size) { + return new ConfigurationChangeItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ConfigurationChangeItem other = (ConfigurationChangeItem) o; + return mConfiguration.equals(other.mConfiguration); + } + + @Override + public int hashCode() { + return mConfiguration.hashCode(); + } +} diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java new file mode 100644 index 000000000000..38fd5fb6c779 --- /dev/null +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; + +/** + * Request to destroy an activity. + * @hide + */ +public class DestroyActivityItem extends ActivityLifecycleItem { + + private final boolean mFinished; + private final int mConfigChanges; + + public DestroyActivityItem(boolean finished, int configChanges) { + mFinished = finished; + mConfigChanges = configChanges; + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); + client.handleDestroyActivity(token, mFinished, mConfigChanges, + false /* getNonConfigInstance */); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public int getTargetState() { + return DESTROYED; + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mFinished); + dest.writeInt(mConfigChanges); + } + + /** Read from Parcel. */ + private DestroyActivityItem(Parcel in) { + mFinished = in.readBoolean(); + mConfigChanges = in.readInt(); + } + + public static final Creator<DestroyActivityItem> CREATOR = + new Creator<DestroyActivityItem>() { + public DestroyActivityItem createFromParcel(Parcel in) { + return new DestroyActivityItem(in); + } + + public DestroyActivityItem[] newArray(int size) { + return new DestroyActivityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DestroyActivityItem other = (DestroyActivityItem) o; + return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mFinished ? 1 : 0); + result = 31 * result + mConfigChanges; + return result; + } +} diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java new file mode 100644 index 000000000000..417ebac8ea20 --- /dev/null +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -0,0 +1,232 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.app.ProfilerInfo; +import android.app.ResultInfo; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.os.BaseBundle; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.PersistableBundle; +import android.os.Trace; + +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.ReferrerIntent; + +import java.util.List; +import java.util.Objects; + +/** + * Request to launch an activity. + * @hide + */ +public class LaunchActivityItem extends ActivityLifecycleItem { + + private final Intent mIntent; + private final int mIdent; + private final ActivityInfo mInfo; + private final Configuration mCurConfig; + private final Configuration mOverrideConfig; + private final CompatibilityInfo mCompatInfo; + private final String mReferrer; + private final IVoiceInteractor mVoiceInteractor; + private final int mProcState; + private final Bundle mState; + private final PersistableBundle mPersistentState; + private final List<ResultInfo> mPendingResults; + private final List<ReferrerIntent> mPendingNewIntents; + // TODO(lifecycler): use lifecycle request instead of this param. + private final boolean mNotResumed; + private final boolean mIsForward; + private final ProfilerInfo mProfilerInfo; + + public LaunchActivityItem(Intent intent, int ident, ActivityInfo info, + Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, + String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, + PersistableBundle persistentState, List<ResultInfo> pendingResults, + List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, + ProfilerInfo profilerInfo) { + mIntent = intent; + mIdent = ident; + mInfo = info; + mCurConfig = curConfig; + mOverrideConfig = overrideConfig; + mCompatInfo = compatInfo; + mReferrer = referrer; + mVoiceInteractor = voiceInteractor; + mProcState = procState; + mState = state; + mPersistentState = persistentState; + mPendingResults = pendingResults; + mPendingNewIntents = pendingNewIntents; + mNotResumed = notResumed; + mIsForward = isForward; + mProfilerInfo = profilerInfo; + } + + @Override + public void prepare(ClientTransactionHandler client, IBinder token) { + client.updateProcessState(mProcState, false); + client.updatePendingConfiguration(mCurConfig); + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); + client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo, + mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, + mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public int getTargetState() { + return mNotResumed ? PAUSED : RESUMED; + } + + + // Parcelable implementation + + /** Write from Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeTypedObject(mIntent, flags); + dest.writeInt(mIdent); + dest.writeTypedObject(mInfo, flags); + dest.writeTypedObject(mCurConfig, flags); + dest.writeTypedObject(mOverrideConfig, flags); + dest.writeTypedObject(mCompatInfo, flags); + dest.writeString(mReferrer); + dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null); + dest.writeInt(mProcState); + dest.writeBundle(mState); + dest.writePersistableBundle(mPersistentState); + dest.writeTypedList(mPendingResults, flags); + dest.writeTypedList(mPendingNewIntents, flags); + dest.writeBoolean(mNotResumed); + dest.writeBoolean(mIsForward); + dest.writeTypedObject(mProfilerInfo, flags); + } + + /** Read from Parcel. */ + private LaunchActivityItem(Parcel in) { + mIntent = in.readTypedObject(Intent.CREATOR); + mIdent = in.readInt(); + mInfo = in.readTypedObject(ActivityInfo.CREATOR); + mCurConfig = in.readTypedObject(Configuration.CREATOR); + mOverrideConfig = in.readTypedObject(Configuration.CREATOR); + mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR); + mReferrer = in.readString(); + mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder(); + mProcState = in.readInt(); + mState = in.readBundle(getClass().getClassLoader()); + mPersistentState = in.readPersistableBundle(getClass().getClassLoader()); + mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR); + mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR); + mNotResumed = in.readBoolean(); + mIsForward = in.readBoolean(); + mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR); + } + + public static final Creator<LaunchActivityItem> CREATOR = + new Creator<LaunchActivityItem>() { + public LaunchActivityItem createFromParcel(Parcel in) { + return new LaunchActivityItem(in); + } + + public LaunchActivityItem[] newArray(int size) { + return new LaunchActivityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final LaunchActivityItem other = (LaunchActivityItem) o; + return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent + && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig) + && Objects.equals(mOverrideConfig, other.mOverrideConfig) + && Objects.equals(mCompatInfo, other.mCompatInfo) + && Objects.equals(mReferrer, other.mReferrer) + && mProcState == other.mProcState && areBundlesEqual(mState, other.mState) + && areBundlesEqual(mPersistentState, other.mPersistentState) + && Objects.equals(mPendingResults, other.mPendingResults) + && Objects.equals(mPendingNewIntents, other.mPendingNewIntents) + && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward + && Objects.equals(mProfilerInfo, other.mProfilerInfo); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mIntent.filterHashCode(); + result = 31 * result + mIdent; + result = 31 * result + Objects.hashCode(mCurConfig); + result = 31 * result + Objects.hashCode(mOverrideConfig); + result = 31 * result + Objects.hashCode(mCompatInfo); + result = 31 * result + Objects.hashCode(mReferrer); + result = 31 * result + Objects.hashCode(mProcState); + result = 31 * result + (mState != null ? mState.size() : 0); + result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0); + result = 31 * result + Objects.hashCode(mPendingResults); + result = 31 * result + Objects.hashCode(mPendingNewIntents); + result = 31 * result + (mNotResumed ? 1 : 0); + result = 31 * result + (mIsForward ? 1 : 0); + result = 31 * result + Objects.hashCode(mProfilerInfo); + return result; + } + + private boolean activityInfoEqual(ActivityInfo other) { + return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio + && Objects.equals(mInfo.launchToken, other.launchToken) + && Objects.equals(mInfo.getComponentName(), other.getComponentName()); + } + + private static boolean areBundlesEqual(BaseBundle extras, BaseBundle newExtras) { + if (extras == null || newExtras == null) { + return extras == newExtras; + } + + if (extras.size() != newExtras.size()) { + return false; + } + + for (String key : extras.keySet()) { + if (key != null) { + final Object value = extras.get(key); + final Object newValue = newExtras.get(key); + if (!Objects.equals(value, newValue)) { + return false; + } + } + } + return true; + } +} diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java new file mode 100644 index 000000000000..ccd80d88620c --- /dev/null +++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java @@ -0,0 +1,93 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; + +/** + * Activity move to a different display message. + * @hide + */ +public class MoveToDisplayItem extends ClientTransactionItem { + + private final int mTargetDisplayId; + private final Configuration mConfiguration; + + public MoveToDisplayItem(int targetDisplayId, Configuration configuration) { + mTargetDisplayId = targetDisplayId; + mConfiguration = configuration; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay"); + client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mTargetDisplayId); + dest.writeTypedObject(mConfiguration, flags); + } + + /** Read from Parcel. */ + private MoveToDisplayItem(Parcel in) { + mTargetDisplayId = in.readInt(); + mConfiguration = in.readTypedObject(Configuration.CREATOR); + } + + public static final Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() { + public MoveToDisplayItem createFromParcel(Parcel in) { + return new MoveToDisplayItem(in); + } + + public MoveToDisplayItem[] newArray(int size) { + return new MoveToDisplayItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MoveToDisplayItem other = (MoveToDisplayItem) o; + return mTargetDisplayId == other.mTargetDisplayId + && mConfiguration.equals(other.mConfiguration); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mTargetDisplayId; + result = 31 * result + mConfiguration.hashCode(); + return result; + } +} diff --git a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java new file mode 100644 index 000000000000..a0c617fa66c3 --- /dev/null +++ b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java @@ -0,0 +1,92 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; + +/** + * Multi-window mode change message. + * @hide + */ +// TODO(lifecycler): Remove the use of this and just use the configuration change message to +// communicate multi-window mode change with WindowConfiguration. +public class MultiWindowModeChangeItem extends ClientTransactionItem { + + private final boolean mIsInMultiWindowMode; + private final Configuration mOverrideConfig; + + public MultiWindowModeChangeItem(boolean isInMultiWindowMode, + Configuration overrideConfig) { + mIsInMultiWindowMode = isInMultiWindowMode; + mOverrideConfig = overrideConfig; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mIsInMultiWindowMode); + dest.writeTypedObject(mOverrideConfig, flags); + } + + /** Read from Parcel. */ + private MultiWindowModeChangeItem(Parcel in) { + mIsInMultiWindowMode = in.readBoolean(); + mOverrideConfig = in.readTypedObject(Configuration.CREATOR); + } + + public static final Creator<MultiWindowModeChangeItem> CREATOR = + new Creator<MultiWindowModeChangeItem>() { + public MultiWindowModeChangeItem createFromParcel(Parcel in) { + return new MultiWindowModeChangeItem(in); + } + + public MultiWindowModeChangeItem[] newArray(int size) { + return new MultiWindowModeChangeItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o; + return mIsInMultiWindowMode == other.mIsInMultiWindowMode + && mOverrideConfig.equals(other.mOverrideConfig); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mIsInMultiWindowMode ? 1 : 0); + result = 31 * result + mOverrideConfig.hashCode(); + return result; + } +} diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java new file mode 100644 index 000000000000..61a8965aae7f --- /dev/null +++ b/core/java/android/app/servertransaction/NewIntentItem.java @@ -0,0 +1,108 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.app.servertransaction.ActivityLifecycleItem.PAUSED; +import static android.app.servertransaction.ActivityLifecycleItem.RESUMED; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Trace; + +import com.android.internal.content.ReferrerIntent; + +import java.util.List; + +/** + * New intent message. + * @hide + */ +public class NewIntentItem extends ClientTransactionItem { + + private final List<ReferrerIntent> mIntents; + private final boolean mPause; + + public NewIntentItem(List<ReferrerIntent> intents, boolean pause) { + mIntents = intents; + mPause = pause; + } + + @Override + public int getPreExecutionState() { + return PAUSED; + } + + @Override + public int getPostExecutionState() { + return RESUMED; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent"); + client.handleNewIntent(token, mIntents, mPause); + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mPause); + dest.writeTypedList(mIntents, flags); + } + + /** Read from Parcel. */ + private NewIntentItem(Parcel in) { + mPause = in.readBoolean(); + mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR); + } + + public static final Parcelable.Creator<NewIntentItem> CREATOR = + new Parcelable.Creator<NewIntentItem>() { + public NewIntentItem createFromParcel(Parcel in) { + return new NewIntentItem(in); + } + + public NewIntentItem[] newArray(int size) { + return new NewIntentItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final NewIntentItem other = (NewIntentItem) o; + return mPause == other.mPause && mIntents.equals(other.mIntents); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mPause ? 1 : 0); + result = 31 * result + mIntents.hashCode(); + return result; + } +} diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java new file mode 100644 index 000000000000..e561a4b51427 --- /dev/null +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; +import android.util.Slog; + +/** + * Request to move an activity to paused state. + * @hide + */ +public class PauseActivityItem extends ActivityLifecycleItem { + + private static final String TAG = "PauseActivityItem"; + + private final boolean mFinished; + private final boolean mUserLeaving; + private final int mConfigChanges; + private final boolean mDontReport; + + private int mLifecycleSeq; + + public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges, + boolean dontReport) { + mFinished = finished; + mUserLeaving = userLeaving; + mConfigChanges = configChanges; + mDontReport = dontReport; + } + + @Override + public void prepare(ClientTransactionHandler client, IBinder token) { + mLifecycleSeq = client.getLifecycleSeq(); + if (DEBUG_ORDER) { + Slog.d(TAG, "Pause transaction for " + client + " received seq: " + + mLifecycleSeq); + } + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause"); + client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport, + mLifecycleSeq); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public int getTargetState() { + return PAUSED; + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mFinished); + dest.writeBoolean(mUserLeaving); + dest.writeInt(mConfigChanges); + dest.writeBoolean(mDontReport); + } + + /** Read from Parcel. */ + private PauseActivityItem(Parcel in) { + mFinished = in.readBoolean(); + mUserLeaving = in.readBoolean(); + mConfigChanges = in.readInt(); + mDontReport = in.readBoolean(); + } + + public static final Creator<PauseActivityItem> CREATOR = + new Creator<PauseActivityItem>() { + public PauseActivityItem createFromParcel(Parcel in) { + return new PauseActivityItem(in); + } + + public PauseActivityItem[] newArray(int size) { + return new PauseActivityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PauseActivityItem other = (PauseActivityItem) o; + return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving + && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mFinished ? 1 : 0); + result = 31 * result + (mUserLeaving ? 1 : 0); + result = 31 * result + mConfigChanges; + result = 31 * result + (mDontReport ? 1 : 0); + return result; + } +} diff --git a/core/java/android/app/servertransaction/PipModeChangeItem.java b/core/java/android/app/servertransaction/PipModeChangeItem.java new file mode 100644 index 000000000000..923839eec15b --- /dev/null +++ b/core/java/android/app/servertransaction/PipModeChangeItem.java @@ -0,0 +1,89 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import android.content.res.Configuration; +import android.os.IBinder; +import android.os.Parcel; + +/** + * Picture in picture mode change message. + * @hide + */ +// TODO(lifecycler): Remove the use of this and just use the configuration change message to +// communicate multi-window mode change with WindowConfiguration. +public class PipModeChangeItem extends ClientTransactionItem { + + private final boolean mIsInPipMode; + private final Configuration mOverrideConfig; + + public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) { + mIsInPipMode = isInPipMode; + mOverrideConfig = overrideConfig; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mIsInPipMode); + dest.writeTypedObject(mOverrideConfig, flags); + } + + /** Read from Parcel. */ + private PipModeChangeItem(Parcel in) { + mIsInPipMode = in.readBoolean(); + mOverrideConfig = in.readTypedObject(Configuration.CREATOR); + } + + public static final Creator<PipModeChangeItem> CREATOR = + new Creator<PipModeChangeItem>() { + public PipModeChangeItem createFromParcel(Parcel in) { + return new PipModeChangeItem(in); + } + + public PipModeChangeItem[] newArray(int size) { + return new PipModeChangeItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final PipModeChangeItem other = (PipModeChangeItem) o; + return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mIsInPipMode ? 1 : 0); + result = 31 * result + mOverrideConfig.hashCode(); + return result; + } +} diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java new file mode 100644 index 000000000000..ea31a461f1f3 --- /dev/null +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -0,0 +1,114 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; +import android.util.Slog; + +/** + * Request to move an activity to resumed state. + * @hide + */ +public class ResumeActivityItem extends ActivityLifecycleItem { + + private static final String TAG = "ResumeActivityItem"; + + private final int mProcState; + private final boolean mIsForward; + + private int mLifecycleSeq; + + public ResumeActivityItem(int procState, boolean isForward) { + mProcState = procState; + mIsForward = isForward; + } + + @Override + public void prepare(ClientTransactionHandler client, IBinder token) { + mLifecycleSeq = client.getLifecycleSeq(); + if (DEBUG_ORDER) { + Slog.d(TAG, "Resume transaction for " + client + " received seq: " + + mLifecycleSeq); + } + client.updateProcessState(mProcState, false); + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume"); + client.handleResumeActivity(token, true /* clearHide */, mIsForward, + true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY"); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public int getTargetState() { + return RESUMED; + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mProcState); + dest.writeBoolean(mIsForward); + } + + /** Read from Parcel. */ + private ResumeActivityItem(Parcel in) { + mProcState = in.readInt(); + mIsForward = in.readBoolean(); + } + + public static final Creator<ResumeActivityItem> CREATOR = + new Creator<ResumeActivityItem>() { + public ResumeActivityItem createFromParcel(Parcel in) { + return new ResumeActivityItem(in); + } + + public ResumeActivityItem[] newArray(int size) { + return new ResumeActivityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ResumeActivityItem other = (ResumeActivityItem) o; + return mProcState == other.mProcState && mIsForward == other.mIsForward; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + mProcState; + result = 31 * result + (mIsForward ? 1 : 0); + return result; + } +} diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java new file mode 100644 index 000000000000..d62c507770a2 --- /dev/null +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -0,0 +1,112 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; +import android.util.Slog; + +/** + * Request to move an activity to stopped state. + * @hide + */ +public class StopActivityItem extends ActivityLifecycleItem { + + private static final String TAG = "StopActivityItem"; + + private final boolean mShowWindow; + private final int mConfigChanges; + + private int mLifecycleSeq; + + public StopActivityItem(boolean showWindow, int configChanges) { + mShowWindow = showWindow; + mConfigChanges = configChanges; + } + + @Override + public void prepare(ClientTransactionHandler client, IBinder token) { + mLifecycleSeq = client.getLifecycleSeq(); + if (DEBUG_ORDER) { + Slog.d(TAG, "Stop transaction for " + client + " received seq: " + + mLifecycleSeq); + } + } + + @Override + public void execute(ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop"); + client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + @Override + public int getTargetState() { + return STOPPED; + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mShowWindow); + dest.writeInt(mConfigChanges); + } + + /** Read from Parcel. */ + private StopActivityItem(Parcel in) { + mShowWindow = in.readBoolean(); + mConfigChanges = in.readInt(); + } + + public static final Creator<StopActivityItem> CREATOR = + new Creator<StopActivityItem>() { + public StopActivityItem createFromParcel(Parcel in) { + return new StopActivityItem(in); + } + + public StopActivityItem[] newArray(int size) { + return new StopActivityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final StopActivityItem other = (StopActivityItem) o; + return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + (mShowWindow ? 1 : 0); + result = 31 * result + mConfigChanges; + return result; + } +} diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java new file mode 100644 index 000000000000..8e88b38d50b1 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowVisibilityItem.java @@ -0,0 +1,85 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; + +import android.os.IBinder; +import android.os.Parcel; +import android.os.Trace; + +/** + * Window visibility change message. + * @hide + */ +public class WindowVisibilityItem extends ClientTransactionItem { + + private final boolean mShowWindow; + + public WindowVisibilityItem(boolean showWindow) { + mShowWindow = showWindow; + } + + @Override + public void execute(android.app.ClientTransactionHandler client, IBinder token) { + Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow"); + client.handleWindowVisibility(token, mShowWindow); + Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + } + + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeBoolean(mShowWindow); + } + + /** Read from Parcel. */ + private WindowVisibilityItem(Parcel in) { + mShowWindow = in.readBoolean(); + } + + public static final Creator<WindowVisibilityItem> CREATOR = + new Creator<WindowVisibilityItem>() { + public WindowVisibilityItem createFromParcel(Parcel in) { + return new WindowVisibilityItem(in); + } + + public WindowVisibilityItem[] newArray(int size) { + return new WindowVisibilityItem[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowVisibilityItem other = (WindowVisibilityItem) o; + return mShowWindow == other.mShowWindow; + } + + @Override + public int hashCode() { + return 17 + 31 * (mShowWindow ? 1 : 0); + } +} diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 837c00a72784..f8cdce64c139 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1156,6 +1156,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { dest.writeString(permission); dest.writeString(taskAffinity); dest.writeString(targetActivity); + dest.writeString(launchToken); dest.writeInt(flags); dest.writeInt(screenOrientation); dest.writeInt(configChanges); @@ -1282,6 +1283,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { permission = source.readString(); taskAffinity = source.readString(); targetActivity = source.readString(); + launchToken = source.readString(); flags = source.readInt(); screenOrientation = source.readInt(); configChanges = source.readInt(); diff --git a/core/java/android/content/pm/InstantAppInfo.java b/core/java/android/content/pm/InstantAppInfo.java index 67afc928fd78..cb04fc3ce8e1 100644 --- a/core/java/android/content/pm/InstantAppInfo.java +++ b/core/java/android/content/pm/InstantAppInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; @@ -31,6 +32,7 @@ import android.os.Parcelable; * * @hide */ +@SystemApi public final class InstantAppInfo implements Parcelable { private final ApplicationInfo mApplicationInfo; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 68bb5620de85..ebeaad78ea6a 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1258,9 +1258,12 @@ public class PackageParser { } } - pkg.setCodePath(packageDir.getAbsolutePath()); + pkg.setCodePath(packageDir.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; + } catch (IOException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to get path: " + lite.baseCodePath, e); } finally { IoUtils.closeQuietly(assetLoader); } @@ -1289,9 +1292,12 @@ public class PackageParser { try { final Package pkg = parseBaseApk(apkFile, assets, flags); - pkg.setCodePath(apkFile.getAbsolutePath()); + pkg.setCodePath(apkFile.getCanonicalPath()); pkg.setUse32bitAbi(lite.use32bitAbi); return pkg; + } catch (IOException e) { + throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, + "Failed to get path: " + apkFile, e); } finally { IoUtils.closeQuietly(assets); } diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS new file mode 100644 index 000000000000..84e81e4285d1 --- /dev/null +++ b/core/java/android/database/OWNERS @@ -0,0 +1,2 @@ +fkupolov@google.com +omakoto@google.com
\ No newline at end of file diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index af91e81959d1..811091e37da2 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -224,6 +224,7 @@ public abstract class BatteryStats implements Parcelable { * - Always On Display (screen doze mode) time and power * New in version 28: * - Light/Deep Doze power + * - WiFi Multicast Wakelock statistics (count & duration) */ static final int CHECKIN_VERSION = 28; @@ -313,6 +314,8 @@ public abstract class BatteryStats implements Parcelable { private static final String CAMERA_DATA = "cam"; private static final String VIDEO_DATA = "vid"; private static final String AUDIO_DATA = "aud"; + private static final String WIFI_MULTICAST_TOTAL_DATA = "wmct"; + private static final String WIFI_MULTICAST_DATA = "wmc"; public static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity"; @@ -516,6 +519,13 @@ public abstract class BatteryStats implements Parcelable { public abstract ArrayMap<String, ? extends Wakelock> getWakelockStats(); /** + * Returns the WiFi Multicast Wakelock statistics. + * + * @return a Timer Object for the per uid Multicast statistics. + */ + public abstract Timer getMulticastWakelockStats(); + + /** * Returns a mapping containing sync statistics. * * @return a Map from Strings to Timer objects. @@ -3363,13 +3373,16 @@ public abstract class BatteryStats implements Parcelable { screenDozeTime / 1000); - // Calculate wakelock times across all uids. + // Calculate both wakelock and wifi multicast wakelock times across all uids. long fullWakeLockTimeTotal = 0; long partialWakeLockTimeTotal = 0; + long multicastWakeLockTimeTotalMicros = 0; + int multicastWakeLockCountTotal = 0; for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); + // First calculating the wakelock stats final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -3387,6 +3400,13 @@ public abstract class BatteryStats implements Parcelable { rawRealtime, which); } } + + // Now calculating the wifi multicast wakelock stats + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } } // Dump network stats @@ -3502,6 +3522,11 @@ public abstract class BatteryStats implements Parcelable { } dumpLine(pw, 0 /* uid */, category, WIFI_SIGNAL_STRENGTH_COUNT_DATA, args); + // Dump Multicast total stats + dumpLine(pw, 0 /* uid */, category, WIFI_MULTICAST_TOTAL_DATA, + multicastWakeLockTimeTotalMicros / 1000, + multicastWakeLockCountTotal); + if (which == STATS_SINCE_UNPLUGGED) { dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), getDischargeCurrentLevel()); @@ -3828,6 +3853,18 @@ public abstract class BatteryStats implements Parcelable { } } + // WiFi Multicast Wakelock Statistics + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + final long totalMcWakelockTimeMs = + mcTimer.getTotalTimeLocked(rawRealtime, which) / 1000 ; + final int countMcWakelock = mcTimer.getCountLocked(which); + if(totalMcWakelockTimeMs > 0) { + dumpLine(pw, uid, category, WIFI_MULTICAST_DATA, + totalMcWakelockTimeMs, countMcWakelock); + } + } + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); for (int isy=syncs.size()-1; isy>=0; isy--) { final Timer timer = syncs.valueAt(isy); @@ -4327,15 +4364,18 @@ public abstract class BatteryStats implements Parcelable { pw.print(" Connectivity changes: "); pw.println(connChanges); } - // Calculate wakelock times across all uids. + // Calculate both wakelock and wifi multicast wakelock times across all uids. long fullWakeLockTimeTotalMicros = 0; long partialWakeLockTimeTotalMicros = 0; + long multicastWakeLockTimeTotalMicros = 0; + int multicastWakeLockCountTotal = 0; final ArrayList<TimerEntry> timers = new ArrayList<>(); for (int iu = 0; iu < NU; iu++) { final Uid u = uidStats.valueAt(iu); + // First calculate wakelock statistics final ArrayMap<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); for (int iw=wakelocks.size()-1; iw>=0; iw--) { @@ -4363,6 +4403,13 @@ public abstract class BatteryStats implements Parcelable { } } } + + // Next calculate wifi multicast wakelock statistics + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + multicastWakeLockTimeTotalMicros += mcTimer.getTotalTimeLocked(rawRealtime, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } } final long mobileRxTotalBytes = getNetworkActivityBytes(NETWORK_MOBILE_RX_DATA, which); @@ -4392,6 +4439,20 @@ public abstract class BatteryStats implements Parcelable { pw.println(sb.toString()); } + if (multicastWakeLockTimeTotalMicros != 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" Total WiFi Multicast wakelock Count: "); + sb.append(multicastWakeLockCountTotal); + pw.println(sb.toString()); + + sb.setLength(0); + sb.append(prefix); + sb.append(" Total WiFi Multicast wakelock time: "); + formatTimeMsNoSpace(sb, (multicastWakeLockTimeTotalMicros + 500) / 1000); + pw.println(sb.toString()); + } + pw.println(""); pw.print(prefix); sb.setLength(0); @@ -5309,6 +5370,24 @@ public abstract class BatteryStats implements Parcelable { } } + // Calculate multicast wakelock stats + final Timer mcTimer = u.getMulticastWakelockStats(); + if (mcTimer != null) { + final long multicastWakeLockTimeMicros = mcTimer.getTotalTimeLocked(rawRealtime, which); + final int multicastWakeLockCount = mcTimer.getCountLocked(which); + + if (multicastWakeLockTimeMicros > 0) { + sb.setLength(0); + sb.append(prefix); + sb.append(" WiFi Multicast Wakelock"); + sb.append(" count = "); + sb.append(multicastWakeLockCount); + sb.append(" time = "); + formatTimeMsNoSpace(sb, (multicastWakeLockTimeMicros + 500) / 1000); + pw.println(sb.toString()); + } + } + final ArrayMap<String, ? extends Timer> syncs = u.getSyncStats(); for (int isy=syncs.size()-1; isy>=0; isy--) { final Timer timer = syncs.valueAt(isy); @@ -7085,6 +7164,10 @@ public abstract class BatteryStats implements Parcelable { proto.end(wToken); } + // Wifi Multicast Wakelock (WIFI_MULTICAST_WAKELOCK_DATA) + dumpTimer(proto, UidProto.WIFI_MULTICAST_WAKELOCK, u.getMulticastWakelockStats(), + rawRealtimeUs, which); + // Wakeup alarms (WAKEUP_ALARM_DATA) for (int ipkg = packageStats.size() - 1; ipkg >= 0; --ipkg) { final Uid.Pkg ps = packageStats.valueAt(ipkg); @@ -7341,6 +7424,30 @@ public abstract class BatteryStats implements Parcelable { getLongestDeviceIdleModeTime(DEVICE_IDLE_MODE_LIGHT)); proto.end(mToken); + // Wifi multicast wakelock total stats (WIFI_MULTICAST_WAKELOCK_TOTAL_DATA) + // Calculate multicast wakelock stats across all uids. + long multicastWakeLockTimeTotalUs = 0; + int multicastWakeLockCountTotal = 0; + + for (int iu = 0; iu < uidStats.size(); iu++) { + final Uid u = uidStats.valueAt(iu); + + final Timer mcTimer = u.getMulticastWakelockStats(); + + if (mcTimer != null) { + multicastWakeLockTimeTotalUs += + mcTimer.getTotalTimeLocked(rawRealtimeUs, which); + multicastWakeLockCountTotal += mcTimer.getCountLocked(which); + } + } + + final long wmctToken = proto.start(SystemProto.WIFI_MULTICAST_WAKELOCK_TOTAL); + proto.write(SystemProto.WifiMulticastWakelockTotal.DURATION_MS, + multicastWakeLockTimeTotalUs / 1000); + proto.write(SystemProto.WifiMulticastWakelockTotal.COUNT, + multicastWakeLockCountTotal); + proto.end(wmctToken); + // Power use item (POWER_USE_ITEM_DATA) final List<BatterySipper> sippers = helper.getUsageList(); if (sippers != null) { diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 5dd8d05cfb98..068f5f7f51cc 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -555,6 +555,11 @@ public final class PowerManager { int FORCE_ALL_APPS_STANDBY = 11; /** + * Whether to enable background check on all apps or not. + */ + int FORCE_BACKGROUND_CHECK = 12; + + /** * Whether to disable non-essential sensors. (e.g. edge sensors.) */ int OPTIONAL_SENSORS = 13; diff --git a/core/java/android/os/UEventObserver.java b/core/java/android/os/UEventObserver.java index 5c80ca6559ae..69a3922585ce 100644 --- a/core/java/android/os/UEventObserver.java +++ b/core/java/android/os/UEventObserver.java @@ -108,7 +108,7 @@ public abstract class UEventObserver { * UEventObserver after this call. Repeated calls have no effect. */ public final void stopObserving() { - final UEventThread t = getThread(); + final UEventThread t = peekThread(); if (t != null) { t.removeObserver(this); } diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl index 2ffc7b028d9b..3b53260e5dd7 100644 --- a/core/java/android/os/storage/IStorageManager.aidl +++ b/core/java/android/os/storage/IStorageManager.aidl @@ -112,7 +112,7 @@ interface IStorageManager { * path belongs to a volume managed by vold, and that path is either * external storage data or OBB directory belonging to calling app. */ - int mkdirs(in String callingPkg, in String path) = 34; + void mkdirs(in String callingPkg, in String path) = 34; /** * Determines the type of the encryption password * @return PasswordType diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 0b007ddf50a1..4796712f2d98 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1123,6 +1123,15 @@ public class StorageManager { return FileUtils.roundStorageSize(Environment.getDataDirectory().getTotalSpace()); } + /** {@hide} */ + public void mkdirs(File file) { + try { + mStorageManager.mkdirs(mContext.getOpPackageName(), file.getAbsolutePath()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @removed */ public @NonNull StorageVolume[] getVolumeList() { return getVolumeList(mContext.getUserId(), 0); diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java index ff8b9dd77068..adf437cedd90 100644 --- a/core/java/android/provider/SearchIndexablesContract.java +++ b/core/java/android/provider/SearchIndexablesContract.java @@ -62,11 +62,25 @@ public class SearchIndexablesContract { public static final String NON_INDEXABLES_KEYS = "non_indexables_key"; /** + * Site map pairs data key + * + * @hide + */ + public static final String SITE_MAP_PAIRS_KEYS = "site_map_pairs"; + + /** * ContentProvider path for non indexable data keys. */ public static final String NON_INDEXABLES_KEYS_PATH = SETTINGS + "/" + NON_INDEXABLES_KEYS; /** + * ContentProvider path for sitemap keys. + * + * @hide + */ + public static final String SITE_MAP_PAIRS_PATH = SETTINGS + "/" + SITE_MAP_PAIRS_KEYS; + + /** * Indexable xml resources columns. */ public static final String[] INDEXABLES_XML_RES_COLUMNS = new String[] { @@ -113,6 +127,18 @@ public class SearchIndexablesContract { }; /** + * Columns for site map queries. + * + * @hide + */ + public static final String[] SITE_MAP_COLUMNS = new String[] { + SiteMapColumns.PARENT_CLASS, + SiteMapColumns.PARENT_TITLE, + SiteMapColumns.CHILD_CLASS, + SiteMapColumns.CHILD_TITLE, + }; + + /** * Indexable raw data columns indices. */ public static final int COLUMN_INDEX_RAW_RANK = 0; @@ -169,6 +195,16 @@ public class SearchIndexablesContract { } /** + * @hide + */ + public static final class SiteMapColumns { + public static final String PARENT_CLASS = "parent_class"; + public static final String CHILD_CLASS = "child_class"; + public static final String PARENT_TITLE = "parent_title"; + public static final String CHILD_TITLE = "child_title"; + } + + /** * Constants related to a {@link SearchIndexableData}. * * This is the raw data that is stored into an Index. This is related to diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java index 3120e543561d..138e77b69627 100644 --- a/core/java/android/provider/SearchIndexablesProvider.java +++ b/core/java/android/provider/SearchIndexablesProvider.java @@ -72,6 +72,7 @@ public abstract class SearchIndexablesProvider extends ContentProvider { private static final int MATCH_RES_CODE = 1; private static final int MATCH_RAW_CODE = 2; private static final int MATCH_NON_INDEXABLE_KEYS_CODE = 3; + private static final int MATCH_SITE_MAP_PAIRS_CODE = 4; /** * Implementation is provided by the parent class. @@ -87,6 +88,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider { MATCH_RAW_CODE); mMatcher.addURI(mAuthority, SearchIndexablesContract.NON_INDEXABLES_KEYS_PATH, MATCH_NON_INDEXABLE_KEYS_CODE); + mMatcher.addURI(mAuthority, SearchIndexablesContract.SITE_MAP_PAIRS_PATH, + MATCH_SITE_MAP_PAIRS_CODE); // Sanity check our setup if (!info.exported) { @@ -112,6 +115,8 @@ public abstract class SearchIndexablesProvider extends ContentProvider { return queryRawData(null); case MATCH_NON_INDEXABLE_KEYS_CODE: return queryNonIndexableKeys(null); + case MATCH_SITE_MAP_PAIRS_CODE: + return querySiteMapPairs(); default: throw new UnsupportedOperationException("Unknown Uri " + uri); } @@ -150,6 +155,17 @@ public abstract class SearchIndexablesProvider extends ContentProvider { */ public abstract Cursor queryNonIndexableKeys(String[] projection); + /** + * Returns a {@link Cursor}, where rows are [parent class, child class] entries to form a site + * map. The list of pairs should be as complete as possible. + * + * @hide + */ + public Cursor querySiteMapPairs() { + // By default no-op. + return null; + } + @Override public String getType(Uri uri) { switch (mMatcher.match(uri)) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 994575545c5c..729c0ff6febb 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9379,6 +9379,9 @@ public final class Settings { /** {@hide} */ public static final String BLUETOOTH_PAN_PRIORITY_PREFIX = "bluetooth_pan_priority_"; + /** {@hide} */ + public static final String + BLUETOOTH_HEARING_AID_PRIORITY_PREFIX = "bluetooth_hearing_aid_priority_"; /** * Activity manager specific settings. @@ -9745,6 +9748,14 @@ public final class Settings { } /** + * Get the key that retrieves a bluetooth hearing aid priority. + * @hide + */ + public static final String getBluetoothHearingAidPriorityKey(String address) { + return BLUETOOTH_HEARING_AID_PRIORITY_PREFIX + address.toUpperCase(Locale.ROOT); + } + + /** * Get the key that retrieves a bluetooth map priority. * @hide */ diff --git a/core/java/android/service/autofill/NegationValidator.java b/core/java/android/service/autofill/NegationValidator.java new file mode 100644 index 000000000000..a963f9f94346 --- /dev/null +++ b/core/java/android/service/autofill/NegationValidator.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 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.service.autofill; + +import static android.view.autofill.Helper.sDebug; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.Preconditions; + +/** + * Validator used to implement a {@code NOT} logical operation. + * + * @hide + */ +final class NegationValidator extends InternalValidator { + @NonNull private final InternalValidator mValidator; + + NegationValidator(@NonNull InternalValidator validator) { + mValidator = Preconditions.checkNotNull(validator); + } + + @Override + public boolean isValid(@NonNull ValueFinder finder) { + return !mValidator.isValid(finder); + } + + ///////////////////////////////////// + // Object "contract" methods. // + ///////////////////////////////////// + @Override + public String toString() { + if (!sDebug) return super.toString(); + + return "NegationValidator: [validator=" + mValidator + "]"; + } + + ///////////////////////////////////// + // Parcelable "contract" methods. // + ///////////////////////////////////// + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mValidator, flags); + } + + public static final Parcelable.Creator<NegationValidator> CREATOR = + new Parcelable.Creator<NegationValidator>() { + @Override + public NegationValidator createFromParcel(Parcel parcel) { + return new NegationValidator(parcel.readParcelable(null)); + } + + @Override + public NegationValidator[] newArray(int size) { + return new NegationValidator[size]; + } + }; +} diff --git a/core/java/android/service/autofill/Validators.java b/core/java/android/service/autofill/Validators.java index 51b503c21690..1c8386870fe7 100644 --- a/core/java/android/service/autofill/Validators.java +++ b/core/java/android/service/autofill/Validators.java @@ -52,6 +52,19 @@ public final class Validators { return new OptionalValidators(getInternalValidators(validators)); } + /** + * Creates a validator that is valid only if {@code validator} is not. + * + * @throws IllegalArgumentException if {@code validator} is an instance of a class that is not + * provided by the Android System. + */ + @NonNull + public static Validator not(@NonNull Validator validator) { + Preconditions.checkArgument(validator instanceof InternalValidator, + "validator not provided by Android System: " + validator); + return new NegationValidator((InternalValidator) validator); + } + private static InternalValidator[] getInternalValidators(Validator[] validators) { Preconditions.checkArrayElementsNotNull(validators, "validators"); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 1c6275fb8dc1..dd0ae3393f1d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -889,7 +889,8 @@ public abstract class WallpaperService extends Service { mFinalStableInsets.set(mDispatchedStableInsets); WindowInsets insets = new WindowInsets(mFinalSystemInsets, null, mFinalStableInsets, - getResources().getConfiguration().isScreenRound(), false); + getResources().getConfiguration().isScreenRound(), false, + null /* displayCutout */); if (DEBUG) { Log.v(TAG, "dispatching insets=" + insets); } diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java new file mode 100644 index 000000000000..19cd42e1fa18 --- /dev/null +++ b/core/java/android/view/DisplayCutout.java @@ -0,0 +1,408 @@ +/* + * Copyright 2017 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.view; + +import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + +import android.annotation.NonNull; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a part of the display that is not functional for displaying content. + * + * <p>{@code DisplayCutout} is immutable. + * + * @hide will become API + */ +public final class DisplayCutout { + + private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0); + private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>(); + + /** + * An instance where {@link #hasCutout()} returns {@code false}. + * + * @hide + */ + public static final DisplayCutout NO_CUTOUT = + new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST); + + private final Rect mSafeInsets; + private final Rect mBoundingRect; + private final List<Point> mBoundingPolygon; + + /** + * Creates a DisplayCutout instance. + * + * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged. + * + * @hide + */ + @VisibleForTesting + public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) { + mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT; + mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT; + mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST; + } + + /** + * Returns whether there is a cutout. + * + * If false, the safe insets will all return zero, and the bounding box or polygon will be + * empty or outside the content view. + * + * @return {@code true} if there is a cutout, {@code false} otherwise + */ + public boolean hasCutout() { + return !mSafeInsets.equals(ZERO_RECT); + } + + /** Returns the inset from the top which avoids the display cutout. */ + public int getSafeInsetTop() { + return mSafeInsets.top; + } + + /** Returns the inset from the bottom which avoids the display cutout. */ + public int getSafeInsetBottom() { + return mSafeInsets.bottom; + } + + /** Returns the inset from the left which avoids the display cutout. */ + public int getSafeInsetLeft() { + return mSafeInsets.left; + } + + /** Returns the inset from the right which avoids the display cutout. */ + public int getSafeInsetRight() { + return mSafeInsets.right; + } + + /** + * Obtains the safe insets in a rect. + * + * @param out a rect which is set to the safe insets. + * @hide + */ + public void getSafeInsets(@NonNull Rect out) { + out.set(mSafeInsets); + } + + /** + * Obtains the bounding rect of the cutout. + * + * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative + * to the top-left corner of the content view. + */ + public void getBoundingRect(@NonNull Rect outRect) { + outRect.set(mBoundingRect); + } + + /** + * Obtains the bounding polygon of the cutout. + * + * @param outPolygon is filled with a list of points representing the corners of a convex + * polygon which covers the cutout. Coordinates are relative to the + * top-left corner of the content view. + */ + public void getBoundingPolygon(List<Point> outPolygon) { + outPolygon.clear(); + for (int i = 0; i < mBoundingPolygon.size(); i++) { + outPolygon.add(new Point(mBoundingPolygon.get(i))); + } + } + + @Override + public int hashCode() { + int result = mSafeInsets.hashCode(); + result = result * 31 + mBoundingRect.hashCode(); + result = result * 31 + mBoundingPolygon.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof DisplayCutout) { + DisplayCutout c = (DisplayCutout) o; + return mSafeInsets.equals(c.mSafeInsets) + && mBoundingRect.equals(c.mBoundingRect) + && mBoundingPolygon.equals(c.mBoundingPolygon); + } + return false; + } + + @Override + public String toString() { + return "DisplayCutout{insets=" + mSafeInsets + + " bounding=" + mBoundingRect + + "}"; + } + + /** + * Insets the reference frame of the cutout in the given directions. + * + * @return a copy of this instance which has been inset + * @hide + */ + public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { + if (mBoundingRect.isEmpty() + || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { + return this; + } + + Rect safeInsets = new Rect(mSafeInsets); + Rect boundingRect = new Rect(mBoundingRect); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + getBoundingPolygon(boundingPolygon); + + // Note: it's not really well defined what happens when the inset is negative, because we + // don't know if the safe inset needs to expand in general. + if (insetTop > 0 || safeInsets.top > 0) { + safeInsets.top = atLeastZero(safeInsets.top - insetTop); + } + if (insetBottom > 0 || safeInsets.bottom > 0) { + safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom); + } + if (insetLeft > 0 || safeInsets.left > 0) { + safeInsets.left = atLeastZero(safeInsets.left - insetLeft); + } + if (insetRight > 0 || safeInsets.right > 0) { + safeInsets.right = atLeastZero(safeInsets.right - insetRight); + } + + boundingRect.offset(-insetLeft, -insetTop); + offset(boundingPolygon, -insetLeft, -insetTop); + + return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + } + + /** + * Calculates the safe insets relative to the given reference frame. + * + * @return a copy of this instance with the safe insets calculated + * @hide + */ + public DisplayCutout calculateRelativeTo(Rect frame) { + if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) { + return NO_CUTOUT; + } + + Rect boundingRect = new Rect(mBoundingRect); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + getBoundingPolygon(boundingPolygon); + + return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon); + } + + private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect, + ArrayList<Point> boundingPolygon) { + Rect safeRect = new Rect(); + int bestArea = 0; + int bestVariant = 0; + for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) { + int area = calculateInsetVariantArea(frame, boundingRect, variant, safeRect); + if (bestArea < area) { + bestArea = area; + bestVariant = variant; + } + } + calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect); + if (safeRect.isEmpty()) { + // The entire frame overlaps with the cutout. + safeRect.set(0, frame.height(), 0, 0); + } else { + // Convert safeRect to insets relative to frame. We're reusing the rect here to avoid + // an allocation. + safeRect.set( + Math.max(0, safeRect.left - frame.left), + Math.max(0, safeRect.top - frame.top), + Math.max(0, frame.right - safeRect.right), + Math.max(0, frame.bottom - safeRect.bottom)); + } + + boundingRect.offset(-frame.left, -frame.top); + offset(boundingPolygon, -frame.left, -frame.top); + + return new DisplayCutout(safeRect, boundingRect, boundingPolygon); + } + + private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant, + Rect outSafeRect) { + switch (variant) { + case ROTATION_0: + outSafeRect.set(frame.left, frame.top, frame.right, boundingRect.top); + break; + case ROTATION_90: + outSafeRect.set(frame.left, frame.top, boundingRect.left, frame.bottom); + break; + case ROTATION_180: + outSafeRect.set(frame.left, boundingRect.bottom, frame.right, frame.bottom); + break; + case ROTATION_270: + outSafeRect.set(boundingRect.right, frame.top, frame.right, frame.bottom); + break; + } + + return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height(); + } + + private static int atLeastZero(int value) { + return value < 0 ? 0 : value; + } + + private static void offset(ArrayList<Point> points, int dx, int dy) { + for (int i = 0; i < points.size(); i++) { + points.get(i).offset(dx, dy); + } + } + + /** + * Creates an instance from a bounding polygon. + * + * @hide + */ + public static DisplayCutout fromBoundingPolygon(List<Point> points) { + Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE, + Integer.MIN_VALUE, Integer.MIN_VALUE); + ArrayList<Point> boundingPolygon = new ArrayList<>(); + + for (int i = 0; i < points.size(); i++) { + Point point = points.get(i); + boundingRect.left = Math.min(boundingRect.left, point.x); + boundingRect.right = Math.max(boundingRect.right, point.x); + boundingRect.top = Math.min(boundingRect.top, point.y); + boundingRect.bottom = Math.max(boundingRect.bottom, point.y); + boundingPolygon.add(new Point(point)); + } + + return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon); + } + + /** + * Helper class for passing {@link DisplayCutout} through binder. + * + * Needed, because {@code readFromParcel} cannot be used with immutable classes. + * + * @hide + */ + public static final class ParcelableWrapper implements Parcelable { + + private DisplayCutout mInner; + + public ParcelableWrapper() { + this(NO_CUTOUT); + } + + public ParcelableWrapper(DisplayCutout cutout) { + mInner = cutout; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + if (mInner == NO_CUTOUT) { + out.writeInt(0); + } else { + out.writeInt(1); + out.writeTypedObject(mInner.mSafeInsets, flags); + out.writeTypedObject(mInner.mBoundingRect, flags); + out.writeTypedList(mInner.mBoundingPolygon, flags); + } + } + + /** + * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing + * instance. + * + * Needed for AIDL out parameters. + */ + public void readFromParcel(Parcel in) { + mInner = readCutout(in); + } + + public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { + @Override + public ParcelableWrapper createFromParcel(Parcel in) { + return new ParcelableWrapper(readCutout(in)); + } + + @Override + public ParcelableWrapper[] newArray(int size) { + return new ParcelableWrapper[size]; + } + }; + + private static DisplayCutout readCutout(Parcel in) { + if (in.readInt() == 0) { + return NO_CUTOUT; + } + + ArrayList<Point> boundingPolygon = new ArrayList<>(); + + Rect safeInsets = in.readTypedObject(Rect.CREATOR); + Rect boundingRect = in.readTypedObject(Rect.CREATOR); + in.readTypedList(boundingPolygon, Point.CREATOR); + + return new DisplayCutout(safeInsets, boundingRect, boundingPolygon); + } + + public DisplayCutout get() { + return mInner; + } + + public void set(ParcelableWrapper cutout) { + mInner = cutout.get(); + } + + public void set(DisplayCutout cutout) { + mInner = cutout; + } + + @Override + public int hashCode() { + return mInner.hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof ParcelableWrapper + && mInner.equals(((ParcelableWrapper) o).mInner); + } + + @Override + public String toString() { + return String.valueOf(mInner); + } + } +} diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index aa2f1c1a9e24..cf059104e373 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -55,8 +55,8 @@ public class SurfaceControl { private static native void nativeScreenshot(IBinder displayToken, Surface consumer, Rect sourceCrop, int width, int height, int minLayer, int maxLayer, boolean allLayers, boolean useIdentityTransform); - private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer, - int rotation); + private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken, + Rect sourceCrop, float frameScale); private static native long nativeCreateTransaction(); private static native long nativeGetNativeTransactionFinalizer(); @@ -1177,14 +1177,16 @@ public class SurfaceControl { * Captures a layer and its children into the provided {@link Surface}. * * @param layerHandleToken The root layer to capture. - * @param consumer The {@link Surface} to capture the layer into. - * @param rotation Apply a custom clockwise rotation to the screenshot, i.e. - * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its - * native portrait orientation by default, so this is useful for returning - * captures that are independent of device orientation. + * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new + * Rect()' or null if no cropping is desired. + * @param frameScale The desired scale of the returned buffer; the raw + * screen will be scaled up/down. + * + * @return Returns a GraphicBuffer that contains the layer capture. */ - public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) { - nativeCaptureLayers(layerHandleToken, consumer, rotation); + public static GraphicBuffer captureLayers(IBinder layerHandleToken, Rect sourceCrop, + float frameScale) { + return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale); } public static class Transaction implements Closeable { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e36a2989afbc..345e30030534 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -2929,6 +2929,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * 1 PFLAG3_TEMPORARY_DETACH * 1 PFLAG3_NO_REVEAL_ON_FOCUS * 1 PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT + * 1 PFLAG3_SCREEN_READER_FOCUSABLE * |-------|-------|-------|-------| */ @@ -3209,6 +3210,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT = 0x8000000; + /** + * Works like focusable for screen readers, but without the side effects on input focus. + * @see #setScreenReaderFocusable(boolean) + */ + private static final int PFLAG3_SCREEN_READER_FOCUSABLE = 0x10000000; + /* End of masks for mPrivateFlags3 */ /** @@ -5344,6 +5351,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setDefaultFocusHighlightEnabled(a.getBoolean(attr, true)); } break; + case R.styleable.View_screenReaderFocusable: + if (a.peekValue(attr) != null) { + setScreenReaderFocusable(a.getBoolean(attr, false)); + } + break; } } @@ -8394,6 +8406,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setEnabled(isEnabled()); info.setClickable(isClickable()); info.setFocusable(isFocusable()); + info.setScreenReaderFocusable(isScreenReaderFocusable()); info.setFocused(isFocused()); info.setAccessibilityFocused(isAccessibilityFocused()); info.setSelected(isSelected()); @@ -10350,6 +10363,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Returns whether the view should be treated as a focusable unit by screen reader + * accessibility tools. + * @see #setScreenReaderFocusable(boolean) + * + * @return Whether the view should be treated as a focusable unit by screen reader. + */ + public boolean isScreenReaderFocusable() { + return (mPrivateFlags3 & PFLAG3_SCREEN_READER_FOCUSABLE) != 0; + } + + /** + * When screen readers (one type of accessibility tool) decide what should be read to the + * user, they typically look for input focusable ({@link #isFocusable()}) parents of + * non-focusable text items, and read those focusable parents and their non-focusable children + * as a unit. In some situations, this behavior is desirable for views that should not take + * input focus. Setting an item to be screen reader focusable requests that the view be + * treated as a unit by screen readers without any effect on input focusability. The default + * value of {@code false} lets screen readers use other signals, like focusable, to determine + * how to group items. + * + * @param screenReaderFocusable Whether the view should be treated as a unit by screen reader + * accessibility tools. + */ + public void setScreenReaderFocusable(boolean screenReaderFocusable) { + int pflags3 = mPrivateFlags3; + if (screenReaderFocusable) { + pflags3 |= PFLAG3_SCREEN_READER_FOCUSABLE; + } else { + pflags3 &= ~PFLAG3_SCREEN_READER_FOCUSABLE; + } + + if (pflags3 != mPrivateFlags3) { + mPrivateFlags3 = pflags3; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + } + + /** * Find the nearest view in the specified direction that can take focus. * This does not actually give focus to that view. * diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 929beaea42b8..122df934111d 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -2591,7 +2591,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { - // If the event is targeting accessiiblity focus we give it to the + // If the event is targeting accessibility focus we give it to the // view that has accessibility focus and if it does not handle it // we clear the flag and dispatch the event to all children as usual. // We are looking up the accessibility focused host to avoid keeping diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index e9509b798c76..1c9d86398a8d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1563,7 +1563,7 @@ public final class ViewRootImpl implements ViewParent, mLastWindowInsets = new WindowInsets(contentInsets, null /* windowDecorInsets */, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), - mAttachInfo.mAlwaysConsumeNavBar); + mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */); } return mLastWindowInsets; } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 750931ab661c..df124ac5be28 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -17,6 +17,7 @@ package android.view; +import android.annotation.NonNull; import android.graphics.Rect; /** @@ -36,6 +37,7 @@ public final class WindowInsets { private Rect mStableInsets; private Rect mTempRect; private boolean mIsRound; + private DisplayCutout mDisplayCutout; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -47,6 +49,7 @@ public final class WindowInsets { private boolean mSystemWindowInsetsConsumed = false; private boolean mWindowDecorInsetsConsumed = false; private boolean mStableInsetsConsumed = false; + private boolean mCutoutConsumed = false; private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0); @@ -59,12 +62,12 @@ public final class WindowInsets { public static final WindowInsets CONSUMED; static { - CONSUMED = new WindowInsets(null, null, null, false, false); + CONSUMED = new WindowInsets(null, null, null, false, false, null); } /** @hide */ public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets, - boolean isRound, boolean alwaysConsumeNavBar) { + boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) { mSystemWindowInsetsConsumed = systemWindowInsets == null; mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets; @@ -76,6 +79,9 @@ public final class WindowInsets { mIsRound = isRound; mAlwaysConsumeNavBar = alwaysConsumeNavBar; + + mCutoutConsumed = displayCutout == null; + mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout; } /** @@ -92,11 +98,13 @@ public final class WindowInsets { mStableInsetsConsumed = src.mStableInsetsConsumed; mIsRound = src.mIsRound; mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar; + mDisplayCutout = src.mDisplayCutout; + mCutoutConsumed = src.mCutoutConsumed; } /** @hide */ public WindowInsets(Rect systemWindowInsets) { - this(systemWindowInsets, null, null, false, false); + this(systemWindowInsets, null, null, false, false, null); } /** @@ -260,9 +268,34 @@ public final class WindowInsets { * @return true if any inset values are nonzero */ public boolean hasInsets() { - return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets(); + return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets() + || mDisplayCutout.hasCutout(); + } + + /** + * @return the display cutout + * @see DisplayCutout + * @hide pending API + */ + @NonNull + public DisplayCutout getDisplayCutout() { + return mDisplayCutout; + } + + /** + * Returns a copy of this WindowInsets with the cutout fully consumed. + * + * @return A modified copy of this WindowInsets + * @hide pending API + */ + public WindowInsets consumeCutout() { + final WindowInsets result = new WindowInsets(this); + result.mDisplayCutout = DisplayCutout.NO_CUTOUT; + result.mCutoutConsumed = true; + return result; } + /** * Check if these insets have been fully consumed. * @@ -277,7 +310,8 @@ public final class WindowInsets { * @return true if the insets have been fully consumed. */ public boolean isConsumed() { - return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed; + return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed + && mCutoutConsumed; } /** @@ -495,7 +529,9 @@ public final class WindowInsets { public String toString() { return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets + " windowDecorInsets=" + mWindowDecorInsets - + " stableInsets=" + mStableInsets + - (isRound() ? " round}" : "}"); + + " stableInsets=" + mStableInsets + + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "") + + (isRound() ? " round" : "") + + "}"; } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index eb5fc92e5ccc..905c0715ecb8 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -604,8 +604,10 @@ public interface WindowManager extends ViewManager { public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16; /** - * Window type: panel that slides out from under the status bar - * In multiuser systems shows on all users' windows. + * Window type: panel that slides out from over the status bar + * In multiuser systems shows on all users' windows. These windows + * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL} + * windows. * @hide */ public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17; diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index d7851171cd67..da5a1cd67922 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -23,8 +23,6 @@ import android.util.LongArray; import android.util.LongSparseArray; import android.util.SparseArray; -import com.android.internal.annotations.VisibleForTesting; - import java.util.ArrayList; import java.util.List; @@ -33,8 +31,7 @@ import java.util.List; * It is updated when windows change or nodes change. * @hide */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) -public final class AccessibilityCache { +public class AccessibilityCache { private static final String LOG_TAG = "AccessibilityCache"; @@ -329,6 +326,8 @@ public final class AccessibilityCache { final long oldParentId = oldInfo.getParentNodeId(); if (info.getParentNodeId() != oldParentId) { clearSubTreeLocked(windowId, oldParentId); + } else { + oldInfo.recycle(); } } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index c3d6c695982d..d890f329dd31 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -28,6 +28,8 @@ import android.util.Log; import android.util.LongSparseArray; import android.util.SparseArray; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -86,6 +88,12 @@ public final class AccessibilityInteractionClient private static final LongSparseArray<AccessibilityInteractionClient> sClients = new LongSparseArray<>(); + private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = + new SparseArray<>(); + + private static AccessibilityCache sAccessibilityCache = + new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher()); + private final AtomicInteger mInteractionIdCounter = new AtomicInteger(); private final Object mInstanceLock = new Object(); @@ -100,12 +108,6 @@ public final class AccessibilityInteractionClient private Message mSameThreadMessage; - private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = - new SparseArray<>(); - - private static final AccessibilityCache sAccessibilityCache = - new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher()); - /** * @return The client for the current thread. */ @@ -133,6 +135,50 @@ public final class AccessibilityInteractionClient } } + /** + * Gets a cached accessibility service connection. + * + * @param connectionId The connection id. + * @return The cached connection if such. + */ + public static IAccessibilityServiceConnection getConnection(int connectionId) { + synchronized (sConnectionCache) { + return sConnectionCache.get(connectionId); + } + } + + /** + * Adds a cached accessibility service connection. + * + * @param connectionId The connection id. + * @param connection The connection. + */ + public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) { + synchronized (sConnectionCache) { + sConnectionCache.put(connectionId, connection); + } + } + + /** + * Removes a cached accessibility service connection. + * + * @param connectionId The connection id. + */ + public static void removeConnection(int connectionId) { + synchronized (sConnectionCache) { + sConnectionCache.remove(connectionId); + } + } + + /** + * This method is only for testing. Replacing the cache is a generally terrible idea, but + * tests need to be able to verify this class's interactions with the cache + */ + @VisibleForTesting + public static void setCache(AccessibilityCache cache) { + sAccessibilityCache = cache; + } + private AccessibilityInteractionClient() { /* reducing constructor visibility */ } @@ -300,7 +346,7 @@ public final class AccessibilityInteractionClient if (success) { List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache); if (infos != null && !infos.isEmpty()) { for (int i = 1; i < infos.size(); i++) { infos.get(i).recycle(); @@ -356,7 +402,7 @@ public final class AccessibilityInteractionClient List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); return infos; } } @@ -409,7 +455,7 @@ public final class AccessibilityInteractionClient List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear( interactionId); if (infos != null) { - finalizeAndCacheAccessibilityNodeInfos(infos, connectionId); + finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false); return infos; } } @@ -460,7 +506,7 @@ public final class AccessibilityInteractionClient if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); return info; } } else { @@ -509,7 +555,7 @@ public final class AccessibilityInteractionClient if (success) { AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear( interactionId); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false); return info; } } else { @@ -731,13 +777,17 @@ public final class AccessibilityInteractionClient * * @param info The info. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if + * this value is {@code false} */ private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info, - int connectionId) { + int connectionId, boolean bypassCache) { if (info != null) { info.setConnectionId(connectionId); info.setSealed(true); - sAccessibilityCache.add(info); + if (!bypassCache) { + sAccessibilityCache.add(info); + } } } @@ -746,14 +796,16 @@ public final class AccessibilityInteractionClient * * @param infos The {@link AccessibilityNodeInfo}s. * @param connectionId The id of the connection to the system. + * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if + * this value is {@code false} */ private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos, - int connectionId) { + int connectionId, boolean bypassCache) { if (infos != null) { final int infosCount = infos.size(); for (int i = 0; i < infosCount; i++) { AccessibilityNodeInfo info = infos.get(i); - finalizeAndCacheAccessibilityNodeInfo(info, connectionId); + finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache); } } } @@ -773,41 +825,6 @@ public final class AccessibilityInteractionClient } /** - * Gets a cached accessibility service connection. - * - * @param connectionId The connection id. - * @return The cached connection if such. - */ - public IAccessibilityServiceConnection getConnection(int connectionId) { - synchronized (sConnectionCache) { - return sConnectionCache.get(connectionId); - } - } - - /** - * Adds a cached accessibility service connection. - * - * @param connectionId The connection id. - * @param connection The connection. - */ - public void addConnection(int connectionId, IAccessibilityServiceConnection connection) { - synchronized (sConnectionCache) { - sConnectionCache.put(connectionId, connection); - } - } - - /** - * Removes a cached accessibility service connection. - * - * @param connectionId The connection id. - */ - public void removeConnection(int connectionId) { - synchronized (sConnectionCache) { - sConnectionCache.remove(connectionId); - } - } - - /** * Checks whether the infos are a fully connected tree with no duplicates. * * @param infos The result list to check. diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 9bdd3ffc9fb2..faea9200c522 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -635,6 +635,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000; + private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000; + private static final int BOOLEAN_PROPERTY_IS_SHOWING_HINT = 0x0100000; /** @@ -2321,6 +2323,37 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note + * that {@code false} indicates that it is not explicitly marked, not that the node is not + * a focusable unit. Screen readers should generally used other signals, such as + * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive + * focus. + * + * @return {@code true} if the node is specifically marked as a focusable unit for screen + * readers, {@code false} otherwise. + * + * @see View#isScreenReaderFocusable() + */ + public boolean isScreenReaderFocusable() { + return getBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE); + } + + /** + * Sets whether the node should be considered a focusable unit by a screen reader. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + * </p> + * + * @param screenReaderFocusable {@code true} if the node is a focusable unit for screen readers, + * {@code false} otherwise. + */ + public void setScreenReaderFocusable(boolean screenReaderFocusable) { + setBooleanProperty(BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE, screenReaderFocusable); + } + + /** * Returns whether the node's text represents a hint for the user to enter text. It should only * be {@code true} if the node has editable text. * diff --git a/core/java/com/android/internal/content/ReferrerIntent.java b/core/java/com/android/internal/content/ReferrerIntent.java index 8d9a1cf9eee5..76dcc9bba91c 100644 --- a/core/java/com/android/internal/content/ReferrerIntent.java +++ b/core/java/com/android/internal/content/ReferrerIntent.java @@ -19,6 +19,8 @@ package com.android.internal.content; import android.content.Intent; import android.os.Parcel; +import java.util.Objects; + /** * Subclass of Intent that also contains referrer (as a package name) information. */ @@ -48,4 +50,21 @@ public class ReferrerIntent extends Intent { return new ReferrerIntent[size]; } }; + + @Override + public boolean equals(Object obj) { + if (obj == null || !(obj instanceof ReferrerIntent)) { + return false; + } + final ReferrerIntent other = (ReferrerIntent) obj; + return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + filterHashCode(); + result = 31 * result + Objects.hashCode(mReferrer); + return result; + } } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 6ede72d49806..56d0bb229b80 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -6020,6 +6020,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public Timer getMulticastWakelockStats() { + return mWifiMulticastTimer; + } + + @Override public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() { return mSyncStats.getMap(); } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 3ad4da6b6580..421e0de52cc0 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -517,23 +517,7 @@ static jint nativeAttachAndQueueBuffer(JNIEnv *env, jclass clazz, jlong nativeOb jobject graphicBuffer) { Surface* surface = reinterpret_cast<Surface*>(nativeObject); sp<GraphicBuffer> bp = graphicBufferForJavaObject(env, graphicBuffer); - if (bp == nullptr) { - return BAD_VALUE; - } - int err = ((ANativeWindow*)surface)->perform(surface, NATIVE_WINDOW_API_CONNECT, - NATIVE_WINDOW_API_CPU); - if (err != OK) { - return err; - } - err = surface->attachBuffer(bp->getNativeBuffer()); - if (err != OK) { - return err; - } - err = ((ANativeWindow*)surface)->queueBuffer(surface, bp->getNativeBuffer(), -1); - if (err != OK) { - return err; - } - err = surface->disconnect(NATIVE_WINDOW_API_CPU); + int err = Surface::attachAndQueueBuffer(surface, bp); return err; } diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp index bb1bfad26dc3..8c968a2a7083 100644 --- a/core/jni/android_view_SurfaceControl.cpp +++ b/core/jni/android_view_SurfaceControl.cpp @@ -167,7 +167,7 @@ static jobject nativeScreenshotToBuffer(JNIEnv* env, jclass clazz, maxLayer = INT32_MAX; } sp<GraphicBuffer> buffer; - status_t res = ScreenshotClient::captureToBuffer(displayToken, + status_t res = ScreenshotClient::capture(displayToken, sourceCrop, width, height, minLayer, maxLayer, useIdentityTransform, rotation, &buffer); if (res != NO_ERROR) { @@ -201,15 +201,18 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, maxLayer = INT32_MAX; } - res = screenshot->update(displayToken, sourceCrop, width, height, - minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation)); + sp<GraphicBuffer> buffer; + res = ScreenshotClient::capture(displayToken, sourceCrop, width, height, + minLayer, maxLayer, useIdentityTransform, static_cast<uint32_t>(rotation), &buffer); if (res != NO_ERROR) { return NULL; } SkColorType colorType; SkAlphaType alphaType; - switch (screenshot->getFormat()) { + + PixelFormat format = buffer->getPixelFormat(); + switch (format) { case PIXEL_FORMAT_RGBX_8888: { colorType = kRGBA_8888_SkColorType; alphaType = kOpaque_SkAlphaType; @@ -235,75 +238,71 @@ static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz, } } - sk_sp<SkColorSpace> colorSpace; - if (screenshot->getDataSpace() == HAL_DATASPACE_DISPLAY_P3) { - colorSpace = SkColorSpace::MakeRGB( - SkColorSpace::kSRGB_RenderTargetGamma, SkColorSpace::kDCIP3_D65_Gamut); - } else { - colorSpace = SkColorSpace::MakeSRGB(); - } - - SkImageInfo screenshotInfo = SkImageInfo::Make(screenshot->getWidth(), - screenshot->getHeight(), - colorType, - alphaType, - colorSpace); - - const size_t rowBytes = - screenshot->getStride() * android::bytesPerPixel(screenshot->getFormat()); + SkImageInfo info = SkImageInfo::Make(buffer->getWidth(), buffer->getHeight(), + colorType, alphaType, + SkColorSpace::MakeSRGB()); - if (!screenshotInfo.width() || !screenshotInfo.height()) { - return NULL; - } - - auto bitmap = new Bitmap( - (void*) screenshot->getPixels(), (void*) screenshot.get(), DeleteScreenshot, - screenshotInfo, rowBytes); - screenshot.release(); - bitmap->setImmutable(); - return bitmap::createBitmap(env, bitmap, - android::bitmap::kBitmapCreateFlag_Premultiplied, NULL); + auto bitmap = sk_sp<Bitmap>(new Bitmap(buffer.get(), info)); + return bitmap::createBitmap(env, bitmap.release(), + android::bitmap::kBitmapCreateFlag_Premultiplied, NULL); } static void nativeScreenshot(JNIEnv* env, jclass clazz, jobject displayTokenObj, jobject surfaceObj, jobject sourceCropObj, jint width, jint height, jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform) { sp<IBinder> displayToken = ibinderForJavaObject(env, displayTokenObj); - if (displayToken != NULL) { - sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj); - if (consumer != NULL) { - int left = env->GetIntField(sourceCropObj, gRectClassInfo.left); - int top = env->GetIntField(sourceCropObj, gRectClassInfo.top); - int right = env->GetIntField(sourceCropObj, gRectClassInfo.right); - int bottom = env->GetIntField(sourceCropObj, gRectClassInfo.bottom); - Rect sourceCrop(left, top, right, bottom); - - if (allLayers) { - minLayer = INT32_MIN; - maxLayer = INT32_MAX; - } - ScreenshotClient::capture(displayToken, - consumer->getIGraphicBufferProducer(), sourceCrop, - width, height, minLayer, maxLayer, - useIdentityTransform); - } + if (displayToken == NULL) { + return; } + + sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj); + if (consumer == NULL) { + return; + } + + Rect sourceCrop; + if (sourceCropObj != NULL) { + sourceCrop = rectFromObj(env, sourceCropObj); + } + + if (allLayers) { + minLayer = INT32_MIN; + maxLayer = INT32_MAX; + } + + sp<GraphicBuffer> buffer; + ScreenshotClient::capture(displayToken, sourceCrop, width, height, minLayer, maxLayer, + useIdentityTransform, 0, &buffer); + + Surface::attachAndQueueBuffer(consumer.get(), buffer); } -static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken, - jobject surfaceObj, int rotation) { +static jobject nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken, + jobject sourceCropObj, jfloat frameScale) { sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken); if (layerHandle == NULL) { - return; + return NULL; } - sp<Surface> consumer = android_view_Surface_getSurface(env, surfaceObj); - if (consumer == NULL) { - return; + Rect sourceCrop; + if (sourceCropObj != NULL) { + sourceCrop = rectFromObj(env, sourceCropObj); } - ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), rotation); + sp<GraphicBuffer> buffer; + status_t res = ScreenshotClient::captureLayers(layerHandle, sourceCrop, frameScale, &buffer); + if (res != NO_ERROR) { + return NULL; + } + + return env->CallStaticObjectMethod(gGraphicBufferClassInfo.clazz, + gGraphicBufferClassInfo.builder, + buffer->getWidth(), + buffer->getHeight(), + buffer->getPixelFormat(), + (jint)buffer->getUsage(), + (jlong)buffer.get()); } static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) { @@ -975,7 +974,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = { {"nativeScreenshotToBuffer", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;", (void*)nativeScreenshotToBuffer }, - {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;I)V", + {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;", (void*)nativeCaptureLayers }, }; diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto index 0a3344fe13f1..cff187992fdb 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -395,6 +395,12 @@ message SystemProto { }; repeated WakeupReason wakeup_reason = 22; + message WifiMulticastWakelockTotal { + optional int64 duration_ms = 1; + optional int32 count = 2; + } + optional WifiMulticastWakelockTotal wifi_multicast_wakelock_total = 23; + message WifiSignalStrength { enum Name { NONE = 0; @@ -406,7 +412,7 @@ message SystemProto { optional Name name = 1; optional TimerProto total = 2; }; - repeated WifiSignalStrength wifi_signal_strength = 23; + repeated WifiSignalStrength wifi_signal_strength = 24; message WifiState { enum Name { @@ -422,7 +428,7 @@ message SystemProto { optional Name name = 1; optional TimerProto total = 2; }; - repeated WifiState wifi_state = 24; + repeated WifiState wifi_state = 25; message WifiSupplicantState { enum Name { @@ -443,7 +449,7 @@ message SystemProto { optional Name name = 1; optional TimerProto total = 2; }; - repeated WifiSupplicantState wifi_supplicant_state = 25; + repeated WifiSupplicantState wifi_supplicant_state = 26; } message TimerProto { @@ -775,4 +781,12 @@ message UidProto { optional TimerProto background_scan = 4; }; optional Wifi wifi = 27; + + // WiFi Multicast Wakelock + // This timer tracks the duration and count for the app to request the + // wakelock for wifi multicast traffic. + // This wakelock disables the filtering of multicast packets to reach the host + // processor, and results in a power penalty. + // It is useful to monitor the applications resulting in that + optional TimerProto wifi_multicast_wakelock = 28; } diff --git a/core/proto/android/providers/settings.proto b/core/proto/android/providers/settings.proto index 5d5aea2a12cd..322914061509 100644 --- a/core/proto/android/providers/settings.proto +++ b/core/proto/android/providers/settings.proto @@ -281,6 +281,7 @@ message GlobalSettingsProto { optional SettingProto bluetooth_pbap_client_priority_prefix = 209; optional SettingProto bluetooth_sap_priority_prefix = 210; optional SettingProto bluetooth_pan_priority_prefix = 211; + optional SettingProto bluetooth_hearing_aid_priority_prefix = 345; optional SettingProto activity_manager_constants = 317; optional SettingProto device_idle_constants = 212; optional SettingProto device_idle_constants_watch = 213; @@ -387,7 +388,7 @@ message GlobalSettingsProto { optional SettingProto enable_deletion_helper_no_threshold_toggle = 340; optional SettingProto notification_snooze_options = 341; - // Next tag = 345; + // Next tag = 346; } message SecureSettingsProto { diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 0eefec91e390..9a0eafdb7c77 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3021,6 +3021,12 @@ <!-- Whether this View should use a default focus highlight when it gets focused but doesn't have {@link android.R.attr#state_focused} defined in its background. --> <attr name="defaultFocusHighlightEnabled" format="boolean" /> + + <!-- Whether this view should be treated as a focusable unit by screen reader accessibility + tools. See {@link android.view.View#setScreenReaderFocusable(boolean)}. The default + value, {@code false}, leaves the screen reader to consider other signals, such as + focusability or the presence of text, to decide what it focus.--> + <attr name="screenReaderFocusable" format="boolean" /> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c10461643406..65a7ec643078 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1955,7 +1955,7 @@ <string name="config_dozeLongPressSensorType" translatable="false"></string> <!-- Control whether the always on display mode is available. This should only be enabled on - devices where the display has be tuned to be power efficient in DOZE and/or DOZE_SUSPEND + devices where the display has been tuned to be power efficient in DOZE and/or DOZE_SUSPEND states. --> <bool name="config_dozeAlwaysOnDisplayAvailable">false</bool> @@ -3174,6 +3174,10 @@ <!-- Corner radius of system dialogs --> <dimen name="config_dialogCornerRadius">2dp</dimen> + <!-- Controls whether system buttons use all caps for text --> + <bool name="config_buttonTextAllCaps">true</bool> + <!-- Name of the font family used for system buttons --> + <string name="config_fontFamilyButton">@string/font_family_button_material</string> <string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string> </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 946216c06a01..258ea387a696 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -266,6 +266,8 @@ <dimen name="alert_dialog_round_padding">27dip</dimen> <!-- Dialog title height --> <dimen name="alert_dialog_title_height">64dip</dimen> + <!-- Dialog button bar width --> + <dimen name="alert_dialog_button_bar_width">64dp</dimen> <!-- Dialog button bar height --> <dimen name="alert_dialog_button_bar_height">48dip</dimen> <!-- Leanback dialog vertical margin --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index d3533fe6e79f..e370786ee1c1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2852,9 +2852,12 @@ <public name="compileSdkVersion" /> <!-- @hide For use by platform and tools only. Developers should not specify this value. --> <public name="compileSdkVersionCodename" /> + <public name="screenReaderFocusable" /> </public-group> <public-group type="style" first-id="0x010302e0"> + <public name="Widget.DeviceDefault.Button.Colored" /> + <public name="Widget.DeviceDefault.Button.Borderless.Colored" /> </public-group> <public-group type="id" first-id="0x01020044"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index e4310e1070c8..2f1d679744c4 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2853,7 +2853,7 @@ <string name="unsupported_display_size_show">Always show</string> <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. --> - <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string> + <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string> <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. --> <string name="unsupported_compile_sdk_show">Always show</string> <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. --> diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml index 93a5264a0e26..189b3b7b0481 100644 --- a/core/res/res/values/styles_device_defaults.xml +++ b/core/res/res/values/styles_device_defaults.xml @@ -37,6 +37,10 @@ easier. <style name="Widget.DeviceDefault.Button.Small" parent="Widget.Material.Button.Small"/> <style name="Widget.DeviceDefault.Button.Inset" parent="Widget.Material.Button.Inset"/> <style name="Widget.DeviceDefault.Button.Toggle" parent="Widget.Material.Button.Toggle"/> + <style name="Widget.DeviceDefault.Button.Colored" parent="Widget.Material.Button.Colored"> + <item name="textAppearance">?attr/textAppearanceButton</item> + <item name="textColor">@color/btn_colored_text_material</item> + </style> <style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView"/> <style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/> <style name="Widget.DeviceDefault.AutoCompleteTextView" parent="Widget.Material.AutoCompleteTextView"/> @@ -77,6 +81,15 @@ easier. <style name="Widget.DeviceDefault.ActionButton.CloseMode" parent="Widget.Material.ActionButton.CloseMode"/> <style name="Widget.DeviceDefault.ActionBar" parent="Widget.Material.ActionBar"/> <style name="Widget.DeviceDefault.Button.Borderless" parent="Widget.Material.Button.Borderless"/> + <!-- Colored borderless ink button --> + <style name="Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored</item> + </style> + <!-- Alert dialog button bar button --> + <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog" parent="Widget.DeviceDefault.Button.Borderless.Colored"> + <item name="minWidth">@dimen/alert_dialog_button_bar_width</item> + <item name="minHeight">@dimen/alert_dialog_button_bar_height</item> + </style> <style name="Widget.DeviceDefault.Tab" parent="Widget.Material.Tab"/> <style name="Widget.DeviceDefault.CalendarView" parent="Widget.Material.CalendarView"/> <style name="Widget.DeviceDefault.DatePicker" parent="Widget.Material.DatePicker"/> @@ -211,7 +224,10 @@ easier. <style name="TextAppearance.DeviceDefault.SearchResult.Title" parent="TextAppearance.Material.SearchResult.Title"/> <style name="TextAppearance.DeviceDefault.SearchResult.Subtitle" parent="TextAppearance.Material.SearchResult.Subtitle"/> <style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"/> - <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button"/> + <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button"> + <item name="fontFamily">@string/config_fontFamilyButton</item> + <item name="textAllCaps">@bool/config_buttonTextAllCaps</item> + </style> <style name="TextAppearance.DeviceDefault.Widget.IconMenu.Item" parent="TextAppearance.Material.Widget.IconMenu.Item"/> <style name="TextAppearance.DeviceDefault.Widget.TabWidget" parent="TextAppearance.Material.Widget.TabWidget"/> <style name="TextAppearance.DeviceDefault.Widget.TextView" parent="TextAppearance.Material.Widget.TextView"/> @@ -220,6 +236,9 @@ easier. <style name="TextAppearance.DeviceDefault.Widget.DropDownItem" parent="TextAppearance.Material.Widget.DropDownItem"/> <style name="TextAppearance.DeviceDefault.Widget.TextView.SpinnerItem" parent="TextAppearance.Material.Widget.TextView.SpinnerItem"/> <style name="TextAppearance.DeviceDefault.Widget.EditText" parent="TextAppearance.Material.Widget.EditText"/> + <style name="TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored" parent="TextAppearance.DeviceDefault.Widget.Button"> + <item name="textColor">@color/btn_colored_borderless_text_material</item> + </style> <style name="TextAppearance.DeviceDefault.Widget.PopupMenu" parent="TextAppearance.Material.Widget.PopupMenu"/> <style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Large" parent="TextAppearance.Material.Widget.PopupMenu.Large"/> <style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Small" parent="TextAppearance.Material.Widget.PopupMenu.Small"/> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 2cd4dcb84a5f..67a499dd9ca6 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -561,7 +561,7 @@ please see styles_device_defaults.xml. <!-- Alert dialog button bar button --> <style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored"> - <item name="minWidth">64dp</item> + <item name="minWidth">@dimen/alert_dialog_button_bar_width</item> <item name="minHeight">@dimen/alert_dialog_button_bar_height</item> </style> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index 68d552367423..3fa0d3b542fc 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -73,8 +73,8 @@ easier. <item name="buttonStyleSmall">@style/Widget.DeviceDefault.Button.Small</item> <item name="buttonStyleInset">@style/Widget.DeviceDefault.Button.Inset</item> - <item name="buttonStyleToggle">@style/Widget.DeviceDefault.Button.Toggle</item> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> <item name="switchStyle">@style/Widget.DeviceDefault.CompoundButton.Switch</item> <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless</item> @@ -219,6 +219,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme @@ -232,6 +238,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and @@ -247,6 +259,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent @@ -261,6 +279,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be @@ -273,8 +297,13 @@ easier. <item name="buttonBarStyle">@style/DeviceDefault.ButtonBar.AlertDialog</item> <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless.Small</item> + <!-- Text styles --> <item name="textAppearance">@style/TextAppearance.DeviceDefault</item> <item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> <!-- Color palette --> <item name="colorPrimary">@color/primary_device_default_dark</item> @@ -297,6 +326,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar --> @@ -309,6 +344,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width @@ -322,6 +363,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. --> @@ -351,6 +398,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for a window without an action bar that will be displayed either @@ -365,6 +418,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for a presentation window on a secondary display. --> @@ -377,6 +436,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for panel windows. This removes all extraneous window @@ -391,6 +456,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear @@ -404,6 +475,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear @@ -417,6 +494,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault style for input methods, which is used by the @@ -430,6 +513,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault style for input methods, which is used by the @@ -443,6 +532,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert"> @@ -456,6 +551,12 @@ easier. <item name="colorPrimary">@color/primary_device_default_dark</item> <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item> <item name="colorAccent">@color/accent_device_default_dark</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar"> @@ -467,6 +568,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame"> @@ -478,6 +585,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style --> @@ -644,6 +757,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar --> @@ -656,6 +775,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar. @@ -669,6 +794,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar @@ -684,6 +815,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent @@ -698,6 +835,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be @@ -711,11 +854,14 @@ easier. <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + <!-- Button styles --> <item name="buttonBarStyle">@style/DeviceDefault.Light.ButtonBar.AlertDialog</item> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> <item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Light.Button.Borderless.Small</item> <item name="textAppearance">@style/TextAppearance.DeviceDefault</item> <item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> <!-- Color palette --> <item name="colorPrimary">@color/primary_device_default_light</item> @@ -734,6 +880,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar --> @@ -746,6 +898,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum @@ -759,6 +917,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. --> @@ -798,6 +962,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault light theme for a window without an action bar that will be displayed either @@ -812,6 +982,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault light theme for a presentation window on a secondary display. --> @@ -824,6 +1000,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault light theme for panel windows. This removes all extraneous window @@ -838,6 +1020,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert"> @@ -851,6 +1039,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar"> @@ -862,6 +1056,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice"> @@ -873,6 +1073,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- DeviceDefault theme for a window that should look like the Settings app. --> @@ -896,6 +1102,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <!-- @hide DeviceDefault theme for a window that should use Settings theme colors @@ -908,6 +1120,12 @@ easier. <item name="colorAccent">@color/accent_device_default_light</item> <item name="colorControlNormal">?attr/textColorPrimary</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Light.Dialog"> @@ -930,6 +1148,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.Material.Settings.Dialog"> @@ -942,6 +1166,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Settings.DialogWhenLarge" parent="Theme.Material.Settings.DialogWhenLarge"> @@ -954,6 +1184,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert"> @@ -966,6 +1202,12 @@ easier. <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> + + <!-- Text styles --> + <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> + + <!-- Button styles --> + <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" /> diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index 51bfc209bf55..3f2a46a0e9ca 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -68,6 +68,7 @@ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SMS"/> <uses-permission android:name="android.permission.TEST_GRANTED" /> + <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" /> <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" /> diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml index 23d70b8dedd4..970a0f00bf5e 100644 --- a/core/tests/coretests/AndroidTest.xml +++ b/core/tests/coretests/AndroidTest.xml @@ -16,6 +16,7 @@ <configuration description="Runs Frameworks Core Tests."> <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> <option name="test-file-name" value="FrameworksCoreTests.apk" /> + <option name="test-file-name" value="BstatsTestApp.apk" /> </target_preparer> <option name="test-suite-tag" value="apct" /> diff --git a/core/tests/coretests/BstatsTestApp/Android.mk b/core/tests/coretests/BstatsTestApp/Android.mk new file mode 100644 index 000000000000..628025751d77 --- /dev/null +++ b/core/tests/coretests/BstatsTestApp/Android.mk @@ -0,0 +1,32 @@ +# Copyright (C) 2017 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 := tests + +LOCAL_COMPATIBILITY_SUITE := device-tests + +LOCAL_STATIC_JAVA_LIBRARIES := coretests-aidl + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := BstatsTestApp +LOCAL_CERTIFICATE := platform +LOCAL_DEX_PREOPT := false +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..0cb5498a9c21 --- /dev/null +++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 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.coretests.apps.bstatstestapp"> + + <application> + <activity android:name=".TestActivity" + android:exported="true" /> + <service android:name=".IsolatedTestService" + android:exported="true" + android:isolatedProcess="true" /> + </application> + +</manifest>
\ No newline at end of file diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java new file mode 100644 index 000000000000..1f5f39730096 --- /dev/null +++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 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.coretests.apps.bstatstestapp; + +import com.android.frameworks.coretests.aidl.ICmdReceiver; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; +import android.os.Process; +import android.os.SystemClock; +import android.util.Log; + +public class IsolatedTestService extends Service { + private static final String TAG = IsolatedTestService.class.getName(); + + @Override + public void onCreate() { + Log.d(TAG, "onCreate called. myUid=" + Process.myUid()); + } + + @Override + public IBinder onBind(Intent intent) { + return mReceiver.asBinder(); + } + + private ICmdReceiver mReceiver = new ICmdReceiver.Stub() { + @Override + public void doSomeWork(int durationMs) { + final long endTime = SystemClock.uptimeMillis() + durationMs; + double x; + double y; + double z; + while (SystemClock.uptimeMillis() <= endTime) { + x = 0.02; + x *= 1000; + y = x % 5; + z = Math.sqrt(y / 100); + } + }; + + @Override + public void finishHost() { + stopSelf(); + } + }; +} diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java new file mode 100644 index 000000000000..87b14d934913 --- /dev/null +++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2017 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.coretests.apps.bstatstestapp; + +import com.android.frameworks.coretests.aidl.ICmdCallback; +import com.android.frameworks.coretests.aidl.ICmdReceiver; + +import android.app.Activity; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +public class TestActivity extends Activity { + private static final String TAG = TestActivity.class.getName(); + + private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver"; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + Log.d(TAG, "onCreate called"); + notifyActivityLaunched(); + } + + private void notifyActivityLaunched() { + if (getIntent() == null) { + return; + } + + final Bundle extras = getIntent().getExtras(); + if (extras == null) { + return; + } + final ICmdCallback callback = ICmdCallback.Stub.asInterface( + extras.getBinder(EXTRA_KEY_CMD_RECEIVER)); + try { + callback.onActivityLaunched(mReceiver.asBinder()); + } catch (RemoteException e) { + Log.e(TAG, "Error occured while notifying the test: " + e); + } + } + + @Override + public void finish() { + super.finish(); + Log.d(TAG, "finish called"); + } + + private ICmdReceiver mReceiver = new ICmdReceiver.Stub() { + @Override + public void doSomeWork(int durationMs) { + final long endTime = SystemClock.uptimeMillis() + durationMs; + double x; + double y; + double z; + while (SystemClock.uptimeMillis() <= endTime) { + x = 0.02; + x *= 1000; + y = x % 5; + z = Math.sqrt(y / 100); + } + }; + + @Override + public void finishHost() { + finish(); + } + }; +} diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl new file mode 100644 index 000000000000..53a181acd5b6 --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; + +interface ICmdCallback { + void onActivityLaunched(IBinder receiver); +}
\ No newline at end of file diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl new file mode 100644 index 000000000000..c406570ecfae --- /dev/null +++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 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.frameworks.coretests.aidl; + +interface ICmdReceiver { + void doSomeWork(int durationMs); + void finishHost(); +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java new file mode 100644 index 000000000000..e0fcf0833232 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.app.ClientTransactionHandler; +import android.os.IBinder; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +// TODO(lifecycler): Add to presubmit after checking for flakiness. +public class ClientTransactionTests { + + @Test + public void testPrepare() { + ClientTransactionItem callback1 = mock(ClientTransactionItem.class); + ClientTransactionItem callback2 = mock(ClientTransactionItem.class); + ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); + ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class); + IBinder token = mock(IBinder.class); + + ClientTransaction transaction = new ClientTransaction(null /* client */, + token /* activityToken */); + transaction.addCallback(callback1); + transaction.addCallback(callback2); + transaction.setLifecycleStateRequest(stateRequest); + + transaction.prepare(clientTransactionHandler); + + verify(callback1, times(1)).prepare(clientTransactionHandler, token); + verify(callback2, times(1)).prepare(clientTransactionHandler, token); + verify(stateRequest, times(1)).prepare(clientTransactionHandler, token); + } + + @Test + public void testExecute() { + ClientTransactionItem callback1 = mock(ClientTransactionItem.class); + ClientTransactionItem callback2 = mock(ClientTransactionItem.class); + ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class); + IBinder token = mock(IBinder.class); + + ClientTransaction transaction = new ClientTransaction(null /* client */, + token /* activityToken */); + transaction.addCallback(callback1); + transaction.addCallback(callback2); + transaction.setLifecycleStateRequest(stateRequest); + + ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class); + transaction.prepare(clientTransactionHandler); + transaction.execute(clientTransactionHandler); + + verify(callback1, times(1)).execute(clientTransactionHandler, token); + verify(callback2, times(1)).execute(clientTransactionHandler, token); + verify(stateRequest, times(1)).execute(clientTransactionHandler, token); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java new file mode 100644 index 000000000000..9db7550a6099 --- /dev/null +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -0,0 +1,641 @@ +/* + * Copyright 2017 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.app.servertransaction; + +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + +import android.app.IApplicationThread; +import android.app.IInstrumentationWatcher; +import android.app.IUiAutomationConnection; +import android.app.ProfilerInfo; +import android.app.ResultInfo; +import android.content.ComponentName; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ParceledListSlice; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Debug; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.PersistableBundle; +import android.os.RemoteException; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.internal.app.IVoiceInteractor; +import com.android.internal.content.ReferrerIntent; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** Test parcelling and unparcelling of transactions and transaction items. */ +@RunWith(AndroidJUnit4.class) +@SmallTest +// TODO(lifecycler): Add to presubmit after checking for flakiness. +public class TransactionParcelTests { + + private Parcel mParcel; + + @Before + public void setUp() throws Exception { + mParcel = Parcel.obtain(); + } + + @Test + public void testConfigurationChange() { + // Write to parcel + ConfigurationChangeItem item = new ConfigurationChangeItem(config()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testActivityConfigChange() { + // Write to parcel + ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(config()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + ActivityConfigurationChangeItem result = + ActivityConfigurationChangeItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testMoveToDisplay() { + // Write to parcel + MoveToDisplayItem item = new MoveToDisplayItem(4 /* targetDisplayId */, config()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testNewIntent() { + // Write to parcel + NewIntentItem item = new NewIntentItem(referrerIntentList(), true /* pause */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testActivityResult() { + // Write to parcel + ActivityResultItem item = new ActivityResultItem(resultInfoList()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testPipModeChange() { + // Write to parcel + PipModeChangeItem item = new PipModeChangeItem(true /* isInPipMode */, config()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + PipModeChangeItem result = PipModeChangeItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testMultiWindowModeChange() { + // Write to parcel + MultiWindowModeChangeItem item = new MultiWindowModeChangeItem( + true /* isInMultiWindowMode */, config()); + writeAndPrepareForReading(item); + + // Read from parcel and assert + MultiWindowModeChangeItem result = + MultiWindowModeChangeItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testWindowVisibilityChange() { + // Write to parcel + WindowVisibilityItem item = new WindowVisibilityItem(true /* showWindow */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + + // Check different value + item = new WindowVisibilityItem(false); + + mParcel = Parcel.obtain(); + writeAndPrepareForReading(item); + + // Read from parcel and assert + result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testDestroy() { + DestroyActivityItem item = new DestroyActivityItem(true /* finished */, + 135 /* configChanges */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testLaunch() { + // Write to parcel + Intent intent = new Intent("action"); + int ident = 57; + ActivityInfo activityInfo = new ActivityInfo(); + activityInfo.flags = 42; + activityInfo.maxAspectRatio = 2.4f; + activityInfo.launchToken = "token"; + activityInfo.applicationInfo = new ApplicationInfo(); + activityInfo.packageName = "packageName"; + activityInfo.name = "name"; + Configuration overrideConfig = new Configuration(); + overrideConfig.assetsSeq = 5; + CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + String referrer = "referrer"; + int procState = 4; + Bundle bundle = new Bundle(); + bundle.putString("key", "value"); + PersistableBundle persistableBundle = new PersistableBundle(); + persistableBundle.putInt("k", 4); + + LaunchActivityItem item = new LaunchActivityItem(intent, ident, activityInfo, + config(), overrideConfig, compat, referrer, null /* voiceInteractor */, + procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(), + true /* notResumed */, true /* isForward */, null /* profilerInfo */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + LaunchActivityItem result = LaunchActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testPause() { + // Write to parcel + PauseActivityItem item = new PauseActivityItem(true /* finished */, true /* userLeaving */, + 135 /* configChanges */, true /* dontReport */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testResume() { + // Write to parcel + ResumeActivityItem item = new ResumeActivityItem(27 /* procState */, true /* isForward */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testStop() { + // Write to parcel + StopActivityItem item = new StopActivityItem(true /* showWindow */, 14 /* configChanges */); + writeAndPrepareForReading(item); + + // Read from parcel and assert + StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel); + + assertEquals(item.hashCode(), result.hashCode()); + assertTrue(item.equals(result)); + } + + @Test + public void testClientTransaction() { + // Write to parcel + WindowVisibilityItem callback1 = new WindowVisibilityItem(true); + ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config()); + + StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */, + 78 /* configChanges */); + + IApplicationThread appThread = new StubAppThread(); + Binder activityToken = new Binder(); + + ClientTransaction transaction = new ClientTransaction(appThread, activityToken); + transaction.addCallback(callback1); + transaction.addCallback(callback2); + transaction.setLifecycleStateRequest(lifecycleRequest); + + writeAndPrepareForReading(transaction); + + // Read from parcel and assert + ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel); + + assertEquals(transaction.hashCode(), result.hashCode()); + assertTrue(transaction.equals(result)); + } + + @Test + public void testClientTransactionCallbacksOnly() { + // Write to parcel + WindowVisibilityItem callback1 = new WindowVisibilityItem(true); + ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config()); + + IApplicationThread appThread = new StubAppThread(); + Binder activityToken = new Binder(); + + ClientTransaction transaction = new ClientTransaction(appThread, activityToken); + transaction.addCallback(callback1); + transaction.addCallback(callback2); + + writeAndPrepareForReading(transaction); + + // Read from parcel and assert + ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel); + + assertEquals(transaction.hashCode(), result.hashCode()); + assertTrue(transaction.equals(result)); + } + + @Test + public void testClientTransactionLifecycleOnly() { + // Write to parcel + StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */, + 78 /* configChanges */); + + IApplicationThread appThread = new StubAppThread(); + Binder activityToken = new Binder(); + + ClientTransaction transaction = new ClientTransaction(appThread, activityToken); + transaction.setLifecycleStateRequest(lifecycleRequest); + + writeAndPrepareForReading(transaction); + + // Read from parcel and assert + ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel); + + assertEquals(transaction.hashCode(), result.hashCode()); + assertTrue(transaction.equals(result)); + } + + private static List<ResultInfo> resultInfoList() { + String resultWho1 = "resultWho1"; + int requestCode1 = 7; + int resultCode1 = 4; + Intent data1 = new Intent("action1"); + ResultInfo resultInfo1 = new ResultInfo(resultWho1, requestCode1, resultCode1, data1); + + String resultWho2 = "resultWho2"; + int requestCode2 = 8; + int resultCode2 = 6; + Intent data2 = new Intent("action2"); + ResultInfo resultInfo2 = new ResultInfo(resultWho2, requestCode2, resultCode2, data2); + + List<ResultInfo> resultInfoList = new ArrayList<>(); + resultInfoList.add(resultInfo1); + resultInfoList.add(resultInfo2); + + return resultInfoList; + } + + private static List<ReferrerIntent> referrerIntentList() { + Intent intent1 = new Intent("action1"); + ReferrerIntent referrerIntent1 = new ReferrerIntent(intent1, "referrer1"); + + Intent intent2 = new Intent("action2"); + ReferrerIntent referrerIntent2 = new ReferrerIntent(intent2, "referrer2"); + + List<ReferrerIntent> referrerIntents = new ArrayList<>(); + referrerIntents.add(referrerIntent1); + referrerIntents.add(referrerIntent2); + + return referrerIntents; + } + + private static Configuration config() { + Configuration config = new Configuration(); + config.densityDpi = 10; + config.fontScale = 0.3f; + config.screenHeightDp = 15; + config.orientation = ORIENTATION_LANDSCAPE; + return config; + } + + /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */ + private void writeAndPrepareForReading(Parcelable parcelable) { + parcelable.writeToParcel(mParcel, 0 /* flags */); + mParcel.setDataPosition(0); + } + + /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */ + class StubAppThread extends android.app.IApplicationThread.Stub { + + @Override + public void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + } + + @Override + public void scheduleReceiver(Intent intent, ActivityInfo activityInfo, + CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b, + int i1, int i2) throws RemoteException { + } + + @Override + public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo, + CompatibilityInfo compatibilityInfo, int i) throws RemoteException { + } + + @Override + public void scheduleStopService(IBinder iBinder) throws RemoteException { + } + + @Override + public void bindApplication(String s, ApplicationInfo applicationInfo, + List<ProviderInfo> list, ComponentName componentName, ProfilerInfo profilerInfo, + Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher, + IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1, + boolean b2, boolean b3, Configuration configuration, + CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1) + throws RemoteException { + } + + @Override + public void scheduleExit() throws RemoteException { + } + + @Override + public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice) + throws RemoteException { + } + + @Override + public void updateTimeZone() throws RemoteException { + } + + @Override + public void processInBackground() throws RemoteException { + } + + @Override + public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i) + throws RemoteException { + } + + @Override + public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException { + } + + @Override + public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, + String[] strings) throws RemoteException { + } + + @Override + public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent, + int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2) + throws RemoteException { + } + + @Override + public void scheduleLowMemory() throws RemoteException { + } + + @Override + public void scheduleRelaunchActivity(IBinder iBinder, List<ResultInfo> list, + List<ReferrerIntent> list1, int i, boolean b, Configuration configuration, + Configuration configuration1, boolean b1) throws RemoteException { + } + + @Override + public void scheduleSleeping(IBinder iBinder, boolean b) throws RemoteException { + } + + @Override + public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i) + throws RemoteException { + } + + @Override + public void setSchedulingGroup(int i) throws RemoteException { + } + + @Override + public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo, + CompatibilityInfo compatibilityInfo, int i) throws RemoteException { + } + + @Override + public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo, + CompatibilityInfo compatibilityInfo) throws RemoteException { + } + + @Override + public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle) + throws RemoteException { + } + + @Override + public void scheduleSuicide() throws RemoteException { + } + + @Override + public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException { + } + + @Override + public void scheduleCrash(String s) throws RemoteException { + } + + @Override + public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, + String s, String[] strings) throws RemoteException { + } + + @Override + public void clearDnsCache() throws RemoteException { + } + + @Override + public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException { + } + + @Override + public void setCoreSettings(Bundle bundle) throws RemoteException { + } + + @Override + public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo) + throws RemoteException { + } + + @Override + public void scheduleTrimMemory(int i) throws RemoteException { + } + + @Override + public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor, + Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3, + boolean b4, String[] strings) throws RemoteException { + } + + @Override + public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) + throws RemoteException { + } + + @Override + public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder, + String[] strings) throws RemoteException { + } + + @Override + public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings) + throws RemoteException { + } + + @Override + public void unstableProviderDied(IBinder iBinder) throws RemoteException { + } + + @Override + public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1, + int i2) throws RemoteException { + } + + @Override + public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b) + throws RemoteException { + } + + @Override + public void setProcessState(int i) throws RemoteException { + } + + @Override + public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException { + } + + @Override + public void updateTimePrefs(int i) throws RemoteException { + } + + @Override + public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException { + } + + @Override + public void notifyCleartextNetwork(byte[] bytes) throws RemoteException { + } + + @Override + public void startBinderTracking() throws RemoteException { + } + + @Override + public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor) + throws RemoteException { + } + + @Override + public void scheduleLocalVoiceInteractionStarted(IBinder iBinder, + IVoiceInteractor iVoiceInteractor) throws RemoteException { + } + + @Override + public void handleTrustStorageUpdate() throws RemoteException { + } + + @Override + public void attachAgent(String s) throws RemoteException { + } + + @Override + public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo) + throws RemoteException { + } + + @Override + public void setNetworkBlockSeq(long l) throws RemoteException { + } + + @Override + public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path, + ParcelFileDescriptor fd) { + } + + @Override + public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) { + } + } +} diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 492ef7288fcc..454d2096eca8 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -127,6 +127,7 @@ public class SettingsBackupTest { Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_PBAP_CLIENT_PRIORITY_PREFIX, Settings.Global.BLUETOOTH_SAP_PRIORITY_PREFIX, + Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX, Settings.Global.BOOT_COUNT, Settings.Global.CAPTIVE_PORTAL_FALLBACK_URL, Settings.Global.CAPTIVE_PORTAL_HTTPS_URL, diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java new file mode 100644 index 000000000000..6dd787d2e038 --- /dev/null +++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2017 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.view; + +import static android.view.DisplayCutout.NO_CUTOUT; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import android.graphics.Point; +import android.graphics.Rect; +import android.os.Parcel; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayCutout.ParcelableWrapper; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class DisplayCutoutTest { + + /** This is not a consistent cutout. Useful for verifying insets in one go though. */ + final DisplayCutout mCutoutNumbers = new DisplayCutout( + new Rect(1, 2, 3, 4), + new Rect(5, 6, 7, 8), + Arrays.asList( + new Point(9, 10), + new Point(11, 12), + new Point(13, 14), + new Point(15, 16))); + + final DisplayCutout mCutoutTop = createCutoutTop(); + + @Test + public void hasCutout() throws Exception { + assertFalse(NO_CUTOUT.hasCutout()); + assertTrue(mCutoutTop.hasCutout()); + } + + @Test + public void getSafeInsets() throws Exception { + assertEquals(1, mCutoutNumbers.getSafeInsetLeft()); + assertEquals(2, mCutoutNumbers.getSafeInsetTop()); + assertEquals(3, mCutoutNumbers.getSafeInsetRight()); + assertEquals(4, mCutoutNumbers.getSafeInsetBottom()); + + Rect safeInsets = new Rect(); + mCutoutNumbers.getSafeInsets(safeInsets); + + assertEquals(new Rect(1, 2, 3, 4), safeInsets); + } + + @Test + public void getBoundingRect() throws Exception { + Rect boundingRect = new Rect(); + mCutoutTop.getBoundingRect(boundingRect); + + assertEquals(new Rect(50, 0, 75, 100), boundingRect); + } + + @Test + public void getBoundingPolygon() throws Exception { + ArrayList<Point> boundingPolygon = new ArrayList<>(); + mCutoutTop.getBoundingPolygon(boundingPolygon); + + assertEquals(Arrays.asList( + new Point(75, 0), + new Point(50, 0), + new Point(75, 100), + new Point(50, 100)), boundingPolygon); + } + + @Test + public void testHashCode() throws Exception { + assertEquals(mCutoutTop.hashCode(), createCutoutTop().hashCode()); + assertNotEquals(mCutoutTop.hashCode(), mCutoutNumbers.hashCode()); + } + + @Test + public void testEquals() throws Exception { + assertEquals(mCutoutTop, createCutoutTop()); + assertNotEquals(mCutoutTop, mCutoutNumbers); + } + + @Test + public void testToString() throws Exception { + assertFalse(mCutoutTop.toString().isEmpty()); + assertFalse(mCutoutNumbers.toString().isEmpty()); + } + + @Test + public void inset_immutable() throws Exception { + DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); + + assertEquals("original instance must not be mutated", createCutoutTop(), mCutoutTop); + } + + @Test + public void inset_insets_withLeftCutout() throws Exception { + DisplayCutout cutout = createCutoutWithInsets(100, 0, 0, 0).inset(1, 2, 3, 4); + + assertEquals(cutout.getSafeInsetLeft(), 99); + assertEquals(cutout.getSafeInsetTop(), 0); + assertEquals(cutout.getSafeInsetRight(), 0); + assertEquals(cutout.getSafeInsetBottom(), 0); + } + + @Test + public void inset_insets_withTopCutout() throws Exception { + DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); + + assertEquals(cutout.getSafeInsetLeft(), 0); + assertEquals(cutout.getSafeInsetTop(), 98); + assertEquals(cutout.getSafeInsetRight(), 0); + assertEquals(cutout.getSafeInsetBottom(), 0); + } + + @Test + public void inset_insets_withRightCutout() throws Exception { + DisplayCutout cutout = createCutoutWithInsets(0, 0, 100, 0).inset(1, 2, 3, 4); + + assertEquals(cutout.getSafeInsetLeft(), 0); + assertEquals(cutout.getSafeInsetTop(), 0); + assertEquals(cutout.getSafeInsetRight(), 97); + assertEquals(cutout.getSafeInsetBottom(), 0); + } + + @Test + public void inset_insets_withBottomCutout() throws Exception { + DisplayCutout cutout = createCutoutWithInsets(0, 0, 0, 100).inset(1, 2, 3, 4); + + assertEquals(cutout.getSafeInsetLeft(), 0); + assertEquals(cutout.getSafeInsetTop(), 0); + assertEquals(cutout.getSafeInsetRight(), 0); + assertEquals(cutout.getSafeInsetBottom(), 96); + } + + @Test + public void inset_insets_consumeInset() throws Exception { + DisplayCutout cutout = mCutoutTop.inset(0, 1000, 0, 0); + + assertEquals(cutout.getSafeInsetLeft(), 0); + assertEquals(cutout.getSafeInsetTop(), 0); + assertEquals(cutout.getSafeInsetRight(), 0); + assertEquals(cutout.getSafeInsetBottom(), 0); + + assertFalse(cutout.hasCutout()); + } + + @Test + public void inset_bounds() throws Exception { + DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4); + + Rect boundingRect = new Rect(); + cutout.getBoundingRect(boundingRect); + + assertEquals(new Rect(49, -2, 74, 98), boundingRect); + + ArrayList<Point> boundingPolygon = new ArrayList<>(); + cutout.getBoundingPolygon(boundingPolygon); + + assertEquals(Arrays.asList( + new Point(74, -2), + new Point(49, -2), + new Point(74, 98), + new Point(49, 98)), boundingPolygon); + } + + @Test + public void calculateRelativeTo_top() throws Exception { + DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400)); + + Rect insets = new Rect(); + cutout.getSafeInsets(insets); + + assertEquals(new Rect(0, 100, 0, 0), insets); + } + + @Test + public void calculateRelativeTo_left() throws Exception { + DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200)); + + Rect insets = new Rect(); + cutout.getSafeInsets(insets); + + assertEquals(new Rect(75, 0, 0, 0), insets); + } + + @Test + public void calculateRelativeTo_bottom() throws Exception { + DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100)); + + Rect insets = new Rect(); + cutout.getSafeInsets(insets); + + assertEquals(new Rect(0, 0, 0, 100), insets); + } + + @Test + public void calculateRelativeTo_right() throws Exception { + DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100)); + + Rect insets = new Rect(); + cutout.getSafeInsets(insets); + + assertEquals(new Rect(0, 0, 50, 0), insets); + } + + @Test + public void calculateRelativeTo_bounds() throws Exception { + DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200)); + + + Rect boundingRect = new Rect(); + cutout.getBoundingRect(boundingRect); + assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect); + + ArrayList<Point> boundingPolygon = new ArrayList<>(); + cutout.getBoundingPolygon(boundingPolygon); + + assertEquals(Arrays.asList( + new Point(1075, 2000), + new Point(1050, 2000), + new Point(1075, 2100), + new Point(1050, 2100)), boundingPolygon); + } + + @Test + public void fromBoundingPolygon() throws Exception { + assertEquals( + new DisplayCutout( + new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets. + new Rect(50, 0, 75, 100), + Arrays.asList( + new Point(75, 0), + new Point(50, 0), + new Point(75, 100), + new Point(50, 100))), + DisplayCutout.fromBoundingPolygon( + Arrays.asList( + new Point(75, 0), + new Point(50, 0), + new Point(75, 100), + new Point(50, 100)))); + } + + @Test + public void parcel_unparcel_regular() { + Parcel p = Parcel.obtain(); + + new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0); + int posAfterWrite = p.dataPosition(); + + p.setDataPosition(0); + + assertEquals(mCutoutTop, ParcelableWrapper.CREATOR.createFromParcel(p).get()); + assertEquals(posAfterWrite, p.dataPosition()); + } + + @Test + public void parcel_unparcel_nocutout() { + Parcel p = Parcel.obtain(); + + new ParcelableWrapper(NO_CUTOUT).writeToParcel(p, 0); + int posAfterWrite = p.dataPosition(); + + p.setDataPosition(0); + + assertEquals(NO_CUTOUT, ParcelableWrapper.CREATOR.createFromParcel(p).get()); + assertEquals(posAfterWrite, p.dataPosition()); + } + + @Test + public void parcel_unparcel_inplace() { + Parcel p = Parcel.obtain(); + + new ParcelableWrapper(mCutoutTop).writeToParcel(p, 0); + int posAfterWrite = p.dataPosition(); + + p.setDataPosition(0); + + ParcelableWrapper wrapper = new ParcelableWrapper(); + wrapper.readFromParcel(p); + + assertEquals(mCutoutTop, wrapper.get()); + assertEquals(posAfterWrite, p.dataPosition()); + } + + @Test + public void wrapper_hashcode() throws Exception { + assertEquals(new ParcelableWrapper(mCutoutTop).hashCode(), + new ParcelableWrapper(createCutoutTop()).hashCode()); + assertNotEquals(new ParcelableWrapper(mCutoutTop).hashCode(), + new ParcelableWrapper(mCutoutNumbers).hashCode()); + } + + @Test + public void wrapper_equals() throws Exception { + assertEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(createCutoutTop())); + assertNotEquals(new ParcelableWrapper(mCutoutTop), new ParcelableWrapper(mCutoutNumbers)); + } + + private static DisplayCutout createCutoutTop() { + return new DisplayCutout( + new Rect(0, 100, 0, 0), + new Rect(50, 0, 75, 100), + Arrays.asList( + new Point(75, 0), + new Point(50, 0), + new Point(75, 100), + new Point(50, 100))); + } + + private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) { + return new DisplayCutout( + new Rect(left, top, right, bottom), + new Rect(50, 0, 75, 100), + Arrays.asList( + new Point(75, 0), + new Point(50, 0), + new Point(75, 100), + new Point(50, 100))); + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index c8dc9ff51a1b..2f32d13693c7 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,26 +14,23 @@ * limitations under the License. */ -package com.android.server.accessibility; +package android.view.accessibility; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNull; + import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.support.test.runner.AndroidJUnit4; -import android.view.accessibility.AccessibilityCache; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityInteractionClient; -import android.view.accessibility.AccessibilityNodeInfo; -import android.view.accessibility.AccessibilityWindowInfo; import android.view.View; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -48,27 +45,27 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(AndroidJUnit4.class) public class AccessibilityCacheTest { - private static int WINDOW_ID_1 = 0xBEEF; - private static int WINDOW_ID_2 = 0xFACE; - private static int SINGLE_VIEW_ID = 0xCAFE; - private static int OTHER_VIEW_ID = 0xCAB2; - private static int PARENT_VIEW_ID = 0xFED4; - private static int CHILD_VIEW_ID = 0xFEED; - private static int OTHER_CHILD_VIEW_ID = 0xACE2; - private static int MOCK_CONNECTION_ID = 1; + private static final int WINDOW_ID_1 = 0xBEEF; + private static final int WINDOW_ID_2 = 0xFACE; + private static final int SINGLE_VIEW_ID = 0xCAFE; + private static final int OTHER_VIEW_ID = 0xCAB2; + private static final int PARENT_VIEW_ID = 0xFED4; + private static final int CHILD_VIEW_ID = 0xFEED; + private static final int OTHER_CHILD_VIEW_ID = 0xACE2; + private static final int MOCK_CONNECTION_ID = 1; AccessibilityCache mAccessibilityCache; AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher; - AtomicInteger numA11yNodeInfosInUse = new AtomicInteger(0); - AtomicInteger numA11yWinInfosInUse = new AtomicInteger(0); + AtomicInteger mNumA11yNodeInfosInUse = new AtomicInteger(0); + AtomicInteger mNumA11yWinInfosInUse = new AtomicInteger(0); @Before public void setUp() { mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class); when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true); mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher); - AccessibilityNodeInfo.setNumInstancesInUseCounter(numA11yNodeInfosInUse); - AccessibilityWindowInfo.setNumInstancesInUseCounter(numA11yWinInfosInUse); + AccessibilityNodeInfo.setNumInstancesInUseCounter(mNumA11yNodeInfosInUse); + AccessibilityWindowInfo.setNumInstancesInUseCounter(mNumA11yWinInfosInUse); } @After @@ -76,7 +73,8 @@ public class AccessibilityCacheTest { // Make sure we're recycling all of our window and node infos mAccessibilityCache.clear(); AccessibilityInteractionClient.getInstance().clearCache(); - assertEquals(0, numA11yWinInfosInUse.get()); + assertEquals(0, mNumA11yWinInfosInUse.get()); + assertEquals(0, mNumA11yNodeInfosInUse.get()); } @Test diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java new file mode 100644 index 000000000000..8a5fc2d11195 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2017 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.view.accessibility; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.os.Bundle; +import android.os.RemoteException; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.Arrays; +import java.util.List; + +/** + * Tests for AccessibilityInteractionClient + */ +@RunWith(AndroidJUnit4.class) +public class AccessibilityInteractionClientTest { + private static final int MOCK_CONNECTION_ID = 0xabcd; + + private MockConnection mMockConnection = new MockConnection(); + @Mock private AccessibilityCache mMockCache; + + @Before + public void setUp() { + initMocks(this); + AccessibilityInteractionClient.setCache(mMockCache); + AccessibilityInteractionClient.addConnection(MOCK_CONNECTION_ID, mMockConnection); + } + + /** + * When the AccessibilityCache refreshes the nodes it contains, it gets very confused if + * it is called to update itself during the refresh. It tries to update the node that it's + * in the process of refreshing, which leads to AccessibilityNodeInfos in inconsistent states. + */ + @Test + public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() { + final int windowId = 0x1234; + final long accessibilityNodeId = 0x4321L; + AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain(); + nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId); + mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection); + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId( + MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null); + assertEquals("Node got lost along the way", nodeFromConnection, node); + + verifyZeroInteractions(mMockCache); + } + + private static class MockConnection extends AccessibilityServiceConnectionImpl { + List<AccessibilityNodeInfo> mInfosToReturn; + + @Override + public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, + Bundle arguments) { + try { + callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + return true; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 7f979738ee52..79e6316ed614 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java @@ -1,10 +1,25 @@ -package com.android.server.accessibility; +/* + * Copyright 2017 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.view.accessibility; import static org.junit.Assert.fail; import android.support.test.runner.AndroidJUnit4; import android.util.ArraySet; -import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import com.android.internal.util.CollectionUtils; diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java new file mode 100644 index 000000000000..d3bbee71d564 --- /dev/null +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -0,0 +1,133 @@ +/* + * Copyright 2017 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.view.accessibility; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.IAccessibilityServiceConnection; +import android.content.pm.ParceledListSlice; +import android.graphics.Region; +import android.os.Bundle; + +import java.util.List; + +/** + * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement + * all of the methods + */ +public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceConnection.Stub { + public void setServiceInfo(AccessibilityServiceInfo info) {} + + public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, + long accessibilityNodeId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, + Bundle arguments) { + return false; + } + + public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, + long accessibilityNodeId, String text, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) { + return false; + } + + public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId, + long accessibilityNodeId, String viewId, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) { + return false; + } + + public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + long threadId) { + return false; + } + + public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + long threadId) { + return false; + } + + public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, + int action, Bundle arguments, int interactionId, + IAccessibilityInteractionConnectionCallback callback, long threadId) { + return false; + } + + public AccessibilityWindowInfo getWindow(int windowId) { + return null; + } + + public List<AccessibilityWindowInfo> getWindows() { + return null; + } + + public AccessibilityServiceInfo getServiceInfo() { + return null; + } + + public boolean performGlobalAction(int action) { + return false; + } + + public void disableSelf() {} + + public void setOnKeyEventResult(boolean handled, int sequence) {} + + public float getMagnificationScale() { + return 0.0f; + } + + public float getMagnificationCenterX() { + return 0.0f; + } + + public float getMagnificationCenterY() { + return 0.0f; + } + + public Region getMagnificationRegion() { + return null; + } + + public boolean resetMagnification(boolean animate) { + return false; + } + + public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY, + boolean animate) { + return false; + } + + public void setMagnificationCallbackEnabled(boolean enabled) {} + + public boolean setSoftKeyboardShowMode(int showMode) { + return false; + } + + public void setSoftKeyboardCallbackEnabled(boolean enabled) {} + + public boolean isAccessibilityButtonAvailable() { + return false; + } + + public void sendGesture(int sequence, ParceledListSlice gestureSteps) {} + + public boolean isFingerprintGestureDetectionAvailable() { + return false; + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java new file mode 100644 index 000000000000..682a0021b274 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.os; + +import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import static org.junit.Assume.assumeTrue; + +import com.android.frameworks.coretests.aidl.ICmdCallback; +import com.android.frameworks.coretests.aidl.ICmdReceiver; + +import android.app.KeyguardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.BatteryManager; +import android.os.Bundle; +import android.os.IBinder; +import android.os.PowerManager; +import android.os.Process; +import android.os.SystemClock; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.LargeTest; +import android.support.test.runner.AndroidJUnit4; +import android.support.test.uiautomator.UiDevice; +import android.util.Log; + +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@LargeTest +@RunWith(AndroidJUnit4.class) +public class BstatsCpuTimesValidationTest { + private static final String TAG = BstatsCpuTimesValidationTest.class.getName(); + + private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp"; + private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity"; + private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService"; + + private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver"; + + private static final int BATTERY_STATE_TIMEOUT_MS = 2000; + private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200; + + private static final int START_ACTIVITY_TIMEOUT_MS = 2000; + private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000; + + private static final int GENERAL_TIMEOUT_MS = 1000; + private static final int GENERAL_INTERVAL_MS = 100; + + private static final int WORK_DURATION_MS = 2000; + + private static Context sContext; + private static UiDevice sUiDevice; + private static int sTestPkgUid; + private static boolean sCpuFreqTimesAvailable; + + @BeforeClass + public static void setupOnce() throws Exception { + sContext = InstrumentationRegistry.getContext(); + sUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0); + sCpuFreqTimesAvailable = cpuFreqTimesAvailable(); + } + + // Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state + // kernel node is available. + private static boolean cpuFreqTimesAvailable() throws Exception { + final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID); + return cpuTimes != null; + } + + @Test + public void testCpuFreqTimes() throws Exception { + assumeTrue(sCpuFreqTimesAvailable); + + batteryOnScreenOn(); + forceStop(); + resetBatteryStats(); + final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid); + assertNull("Initial snapshot should be null", initialSnapshot); + doSomeWork(); + forceStop(); + + final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid); + assertCpuTimesValid(cpuTimesMs); + long actualCpuTimeMs = 0; + for (int i = 0; i < cpuTimesMs.length / 2; ++i) { + actualCpuTimeMs += cpuTimesMs[i]; + } + assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs); + batteryOffScreenOn(); + } + + @Test + public void testCpuFreqTimes_screenOff() throws Exception { + assumeTrue(sCpuFreqTimesAvailable); + + batteryOnScreenOff(); + forceStop(); + resetBatteryStats(); + final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid); + assertNull("Initial snapshot should be null", initialSnapshot); + doSomeWork(); + forceStop(); + + final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid); + assertCpuTimesValid(cpuTimesMs); + long actualTotalCpuTimeMs = 0; + for (int i = 0; i < cpuTimesMs.length / 2; ++i) { + actualTotalCpuTimeMs += cpuTimesMs[i]; + } + assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualTotalCpuTimeMs); + long actualScreenOffCpuTimeMs = 0; + for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) { + actualScreenOffCpuTimeMs += cpuTimesMs[i]; + } + assertApproximateValue("Incorrect screen-off cpu time", + WORK_DURATION_MS, actualScreenOffCpuTimeMs); + batteryOffScreenOn(); + } + + @Test + public void testCpuFreqTimes_isolatedProcess() throws Exception { + assumeTrue(sCpuFreqTimesAvailable); + + batteryOnScreenOn(); + forceStop(); + resetBatteryStats(); + final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid); + assertNull("Initial snapshot should be null", initialSnapshot); + doSomeWorkInIsolatedProcess(); + forceStop(); + + final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid); + assertCpuTimesValid(cpuTimesMs); + long actualCpuTimeMs = 0; + for (int i = 0; i < cpuTimesMs.length / 2; ++i) { + actualCpuTimeMs += cpuTimesMs[i]; + } + assertApproximateValue("Incorrect total cpu time", WORK_DURATION_MS, actualCpuTimeMs); + batteryOffScreenOn(); + } + + private void assertCpuTimesValid(long[] cpuTimes) { + assertNotNull(cpuTimes); + for (int i = 0; i < cpuTimes.length; ++i) { + if (cpuTimes[i] < 0) { + fail("Malformed cpu times data (-ve values): " + Arrays.toString(cpuTimes)); + } + } + final int numFreqs = cpuTimes.length / 2; + for (int i = 0; i < numFreqs; ++i) { + if (cpuTimes[i] < cpuTimes[numFreqs + i]) { + fail("Malformed cpu times data (screen-off > total)" + Arrays.toString(cpuTimes)); + } + } + } + + private void assertApproximateValue(String errorPrefix, long expectedValue, long actualValue) { + assertValueRange(errorPrefix, actualValue, expectedValue * 0.5, expectedValue * 1.5); + } + + private void assertValueRange(String errorPrefix, + long actualvalue, double minValue, double maxValue) { + final String errorMsg = String.format(errorPrefix + "; actual=%s; min=%s; max=%s", + actualvalue, minValue, maxValue); + assertTrue(errorMsg, actualvalue < maxValue); + assertTrue(errorMsg, actualvalue > minValue); + } + + private void doSomeWork() throws Exception { + final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startActivity()); + receiver.doSomeWork(WORK_DURATION_MS); + receiver.finishHost(); + } + + private void doSomeWorkInIsolatedProcess() throws Exception { + final ICmdReceiver receiver = ICmdReceiver.Stub.asInterface(startIsolatedService()); + receiver.doSomeWork(WORK_DURATION_MS); + receiver.finishHost(); + } + + private IBinder startIsolatedService() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final IBinder[] binders = new IBinder[1]; + final ServiceConnection connection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + binders[0] = service; + latch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + }; + final Intent launchIntent = new Intent() + .setComponent(new ComponentName(TEST_PKG, ISOLATED_TEST_SERVICE)); + sContext.bindService(launchIntent, connection, Context.BIND_AUTO_CREATE + | Context.BIND_ALLOW_OOM_MANAGEMENT | Context.BIND_NOT_FOREGROUND); + if (latch.await(START_ISOLATED_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + if (binders[0] == null) { + fail("Receiver binder should not be null"); + } + return binders[0]; + } else { + fail("Timed out waiting for the isolated test service to start"); + } + return null; + } + + private IBinder startActivity() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + final Intent launchIntent = new Intent() + .setComponent(new ComponentName(TEST_PKG, TEST_ACTIVITY)); + final Bundle extras = new Bundle(); + final IBinder[] binders = new IBinder[1]; + extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() { + @Override + public void onActivityLaunched(IBinder receiver) { + binders[0] = receiver; + latch.countDown(); + } + }); + launchIntent.putExtras(extras); + sContext.startActivity(launchIntent); + if (latch.await(START_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { + if (binders[0] == null) { + fail("Receiver binder should not be null"); + } + return binders[0]; + } else { + fail("Timed out waiting for the test activity to start; testUid=" + sTestPkgUid); + } + return null; + } + + private static long[] getAllCpuFreqTimes(int uid) throws Exception { + final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin"); + final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n"); + final Matcher matcher = pattern.matcher(checkinDump); + if (!matcher.find()) { + return null; + } + final String[] uidTimesStr = matcher.group(1).split(","); + final int freqCount = Integer.parseInt(uidTimesStr[0]); + if (uidTimesStr.length != (2 * freqCount + 1)) { + fail("Malformed data: " + Arrays.toString(uidTimesStr)); + } + final long[] cpuTimes = new long[freqCount * 2]; + for (int i = 0; i < cpuTimes.length; ++i) { + cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]); + } + return cpuTimes; + } + + private void resetBatteryStats() throws Exception { + executeCmd("dumpsys batterystats --reset"); + } + + private void batteryOnScreenOn() throws Exception { + batteryOn(); + screenOn(); + } + + private void batteryOnScreenOff() throws Exception { + batteryOn(); + screenoff(); + } + + private void batteryOffScreenOn() throws Exception { + batteryOff(); + screenOn(); + } + + private void batteryOn() throws Exception { + executeCmd("dumpsys battery unplug"); + assertBatteryState(false); + } + + private void batteryOff() throws Exception { + executeCmd("dumpsys battery reset"); + assertBatteryState(true); + } + + private void screenOn() throws Exception { + executeCmd("input keyevent KEYCODE_WAKEUP"); + executeCmd("wm dismiss-keyguard"); + assertKeyguardUnLocked(); + assertScreenInteractive(true); + } + + private void screenoff() throws Exception { + executeCmd("input keyevent KEYCODE_SLEEP"); + assertScreenInteractive(false); + } + + private void forceStop() throws Exception { + executeCmd("cmd activity force-stop " + TEST_PKG); + assertUidState(PROCESS_STATE_NONEXISTENT); + } + + private void assertUidState(int state) throws Exception { + final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid); + final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]); + assertEquals(state, uidState); + } + + private void assertKeyguardUnLocked() { + final KeyguardManager keyguardManager = + (KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE); + assertDelayedCondition("Keyguard should be unlocked", + () -> !keyguardManager.isKeyguardLocked()); + } + + private void assertScreenInteractive(boolean interactive) { + final PowerManager powerManager = + (PowerManager) sContext.getSystemService(Context.POWER_SERVICE); + assertDelayedCondition("Unexpected screen interactive state", + () -> interactive == powerManager.isInteractive()); + } + + private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) { + final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS; + while (SystemClock.uptimeMillis() <= endTime) { + if (condition.isTrue()) { + return; + } + SystemClock.sleep(GENERAL_INTERVAL_MS); + } + if (!condition.isTrue()) { + fail(errorMsg); + } + } + + private void assertBatteryState(boolean pluggedIn) throws Exception { + final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS; + while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) { + Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS); + } + if (isDevicePluggedIn() != pluggedIn) { + fail("Timed out waiting for the plugged-in state to change," + + " expected pluggedIn: " + pluggedIn); + } + } + + private boolean isDevicePluggedIn() { + final Intent batteryIntent = sContext.registerReceiver(null, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0; + } + + private String executeCmd(String cmd) throws Exception { + final String result = sUiDevice.executeShellCommand(cmd).trim(); + Log.d(TAG, String.format("Result for '%s': %s", cmd, result)); + return result; + } + + private static String executeCmdSilent(String cmd) throws Exception { + return sUiDevice.executeShellCommand(cmd).trim(); + } + + private interface ExpectedCondition { + boolean isTrue(); + } +} diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java index 3fe730fdacba..2daf733d057f 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -40,6 +40,7 @@ import android.security.keystore.KeyProperties; import java.io.ByteArrayInputStream; import java.io.Closeable; +import java.security.KeyPair; import java.security.Principal; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; @@ -418,6 +419,18 @@ public final class KeyChain { @Nullable @WorkerThread public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias) throws KeyChainException, InterruptedException { + KeyPair keyPair = getKeyPair(context, alias); + if (keyPair != null) { + return keyPair.getPrivate(); + } + + return null; + } + + /** @hide */ + @Nullable @WorkerThread + public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias) + throws KeyChainException, InterruptedException { if (alias == null) { throw new NullPointerException("alias == null"); } @@ -439,7 +452,7 @@ public final class KeyChain { return null; } else { try { - return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore( + return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore( KeyStore.getInstance(), keyId, KeyStore.UID_SELF); } catch (RuntimeException | UnrecoverableKeyException e) { throw new KeyChainException(e); diff --git a/location/tests/Android.mk b/location/tests/Android.mk new file mode 100644 index 000000000000..57848f353fd4 --- /dev/null +++ b/location/tests/Android.mk @@ -0,0 +1,3 @@ +LOCAL_PATH:= $(call my-dir) + +include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index 6c87a9d8d915..143182f83ace 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -1586,8 +1586,10 @@ public final class TvInputManager { * @param info The TV input which will use the acquired Hardware. * @return Hardware on success, {@code null} otherwise. * + * @hide * @removed */ + @SystemApi @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback, TvInputInfo info) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java index ba1c9e3cc43b..52b4f4df5735 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java @@ -801,6 +801,9 @@ class SettingsProtoDumpUtil { Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX, GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX); dumpSetting(s, p, + Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX, + GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX); + dumpSetting(s, p, Settings.Global.ACTIVITY_MANAGER_CONSTANTS, GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS); dumpSetting(s, p, diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index cf7923805f0f..8e065d1bf06b 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -250,10 +250,7 @@ <string name="doze_brightness_sensor_type" translatable="false"></string> <!-- Doze: pulse parameter - how long does it take to fade in? --> - <integer name="doze_pulse_duration_in">900</integer> - - <!-- Doze: pulse parameter - how long does it take to fade in after a pickup? --> - <integer name="doze_pulse_duration_in_pickup">130</integer> + <integer name="doze_pulse_duration_in">130</integer> <!-- Doze: pulse parameter - once faded in, how long does it stay visible? --> <integer name="doze_pulse_duration_visible">6000</integer> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index cfd95b41825a..884e18918fd6 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -47,6 +47,7 @@ <item type="id" name="qs_icon_tag"/> <item type="id" name="qs_slash_tag"/> <item type="id" name="scrim"/> + <item type="id" name="scrim_blanking"/> <item type="id" name="scrim_target"/> <item type="id" name="scrim_alpha_start"/> <item type="id" name="scrim_alpha_end"/> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 0c067ff38295..526a8f464441 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -28,6 +28,7 @@ import com.android.systemui.Dependency.DependencyProvider; import com.android.systemui.keyguard.DismissCallbackRegistry; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.LightBarController; import com.android.systemui.statusbar.phone.LockIcon; @@ -86,10 +87,10 @@ public class SystemUIFactory { public ScrimController createScrimController(LightBarController lightBarController, ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, - LockscreenWallpaper lockscreenWallpaper, - Consumer<Boolean> scrimVisibleListener) { + LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener, + DozeParameters dozeParameters) { return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim, - scrimVisibleListener); + scrimVisibleListener, dozeParameters); } public NotificationIconAreaController createNotificationIconAreaController(Context context, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java index 7db118d7fb1c..2f607eee4f16 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java @@ -35,7 +35,6 @@ public interface DozeHost { boolean isBlockingDoze(); void startPendingIntentDismissingKeyguard(PendingIntent intent); - void abortPulsing(); void extendPulse(); void setAnimateWakeup(boolean animateWakeup); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index b99e76a28c6d..c92acd068745 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2028,12 +2028,9 @@ public class KeyguardViewMediator extends SystemUI { } public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar, - ViewGroup container, - ScrimController scrimController, - FingerprintUnlockController fingerprintUnlockController) { + ViewGroup container, FingerprintUnlockController fingerprintUnlockController) { mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, - scrimController, fingerprintUnlockController, - mDismissCallbackRegistry); + fingerprintUnlockController, mDismissCallbackRegistry); return mStatusBarKeyguardViewManager; } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 991c3c83cbc1..e9096447bbf6 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -162,7 +162,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { Matrix matrix = new Matrix(); int overlayColor = 0x40FFFFFF; - Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, data.image.getConfig()); + Bitmap picture = Bitmap.createBitmap(previewWidth, previewHeight, Bitmap.Config.ARGB_8888); matrix.setTranslate((previewWidth - mImageWidth) / 2, (previewHeight - mImageHeight) / 2); c.setBitmap(picture); c.drawBitmap(data.image, matrix, paint); @@ -171,7 +171,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { // Note, we can't use the preview for the small icon, since it is non-square float scale = (float) iconSize / Math.min(mImageWidth, mImageHeight); - Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, data.image.getConfig()); + Bitmap icon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); matrix.setScale(scale, scale); matrix.postTranslate((iconSize - (scale * mImageWidth)) / 2, (iconSize - (scale * mImageHeight)) / 2); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java index a53e348fd16c..fb9c037c236c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java @@ -252,6 +252,13 @@ public class ScrimView extends View implements ConfigurationController.Configura return false; } + /** + * It might look counterintuitive to have another method to set the alpha instead of + * only using {@link #setAlpha(float)}. In this case we're in a hardware layer + * optimizing blend modes, so it makes sense. + * + * @param alpha Gradient alpha from 0 to 1. + */ public void setViewAlpha(float alpha) { if (alpha != mViewAlpha) { mViewAlpha = alpha; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java index eb211a10737b..4d58cb88dd13 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java @@ -58,6 +58,6 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi @Override public boolean isDimmable() { - return false; + return getCustomBackgroundColor() == 0; } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java index 6b7397b3f8ca..3f57c2f9384f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java @@ -46,10 +46,8 @@ public class DozeParameters { public void dump(PrintWriter pw) { pw.println(" DozeParameters:"); pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported()); - pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false)); - pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true)); - pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false)); - pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true)); + pw.print(" getPulseDuration(): "); pw.println(getPulseDuration()); + pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration()); pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration()); pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration()); pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion()); @@ -81,14 +79,12 @@ public class DozeParameters { return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported); } - public int getPulseDuration(boolean pickup) { - return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration(); + public int getPulseDuration() { + return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration(); } - public int getPulseInDuration(boolean pickupOrDoubleTap) { - return pickupOrDoubleTap - ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup) - : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); + public int getPulseInDuration() { + return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in); } public int getPulseVisibleDuration() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java index 8afb8490808e..1011383b72e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java @@ -16,16 +16,11 @@ package com.android.systemui.statusbar.phone; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.annotation.NonNull; import android.content.Context; import android.os.Handler; import android.util.Log; -import android.view.animation.Interpolator; -import com.android.systemui.Interpolators; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; @@ -40,74 +35,59 @@ public class DozeScrimController { private final Handler mHandler = new Handler(); private final ScrimController mScrimController; - private final Context mContext; - private boolean mDozing; private DozeHost.PulseCallback mPulseCallback; private int mPulseReason; - private Animator mInFrontAnimator; - private Animator mBehindAnimator; - private float mInFrontTarget; - private float mBehindTarget; - private boolean mDozingAborted; - private boolean mWakeAndUnlocking; private boolean mFullyPulsing; - private float mAodFrontScrimOpacity = 0; - private Runnable mSetDozeInFrontAlphaDelayed; + private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + if (DEBUG) { + Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" + + DozeLog.pulseReasonToString(mPulseReason)); + } + if (!mDozing) { + return; + } + + // Signal that the pulse is ready to turn the screen on and draw. + pulseStarted(); + } + + @Override + public void onFinished() { + if (DEBUG) { + Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); + } + if (!mDozing) { + return; + } + mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); + mHandler.postDelayed(mPulseOutExtended, + mDozeParameters.getPulseVisibleDurationExtended()); + mFullyPulsing = true; + } + + /** + * Transition was aborted before it was over. + */ + @Override + public void onCancelled() { + pulseFinished(); + } + }; public DozeScrimController(ScrimController scrimController, Context context) { - mContext = context; mScrimController = scrimController; mDozeParameters = new DozeParameters(context); } - public void setDozing(boolean dozing, boolean animate) { + public void setDozing(boolean dozing) { if (mDozing == dozing) return; mDozing = dozing; - mWakeAndUnlocking = false; - if (mDozing) { - mDozingAborted = false; - abortAnimations(); - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f); - } else { + if (!mDozing) { cancelPulsing(); - if (animate) { - startScrimAnimation(false /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - startScrimAnimation(true /* inFront */, 0f /* target */, - NotificationPanelView.DOZE_ANIMATION_DURATION, - Interpolators.LINEAR_OUT_SLOW_IN); - } else { - abortAnimations(); - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); - } - } - } - - /** - * Set the opacity of the front scrim when showing AOD1 - * - * Used to emulate lower brightness values than the hardware supports natively. - */ - public void setAodDimmingScrim(float scrimOpacity) { - mAodFrontScrimOpacity = scrimOpacity; - if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking - && mDozeParameters.getAlwaysOn()) { - setDozeInFrontAlpha(mAodFrontScrimOpacity); - } - } - - public void setWakeAndUnlocking() { - // Immediately abort the doze scrims in case of wake-and-unlock - // for pulsing so the Keyguard fade-out animation scrim can take over. - if (!mWakeAndUnlocking) { - mWakeAndUnlocking = true; - mScrimController.setDozeBehindAlpha(0f); - setDozeInFrontAlpha(0f); } } @@ -118,37 +98,21 @@ public class DozeScrimController { } if (!mDozing || mPulseCallback != null) { + if (DEBUG) { + Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? " + + (mPulseCallback != null)); + } // Pulse suppressed. callback.onPulseFinished(); return; } - // Begin pulse. Note that it's very important that the pulse finished callback + // Begin pulse. Note that it's very important that the pulse finished callback // be invoked when we're done so that the caller can drop the pulse wakelock. mPulseCallback = callback; mPulseReason = reason; - setDozeInFrontAlpha(1f); - mHandler.post(mPulseIn); - } - - /** - * Aborts pulsing immediately. - */ - public void abortPulsing() { - cancelPulsing(); - if (mDozing && !mWakeAndUnlocking) { - mScrimController.setDozeBehindAlpha(1f); - setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted - ? mAodFrontScrimOpacity : 1f); - } - } - /** - * Aborts dozing immediately. - */ - public void abortDoze() { - mDozingAborted = true; - abortPulsing(); + mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback); } public void pulseOutNow() { @@ -157,17 +121,6 @@ public class DozeScrimController { } } - public void onScreenTurnedOn() { - if (isPulsing()) { - final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP - || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP; - startScrimAnimation(true /* inFront */, 0f, - mDozeParameters.getPulseInDuration(pickupOrDoubleTap), - pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT, - mPulseInFinished); - } - } - public boolean isPulsing() { return mPulseCallback != null; } @@ -181,11 +134,9 @@ public class DozeScrimController { } private void cancelPulsing() { - if (DEBUG) Log.d(TAG, "Cancel pulsing"); - if (mPulseCallback != null) { + if (DEBUG) Log.d(TAG, "Cancel pulsing"); mFullyPulsing = false; - mHandler.removeCallbacks(mPulseIn); mHandler.removeCallbacks(mPulseOut); mHandler.removeCallbacks(mPulseOutExtended); pulseFinished(); @@ -193,151 +144,20 @@ public class DozeScrimController { } private void pulseStarted() { + DozeLog.tracePulseStart(mPulseReason); if (mPulseCallback != null) { mPulseCallback.onPulseStarted(); } } private void pulseFinished() { + DozeLog.tracePulseFinish(); if (mPulseCallback != null) { mPulseCallback.onPulseFinished(); mPulseCallback = null; } } - private void abortAnimations() { - if (mInFrontAnimator != null) { - mInFrontAnimator.cancel(); - } - if (mBehindAnimator != null) { - mBehindAnimator.cancel(); - } - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator) { - startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */); - } - - private void startScrimAnimation(final boolean inFront, float target, long duration, - Interpolator interpolator, final Runnable endRunnable) { - Animator current = getCurrentAnimator(inFront); - if (current != null) { - float currentTarget = getCurrentTarget(inFront); - if (currentTarget == target) { - return; - } - current.cancel(); - } - ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (float) animation.getAnimatedValue(); - setDozeAlpha(inFront, value); - } - }); - anim.setInterpolator(interpolator); - anim.setDuration(duration); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - setCurrentAnimator(inFront, null); - if (endRunnable != null) { - endRunnable.run(); - } - } - }); - anim.start(); - setCurrentAnimator(inFront, anim); - setCurrentTarget(inFront, target); - } - - private float getCurrentTarget(boolean inFront) { - return inFront ? mInFrontTarget : mBehindTarget; - } - - private void setCurrentTarget(boolean inFront, float target) { - if (inFront) { - mInFrontTarget = target; - } else { - mBehindTarget = target; - } - } - - private Animator getCurrentAnimator(boolean inFront) { - return inFront ? mInFrontAnimator : mBehindAnimator; - } - - private void setCurrentAnimator(boolean inFront, Animator animator) { - if (inFront) { - mInFrontAnimator = animator; - } else { - mBehindAnimator = animator; - } - } - - private void setDozeAlpha(boolean inFront, float alpha) { - if (mWakeAndUnlocking) { - return; - } - if (inFront) { - mScrimController.setDozeInFrontAlpha(alpha); - } else { - mScrimController.setDozeBehindAlpha(alpha); - } - } - - private float getDozeAlpha(boolean inFront) { - return inFront - ? mScrimController.getDozeInFrontAlpha() - : mScrimController.getDozeBehindAlpha(); - } - - private void setDozeInFrontAlpha(float opacity) { - setDozeInFrontAlphaDelayed(opacity, 0 /* delay */); - - } - - private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) { - if (mSetDozeInFrontAlphaDelayed != null) { - mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed); - mSetDozeInFrontAlphaDelayed = null; - } - if (delayMs <= 0) { - mScrimController.setDozeInFrontAlpha(opacity); - } else { - mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> { - setDozeInFrontAlpha(opacity); - }, delayMs); - } - } - - private final Runnable mPulseIn = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason=" - + DozeLog.pulseReasonToString(mPulseReason)); - if (!mDozing) return; - DozeLog.tracePulseStart(mPulseReason); - - // Signal that the pulse is ready to turn the screen on and draw. - pulseStarted(); - } - }; - - private final Runnable mPulseInFinished = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing); - if (!mDozing) return; - mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration()); - mHandler.postDelayed(mPulseOutExtended, - mDozeParameters.getPulseVisibleDurationExtended()); - mFullyPulsing = true; - } - }; - private final Runnable mPulseOutExtended = new Runnable() { @Override public void run() { @@ -354,38 +174,13 @@ public class DozeScrimController { mHandler.removeCallbacks(mPulseOutExtended); if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing); if (!mDozing) return; - startScrimAnimation(true /* inFront */, 1, - mDozeParameters.getPulseOutDuration(), - Interpolators.ALPHA_IN, mPulseOutFinishing); - } - }; - - private final Runnable mPulseOutFinishing = new Runnable() { - @Override - public void run() { - if (DEBUG) Log.d(TAG, "Pulse out finished"); - DozeLog.tracePulseFinish(); - if (mDozeParameters.getAlwaysOn() && mDozing) { - // Setting power states can block rendering. For AOD, delay finishing the pulse and - // setting the power state until the fully black scrim had time to hit the - // framebuffer. - mHandler.postDelayed(mPulseOutFinished, 30); - } else { - mPulseOutFinished.run(); - } - } - }; - - private final Runnable mPulseOutFinished = new Runnable() { - @Override - public void run() { - // Signal that the pulse is all finished so we can turn the screen off now. - DozeScrimController.this.pulseFinished(); - if (mDozeParameters.getAlwaysOn()) { - // Setting power states can happen after we push out the frame. Make sure we - // stay fully opaque until the power state request reaches the lower levels. - setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100); - } + mScrimController.transitionTo(ScrimState.AOD, + new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + pulseFinished(); + } + }); } }; -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java index 91369dbd5f89..80d4061b3864 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java @@ -181,9 +181,9 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { } private boolean pulsingOrAod() { - boolean pulsing = mDozeScrimController.isPulsing(); - boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff(); - return pulsing || dozingWithScreenOn; + final ScrimState scrimState = mScrimController.getState(); + return scrimState == ScrimState.AOD + || scrimState == ScrimState.PULSING; } @Override @@ -246,15 +246,12 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { true /* allowEnterAnimation */); } else if (mMode == MODE_WAKE_AND_UNLOCK){ Trace.beginSection("MODE_WAKE_AND_UNLOCK"); - mDozeScrimController.abortDoze(); } else { Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM"); mUpdateMonitor.awakenFromDream(); } mStatusBarWindowManager.setStatusBarFocusable(false); mKeyguardViewMediator.onWakeAndUnlocking(); - mScrimController.setWakeAndUnlocking(); - mDozeScrimController.setWakeAndUnlocking(); if (mStatusBar.getNavigationBarView() != null) { mStatusBar.getNavigationBarView().setWakeAndUnlocking(true); } @@ -269,6 +266,7 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback { } private void showBouncer() { + mScrimController.transitionTo(ScrimState.BOUNCER); mStatusBarKeyguardViewManager.animateCollapsePanels( FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR); mPendingShowBouncer = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 702afa3a38b1..dfd4c1768268 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -25,7 +25,9 @@ import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Handler; import android.os.Trace; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -34,12 +36,14 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.colorextraction.ColorExtractor; import com.android.internal.colorextraction.ColorExtractor.GradientColors; import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener; import com.android.internal.graphics.ColorUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dependency; +import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.statusbar.ExpandableNotificationRow; @@ -47,7 +51,10 @@ import com.android.systemui.statusbar.NotificationData; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.stack.ViewState; +import com.android.systemui.util.wakelock.DelayedWakeLock; +import com.android.systemui.util.wakelock.WakeLock; +import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.function.Consumer; @@ -56,33 +63,54 @@ import java.util.function.Consumer; * security method gets shown). */ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, - OnHeadsUpChangedListener, OnColorsChangedListener { + OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable { + + private static final String TAG = "ScrimController"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + public static final long ANIMATION_DURATION = 220; public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR = new PathInterpolator(0f, 0, 0.7f, 1f); public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED = new PathInterpolator(0.3f, 0f, 0.8f, 1f); - // Default alpha value for most scrims, if unsure use this constant + /** + * Default alpha value for most scrims. + */ public static final float GRADIENT_SCRIM_ALPHA = 0.45f; - // A scrim varies its opacity based on a busyness factor, for example - // how many notifications are currently visible. + /** + * A scrim varies its opacity based on a busyness factor, for example + * how many notifications are currently visible. + */ public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f; + /** + * The most common scrim, the one under the keyguard. + */ protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA; + /** + * We fade out the bottom scrim when the bouncer is visible. + */ protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f; - private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY; - private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; - private static final int TAG_KEY_ANIM = R.id.scrim; + /** + * Opacity of the scrim behind the bouncer (the one doing actual background protection.) + */ + protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY; + + static final int TAG_KEY_ANIM = R.id.scrim; + static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking; private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target; private static final int TAG_START_ALPHA = R.id.scrim_alpha_start; private static final int TAG_END_ALPHA = R.id.scrim_alpha_end; private static final float NOT_INITIALIZED = -1; - private final LightBarController mLightBarController; + private ScrimState mState = ScrimState.UNINITIALIZED; + private final Context mContext; protected final ScrimView mScrimBehind; protected final ScrimView mScrimInFront; - private final UnlockMethodCache mUnlockMethodCache; private final View mHeadsUpScrim; + private final LightBarController mLightBarController; + private final UnlockMethodCache mUnlockMethodCache; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final DozeParameters mDozeParameters; private final SysuiColorExtractor mColorExtractor; private GradientColors mLockColors; @@ -94,61 +122,53 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD; protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING; - protected boolean mKeyguardShowing; private float mFraction; private boolean mDarkenWhileDragging; - protected boolean mBouncerShowing; - protected boolean mBouncerIsKeyguard = false; - private boolean mWakeAndUnlocking; protected boolean mAnimateChange; private boolean mUpdatePending; private boolean mTracking; private boolean mAnimateKeyguardFadingOut; - protected long mDurationOverride = -1; + protected long mAnimationDuration = -1; private long mAnimationDelay; private Runnable mOnAnimationFinished; private boolean mDeferFinishedListener; private final Interpolator mInterpolator = new DecelerateInterpolator(); - private boolean mDozing; - private float mDozeInFrontAlpha; - private float mDozeBehindAlpha; private float mCurrentInFrontAlpha = NOT_INITIALIZED; private float mCurrentBehindAlpha = NOT_INITIALIZED; - private float mCurrentHeadsUpAlpha = NOT_INITIALIZED; + private int mCurrentInFrontTint; + private int mCurrentBehindTint; private int mPinnedHeadsUpCount; private float mTopHeadsUpDragAmount; private View mDraggedHeadsUpView; - private boolean mForceHideScrims; - private boolean mSkipFirstFrame; - private boolean mDontAnimateBouncerChanges; private boolean mKeyguardFadingOutInProgress; - private boolean mAnimatingDozeUnlock; private ValueAnimator mKeyguardFadeoutAnimation; - /** Wake up from AOD transition is starting; need fully opaque front scrim */ - private boolean mWakingUpFromAodStarting; - /** Wake up from AOD transition is in progress; need black tint */ - private boolean mWakingUpFromAodInProgress; - /** Wake up from AOD transition is animating; need to reset when animation finishes */ - private boolean mWakingUpFromAodAnimationRunning; - private boolean mScrimsVisble; + private boolean mScrimsVisible; private final Consumer<Boolean> mScrimVisibleListener; + private boolean mBlankScreen; + private boolean mScreenBlankingCallbackCalled; + private Callback mCallback; + + private final WakeLock mWakeLock; + private boolean mWakeLockHeld; public ScrimController(LightBarController lightBarController, ScrimView scrimBehind, - ScrimView scrimInFront, View headsUpScrim, - Consumer<Boolean> scrimVisibleListener) { + ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener, + DozeParameters dozeParameters) { mScrimBehind = scrimBehind; mScrimInFront = scrimInFront; mHeadsUpScrim = headsUpScrim; mScrimVisibleListener = scrimVisibleListener; - final Context context = scrimBehind.getContext(); - mUnlockMethodCache = UnlockMethodCache.getInstance(context); - mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); + mContext = scrimBehind.getContext(); + mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); + mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mLightBarController = lightBarController; - mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha); + mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha); + mWakeLock = createWakeLock(); // Scrim alpha is initially set to the value on the resource but might be changed // to make sure that text on top of it is legible. mScrimBehindAlpha = mScrimBehindAlphaResValue; + mDozeParameters = dozeParameters; mColorExtractor = Dependency.get(SysuiColorExtractor.class); mColorExtractor.addOnColorsChangedListener(this); @@ -158,159 +178,155 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, ColorExtractor.TYPE_DARK, true /* ignoreVisibility */); mNeedsDrawableColorUpdate = true; + final ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].init(mScrimInFront, mScrimBehind, mDozeParameters); + states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard); + } + mState = ScrimState.UNINITIALIZED; + updateHeadsUpScrim(false); updateScrims(); } - public void setKeyguardShowing(boolean showing) { - mKeyguardShowing = showing; - - // Showing/hiding the keyguard means that scrim colors have to be switched - mNeedsDrawableColorUpdate = true; - scheduleUpdate(); - } - - protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, - float scrimBehindAlphaUnlocking) { - mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; - mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; - scheduleUpdate(); - } - - public void onTrackingStarted() { - mTracking = true; - mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); - } - - public void onExpandingFinished() { - mTracking = false; + public void transitionTo(ScrimState state) { + transitionTo(state, null); } - public void setPanelExpansion(float fraction) { - if (mFraction != fraction) { - mFraction = fraction; - scheduleUpdate(); - if (mPinnedHeadsUpCount != 0) { - updateHeadsUpScrim(false); - } - if (mKeyguardFadeoutAnimation != null && mTracking) { - mKeyguardFadeoutAnimation.cancel(); - } + public void transitionTo(ScrimState state, Callback callback) { + if (state == mState) { + return; + } else if (DEBUG) { + Log.d(TAG, "State changed to: " + state); } - } - - public void setBouncerShowing(boolean showing) { - mBouncerShowing = showing; - mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress; - scheduleUpdate(); - } - /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */ - public void prepareWakeUpFromAod() { - if (mWakingUpFromAodInProgress) { - return; + if (state == ScrimState.UNINITIALIZED) { + throw new IllegalArgumentException("Cannot change to UNINITIALIZED."); } - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = true; - mAnimateChange = false; - scheduleUpdate(); - onPreDraw(); - } - /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */ - public void wakeUpFromAod() { - if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) { - // Wake and unlocking has a separate transition that must not be interfered with. - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - return; + if (mCallback != null) { + mCallback.onCancelled(); } - if (mWakingUpFromAodStarting) { - mWakingUpFromAodInProgress = true; - mWakingUpFromAodStarting = false; - mAnimateChange = true; - scheduleUpdate(); + mCallback = callback; + + state.prepare(mState); + mScreenBlankingCallbackCalled = false; + mAnimationDelay = 0; + mBlankScreen = state.getBlanksScreen(); + mAnimateChange = state.getAnimateChange(); + mAnimationDuration = state.getAnimationDuration(); + mCurrentInFrontTint = state.getFrontTint(); + mCurrentBehindTint = state.getBehindTint(); + mCurrentInFrontAlpha = state.getFrontAlpha(); + mCurrentBehindAlpha = state.getBehindAlpha(); + + // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary + // to do the same when you're just showing the brightness mirror. + mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR; + + if (mKeyguardFadeoutAnimation != null) { + mKeyguardFadeoutAnimation.cancel(); } - } - public void setWakeAndUnlocking() { - mWakeAndUnlocking = true; - mAnimatingDozeUnlock = true; - mWakingUpFromAodStarting = false; - mWakingUpFromAodInProgress = false; - scheduleUpdate(); - } + mState = state; - public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished, - boolean skipFirstFrame) { - mWakeAndUnlocking = false; - mAnimateKeyguardFadingOut = true; - mDurationOverride = duration; - mAnimationDelay = delay; - mAnimateChange = true; - mSkipFirstFrame = skipFirstFrame; - mOnAnimationFinished = onAnimationFinished; + // Do not let the device sleep until we're done with all animations + if (!mWakeLockHeld) { + if (mWakeLock != null) { + mWakeLockHeld = true; + mWakeLock.acquire(); + } else { + Log.w(TAG, "Cannot hold wake lock, it has not been set yet"); + } + } if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) { scheduleUpdate(); - - // No need to wait for the next frame to be drawn for this case - onPreDraw will execute - // the changes we just scheduled. - onPreDraw(); } else { - // In case the user isn't unlocked, make sure to delay a bit because the system is hosed - // with too many things in this case, in order to not skip the initial frames. + // with too many things at this case, in order to not skip the initial frames. mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16); + mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY; } } - public void abortKeyguardFadingOut() { - if (mAnimateKeyguardFadingOut) { - endAnimateKeyguardFadingOut(true /* force */); - } + public ScrimState getState() { + return mState; } - public void animateKeyguardUnoccluding(long duration) { - mAnimateChange = false; - setScrimBehindAlpha(0f); - mAnimateChange = true; + protected void setScrimBehindValues(float scrimBehindAlphaKeyguard, + float scrimBehindAlphaUnlocking) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking; + ScrimState[] states = ScrimState.values(); + for (int i = 0; i < states.length; i++) { + states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard); + } scheduleUpdate(); - mDurationOverride = duration; } - public void animateGoingToFullShade(long delay, long duration) { - mDurationOverride = duration; - mAnimationDelay = delay; - mAnimateChange = true; - scheduleUpdate(); + public void onTrackingStarted() { + mTracking = true; + mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer(); } - public void setDozing(boolean dozing) { - if (mDozing != dozing) { - mDozing = dozing; - scheduleUpdate(); - } + public void onExpandingFinished() { + mTracking = false; } - public void setDozeInFrontAlpha(float alpha) { - mDozeInFrontAlpha = alpha; - updateScrimColor(mScrimInFront); - } + /** + * Current state of the shade expansion when pulling it from the top. + * This value is 1 when on top of the keyguard and goes to 0 as the user drags up. + * + * The expansion fraction is tied to the scrim opacity. + * + * @param fraction From 0 to 1 where 0 means collapse and 1 expanded. + */ + public void setPanelExpansion(float fraction) { + if (mFraction != fraction) { + mFraction = fraction; - public void setDozeBehindAlpha(float alpha) { - mDozeBehindAlpha = alpha; - updateScrimColor(mScrimBehind); - } + if (mState == ScrimState.UNLOCKED) { + // Darken scrim as you pull down the shade when unlocked + float behindFraction = getInterpolatedFraction(); + behindFraction = (float) Math.pow(behindFraction, 0.8f); + mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } else if (mState == ScrimState.KEYGUARD) { + if (mUpdatePending) { + return; + } - public float getDozeBehindAlpha() { - return mDozeBehindAlpha; - } + // Either darken of make the scrim transparent when you + // pull down the shade + float interpolatedFract = getInterpolatedFraction(); + if (mDarkenWhileDragging) { + mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking, + mScrimBehindAlphaKeyguard, interpolatedFract); + mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED; + } else { + mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard, + interpolatedFract); + mCurrentInFrontAlpha = 0; + } + } else { + Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState); + return; + } + + if (mPinnedHeadsUpCount != 0) { + updateHeadsUpScrim(false); + } - public float getDozeInFrontAlpha() { - return mDozeInFrontAlpha; + updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha); + updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha); + } } + /** + * Keyguard and shade scrim opacity varies according to how many notifications are visible. + * @param notificationCount Number of visible notifications. + */ public void setNotificationCount(int notificationCount) { final float maxNotificationDensity = 3; float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f); @@ -319,15 +335,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, notificationDensity); if (mScrimBehindAlphaKeyguard != newAlpha) { mScrimBehindAlphaKeyguard = newAlpha; - mAnimateChange = true; - scheduleUpdate(); - } - } - private float getScrimInFrontAlpha() { - return mKeyguardUpdateMonitor.needsSlowUnlockTransition() - ? SCRIM_IN_FRONT_ALPHA_LOCKED - : SCRIM_IN_FRONT_ALPHA; + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { + scheduleUpdate(); + } + } } /** @@ -352,7 +364,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, if (mNeedsDrawableColorUpdate) { mNeedsDrawableColorUpdate = false; final GradientColors currentScrimColors; - if (mKeyguardShowing) { + if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) { // Always animate color changes if we're seeing the keyguard mScrimInFront.setColors(mLockColors, true /* animated */); mScrimBehind.setColors(mLockColors, true /* animated */); @@ -375,77 +387,31 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mLightBarController.setScrimColor(mScrimInFront.getColors()); } - if (mAnimateKeyguardFadingOut || mForceHideScrims) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(0f); - } else if (mWakeAndUnlocking) { - // During wake and unlock, we first hide everything behind a black scrim, which then - // gets faded out from animateKeyguardFadingOut. This must never be animated. - mAnimateChange = false; - if (mDozing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(1f); - } else { - setScrimInFrontAlpha(1f); - setScrimBehindAlpha(0f); - } - } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) { - updateScrimNormal(); - setScrimInFrontAlpha(0); - } else { - updateScrimKeyguard(); - } - mAnimateChange = false; + setScrimInFrontAlpha(mCurrentInFrontAlpha); + setScrimBehindAlpha(mCurrentBehindAlpha); + dispatchScrimsVisible(); } private void dispatchScrimsVisible() { boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0; - if (mScrimsVisble != scrimsVisible) { - mScrimsVisble = scrimsVisible; + if (mScrimsVisible != scrimsVisible) { + mScrimsVisible = scrimsVisible; mScrimVisibleListener.accept(scrimsVisible); } } - private void updateScrimKeyguard() { - if (mTracking && mDarkenWhileDragging) { - float behindFraction = Math.max(0, Math.min(mFraction, 1)); - float fraction = 1 - behindFraction; - fraction = (float) Math.pow(fraction, 0.8f); - behindFraction = (float) Math.pow(behindFraction, 0.8f); - setScrimInFrontAlpha(fraction * getScrimInFrontAlpha()); - setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard); - } else if (mBouncerShowing && !mBouncerIsKeyguard) { - setScrimInFrontAlpha(getScrimInFrontAlpha()); - updateScrimNormal(); - } else if (mBouncerShowing) { - setScrimInFrontAlpha(0f); - setScrimBehindAlpha(mScrimBehindAlpha); - } else { - float fraction = Math.max(0, Math.min(mFraction, 1)); - if (mWakingUpFromAodStarting) { - setScrimInFrontAlpha(1f); - } else { - setScrimInFrontAlpha(0f); - } - setScrimBehindAlpha(fraction - * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking) - + mScrimBehindAlphaUnlocking); - } - } - - private void updateScrimNormal() { + private float getInterpolatedFraction() { float frac = mFraction; // let's start this 20% of the way down the screen frac = frac * 1.2f - 0.2f; if (frac <= 0) { - setScrimBehindAlpha(0); + return 0; } else { // woo, special effects - final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); - setScrimBehindAlpha(k * mScrimBehindAlpha); + return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f)))); } } @@ -455,102 +421,76 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, private void setScrimInFrontAlpha(float alpha) { setScrimAlpha(mScrimInFront, alpha); - if (alpha == 0f) { - mScrimInFront.setClickable(false); - } else { - // Eat touch events (unless dozing). - mScrimInFront.setClickable(!mDozing); - } } private void setScrimAlpha(View scrim, float alpha) { - updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim)); - } - - protected float getDozeAlpha(View scrim) { - return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha; - } - - protected float getCurrentScrimAlpha(View scrim) { - return scrim == mScrimBehind ? mCurrentBehindAlpha - : scrim == mScrimInFront ? mCurrentInFrontAlpha - : mCurrentHeadsUpAlpha; - } - - private void setCurrentScrimAlpha(View scrim, float alpha) { - if (scrim == mScrimBehind) { - mCurrentBehindAlpha = alpha; - mLightBarController.setScrimAlpha(mCurrentBehindAlpha); - } else if (scrim == mScrimInFront) { - mCurrentInFrontAlpha = alpha; + if (alpha == 0f) { + scrim.setClickable(false); } else { - alpha = Math.max(0.0f, Math.min(1.0f, alpha)); - mCurrentHeadsUpAlpha = alpha; + // Eat touch events (unless dozing). + scrim.setClickable(!(mState == ScrimState.AOD)); } + updateScrim(mAnimateChange, scrim, alpha); } - private void updateScrimColor(View scrim) { - float alpha1 = getCurrentScrimAlpha(scrim); + private void updateScrimColor(View scrim, float alpha, int tint) { + alpha = Math.max(0, Math.min(1.0f, alpha)); if (scrim instanceof ScrimView) { ScrimView scrimView = (ScrimView) scrim; - float dozeAlpha = getDozeAlpha(scrim); - float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha); - alpha = Math.max(0, Math.min(1.0f, alpha)); - scrimView.setViewAlpha(alpha); Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", (int) (alpha * 255)); - int dozeTint = Color.TRANSPARENT; - - boolean dozing = mAnimatingDozeUnlock || mDozing; - boolean frontScrimDozing = mWakingUpFromAodInProgress; - if (dozing || frontScrimDozing && scrim == mScrimInFront) { - dozeTint = Color.BLACK; - } Trace.traceCounter(Trace.TRACE_TAG_APP, scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", - dozeTint == Color.BLACK ? 1 : 0); + Color.alpha(tint)); - scrimView.setTint(dozeTint); + scrimView.setTint(tint); + scrimView.setViewAlpha(alpha); } else { - scrim.setAlpha(alpha1); + scrim.setAlpha(alpha); } dispatchScrimsVisible(); } - private void startScrimAnimation(final View scrim, float target) { - float current = getCurrentScrimAlpha(scrim); - ValueAnimator anim = ValueAnimator.ofFloat(current, target); + private int getCurrentScrimTint(View scrim) { + return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint; + } + + private void startScrimAnimation(final View scrim, float current, float target) { + ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); + final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() : + Color.TRANSPARENT; anim.addUpdateListener(animation -> { - float alpha = (float) animation.getAnimatedValue(); - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + final float animAmount = (float) animation.getAnimatedValue(); + final int finalScrimTint = scrim == mScrimInFront ? + mCurrentInFrontTint : mCurrentBehindTint; + float alpha = MathUtils.lerp(current, target, animAmount); + int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount); + updateScrimColor(scrim, alpha, tint); dispatchScrimsVisible(); }); anim.setInterpolator(getInterpolator()); anim.setStartDelay(mAnimationDelay); - anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION); + anim.setDuration(mAnimationDuration); anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - if (!mDeferFinishedListener && mOnAnimationFinished != null) { - mOnAnimationFinished.run(); - mOnAnimationFinished = null; - } if (mKeyguardFadingOutInProgress) { mKeyguardFadeoutAnimation = null; mKeyguardFadingOutInProgress = false; - mAnimatingDozeUnlock = false; - } - if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) { - mWakingUpFromAodAnimationRunning = false; - mWakingUpFromAodInProgress = false; } + onFinished(); + scrim.setTag(TAG_KEY_ANIM, null); scrim.setTag(TAG_KEY_ANIM_TARGET, null); dispatchScrimsVisible(); + + if (!mDeferFinishedListener && mOnAnimationFinished != null) { + mOnAnimationFinished.run(); + mOnAnimationFinished = null; + } } }); anim.start(); @@ -558,12 +498,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mKeyguardFadingOutInProgress = true; mKeyguardFadeoutAnimation = anim; } - if (mWakingUpFromAodInProgress) { - mWakingUpFromAodAnimationRunning = true; - } - if (mSkipFirstFrame) { - anim.setCurrentPlayTime(16); - } scrim.setTag(TAG_KEY_ANIM, anim); scrim.setTag(TAG_KEY_ANIM_TARGET, target); } @@ -582,19 +516,33 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, public boolean onPreDraw() { mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this); mUpdatePending = false; - if (mDontAnimateBouncerChanges) { - mDontAnimateBouncerChanges = false; + if (mCallback != null) { + mCallback.onStart(); } updateScrims(); - mDurationOverride = -1; - mAnimationDelay = 0; - mSkipFirstFrame = false; // Make sure that we always call the listener even if we didn't start an animation. endAnimateKeyguardFadingOut(false /* force */); return true; } + private void onFinished() { + if (mWakeLockHeld) { + mWakeLock.release(); + mWakeLockHeld = false; + } + if (mCallback != null) { + mCallback.onFinished(); + mCallback = null; + } + // When unlocking with fingerprint, we'll fade the scrims from black to transparent. + // At the end of the animation we need to remove the tint. + if (mState == ScrimState.UNLOCKED) { + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + } + } + private void endAnimateKeyguardFadingOut(boolean force) { mAnimateKeyguardFadingOut = false; if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) { @@ -603,8 +551,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, mOnAnimationFinished = null; } mKeyguardFadingOutInProgress = false; - if (!mWakeAndUnlocking || force) - mAnimatingDozeUnlock = false; } } @@ -641,16 +587,19 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } private void updateHeadsUpScrim(boolean animate) { - updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha); + updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha()); } - private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) { - if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) { - return; - } + @VisibleForTesting + void setOnAnimationFinished(Runnable onAnimationFinished) { + mOnAnimationFinished = onAnimationFinished; + } + + private void updateScrim(boolean animate, View scrim, float alpha) { + final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha() + : scrim.getAlpha(); - ValueAnimator previousAnimator = ViewState.getChildTag(scrim, - TAG_KEY_ANIM); + ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM); float animEndValue = -1; if (previousAnimator != null) { if (animate || alpha == currentAlpha) { @@ -664,9 +613,32 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA); } } - if (alpha != currentAlpha && alpha != animEndValue) { + + final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null; + if (mBlankScreen || blankingInProgress) { + if (!blankingInProgress) { + blankDisplay(); + } + return; + } else if (!mScreenBlankingCallbackCalled) { + // Not blanking the screen. Letting the callback know that we're ready + // to replace what was on the screen before. + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; + } + } + + final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null; + final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue; + final boolean wantsTintUpdate = scrimView != null + && scrimView.getTint() != getCurrentScrimTint(scrimView); + + if (wantsAlphaUpdate || wantsTintUpdate) { if (animate) { - startScrimAnimation(scrim, alpha); + final float fromAlpha = scrimView == null ? scrim.getAlpha() + : scrimView.getViewAlpha(); + startScrimAnimation(scrim, fromAlpha, alpha); scrim.setTag(TAG_START_ALPHA, currentAlpha); scrim.setTag(TAG_END_ALPHA, alpha); } else { @@ -685,13 +657,62 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); } else { // update the alpha directly - setCurrentScrimAlpha(scrim, alpha); - updateScrimColor(scrim); + updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim)); + onFinished(); } } + } else { + onFinished(); } } + private void blankDisplay() { + final float initialAlpha = mScrimInFront.getViewAlpha(); + final int initialTint = mScrimInFront.getTint(); + ValueAnimator anim = ValueAnimator.ofFloat(0, 1); + anim.addUpdateListener(animation -> { + final float amount = (float) animation.getAnimatedValue(); + float animAlpha = MathUtils.lerp(initialAlpha, 1, amount); + int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount); + updateScrimColor(mScrimInFront, animAlpha, animTint); + dispatchScrimsVisible(); + }); + anim.setInterpolator(getInterpolator()); + anim.setDuration(mDozeParameters.getPulseInDuration()); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (mCallback != null) { + mCallback.onDisplayBlanked(); + mScreenBlankingCallbackCalled = true; + } + Runnable blankingCallback = () -> { + mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null); + mBlankScreen = false; + // Try again. + updateScrims(); + }; + + // Setting power states can happen after we push out the frame. Make sure we + // stay fully opaque until the power state request reaches the lower levels. + getHandler().postDelayed(blankingCallback, 100); + + } + }); + anim.start(); + mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim); + + // Finish animation if we're already at its final state + if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) { + anim.end(); + } + } + + @VisibleForTesting + protected Handler getHandler() { + return Handler.getMain(); + } + /** * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means * the heads up is in its resting space and 1 means it's fully dragged out. @@ -719,23 +740,13 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, return alpha * expandFactor; } - public void forceHideScrims(boolean hide, boolean animated) { - mForceHideScrims = hide; - mAnimateChange = animated; - scheduleUpdate(); - } - - public void dontAnimateBouncerChangesUntilNextFrame() { - mDontAnimateBouncerChanges = true; - } - public void setExcludedBackgroundArea(Rect area) { mScrimBehind.setExcludedArea(area); } public int getBackgroundColor() { int color = mLockColors.getMainColor(); - return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)), + return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)), Color.red(color), Color.green(color), Color.blue(color)); } @@ -764,27 +775,41 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, } if ((which & WallpaperManager.FLAG_SYSTEM) != 0) { mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM, - ColorExtractor.TYPE_DARK, mKeyguardShowing); + ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED); mNeedsDrawableColorUpdate = true; scheduleUpdate(); } } - public void dump(PrintWriter pw) { - pw.println(" ScrimController:"); + @VisibleForTesting + protected WakeLock createWakeLock() { + return new DelayedWakeLock(getHandler(), + WakeLock.createPartial(mContext, "Doze")); + } + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" ScrimController:"); + pw.print(" state:"); pw.println(mState); pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentInFrontAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint())); pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha()); pw.print(" alpha="); pw.print(mCurrentBehindAlpha); - pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha); pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint())); - pw.print(" mBouncerShowing="); pw.println(mBouncerShowing); pw.print(" mTracking="); pw.println(mTracking); - pw.print(" mForceHideScrims="); pw.println(mForceHideScrims); + } + + public interface Callback { + default void onStart() { + } + default void onDisplayBlanked() { + } + default void onFinished() { + } + default void onCancelled() { + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java new file mode 100644 index 000000000000..0db98f372561 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import android.graphics.Color; +import android.os.Trace; + +import com.android.systemui.statusbar.ScrimView; + +/** + * Possible states of the ScrimController state machine. + */ +public enum ScrimState { + + /** + * Initial state. + */ + UNINITIALIZED, + + /** + * On the lock screen. + */ + KEYGUARD { + + @Override + public void prepare(ScrimState previousState) { + // DisplayPowerManager will blank the screen, we'll just + // set our scrim to black in this frame to avoid flickering and + // fade it out afterwards. + mBlankScreen = previousState == ScrimState.AOD; + if (previousState == ScrimState.AOD) { + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + mCurrentBehindAlpha = mScrimBehindAlphaKeyguard; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Showing password challenge. + */ + BOUNCER { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING; + mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED; + } + }, + + /** + * Changing screen brightness from quick settings. + */ + BRIGHTNESS_MIRROR { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + } + }, + + /** + * Always on display or screen off. + */ + AOD { + @Override + public void prepare(ScrimState previousState) { + if (previousState == ScrimState.PULSING) { + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); + mBlankScreen = previousState == ScrimState.PULSING; + mCurrentBehindAlpha = 1; + mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + // DisplayPowerManager will blank the screen for us, we just need + // to set our state. + mAnimateChange = false; + } + }, + + /** + * When phone wakes up because you received a notification. + */ + PULSING { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 1; + mCurrentInFrontAlpha = 0; + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = true; + updateScrimColor(mScrimInFront, 1, Color.BLACK); + } + }, + + /** + * Unlocked on top of an app (launcher or any other activity.) + */ + UNLOCKED { + @Override + public void prepare(ScrimState previousState) { + mCurrentBehindAlpha = 0; + mCurrentInFrontAlpha = 0; + mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION; + + if (previousState == ScrimState.AOD) { + // Fade from black to transparent when coming directly from AOD + updateScrimColor(mScrimInFront, 1, Color.BLACK); + updateScrimColor(mScrimBehind, 1, Color.BLACK); + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.BLACK; + mCurrentBehindTint = Color.BLACK; + mBlankScreen = true; + } else { + // Scrims should still be black at the end of the transition. + mCurrentInFrontTint = Color.TRANSPARENT; + mCurrentBehindTint = Color.TRANSPARENT; + mBlankScreen = false; + } + } + }; + + boolean mBlankScreen = false; + long mAnimationDuration = ScrimController.ANIMATION_DURATION; + int mCurrentInFrontTint = Color.TRANSPARENT; + int mCurrentBehindTint = Color.TRANSPARENT; + boolean mAnimateChange = true; + float mCurrentInFrontAlpha; + float mCurrentBehindAlpha; + float mAodFrontScrimAlpha; + float mScrimBehindAlphaKeyguard; + ScrimView mScrimInFront; + ScrimView mScrimBehind; + DozeParameters mDozeParameters; + + public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) { + mScrimInFront = scrimInFront; + mScrimBehind = scrimBehind; + mDozeParameters = dozeParameters; + } + + public void prepare(ScrimState previousState) { + } + + public float getFrontAlpha() { + return mCurrentInFrontAlpha; + } + + public float getBehindAlpha() { + return mCurrentBehindAlpha; + } + + public int getFrontTint() { + return mCurrentInFrontTint; + } + + public int getBehindTint() { + return mCurrentBehindTint; + } + + public long getAnimationDuration() { + return mAnimationDuration; + } + + public boolean getBlanksScreen() { + return mBlankScreen; + } + + public void updateScrimColor(ScrimView scrim, float alpha, int tint) { + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha", + (int) (alpha * 255)); + + Trace.traceCounter(Trace.TRACE_TAG_APP, + scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint", + Color.alpha(tint)); + + scrim.setTint(tint); + scrim.setViewAlpha(alpha); + } + + public boolean getAnimateChange() { + return mAnimateChange; + } + + public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) { + mAodFrontScrimAlpha = aodFrontScrimAlpha; + } + + public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) { + mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard; + } +}
\ No newline at end of file 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 43cc0f771a97..a5609da78abb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -238,6 +238,7 @@ import com.android.systemui.statusbar.stack.NotificationStackScrollLayout .OnChildLocationsChangedListener; import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.leak.LeakDetector; +import com.android.systemui.util.wakelock.WakeLock; import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; @@ -386,6 +387,7 @@ public class StatusBar extends SystemUI implements DemoMode, private VolumeComponent mVolumeComponent; private BrightnessMirrorController mBrightnessMirrorController; + private boolean mBrightnessMirrorVisible; protected FingerprintUnlockController mFingerprintUnlockController; private LightBarController mLightBarController; protected LockscreenWallpaper mLockscreenWallpaper; @@ -646,6 +648,31 @@ public class StatusBar extends SystemUI implements DemoMode, } }; + // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over, + // this animation is tied to the scrim for historic reasons. + // TODO: notify when keyguard has faded away instead of the scrim. + private final ScrimController.Callback mUnlockScrimCallback = new ScrimController + .Callback() { + @Override + public void onFinished() { + notifyKeyguardState(); + } + + @Override + public void onCancelled() { + notifyKeyguardState(); + } + + private void notifyKeyguardState() { + if (mStatusBarKeyguardViewManager == null) { + Log.w(TAG, "Tried to notify keyguard visibility when " + + "mStatusBarKeyguardViewManager was null"); + return; + } + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + }; + private NotificationMessagingUtil mMessagingUtil; private KeyguardUserSwitcher mKeyguardUserSwitcher; private UserSwitcherController mUserSwitcherController; @@ -1045,7 +1072,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarWindowManager != null) { mStatusBarWindowManager.setScrimsVisible(scrimsVisible); } - }); + }, new DozeParameters(mContext)); if (mScrimSrcModeEnabled) { Runnable runnable = () -> { boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE; @@ -1081,7 +1108,10 @@ public class StatusBar extends SystemUI implements DemoMode, final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow, - mScrimController); + (visible) -> { + mBrightnessMirrorVisible = visible; + updateScrimController(); + }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { @@ -1457,8 +1487,7 @@ public class StatusBar extends SystemUI implements DemoMode, mDozeScrimController, keyguardViewMediator, mScrimController, this, UnlockMethodCache.getInstance(mContext)); mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this, - getBouncerContainer(), mScrimController, - mFingerprintUnlockController); + getBouncerContainer(), mFingerprintUnlockController); mKeyguardIndicationController .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager); @@ -2640,7 +2669,7 @@ public class StatusBar extends SystemUI implements DemoMode, } public boolean isPulsing() { - return mDozeScrimController.isPulsing(); + return mDozeScrimController != null && mDozeScrimController.isPulsing(); } @Override @@ -3348,7 +3377,7 @@ public class StatusBar extends SystemUI implements DemoMode, } if (mScrimController != null) { - mScrimController.dump(pw); + mScrimController.dump(fd, pw, args); } if (DUMPTRUCK) { @@ -4081,7 +4110,6 @@ public class StatusBar extends SystemUI implements DemoMode, releaseGestureWakeLock(); runLaunchTransitionEndRunnable(); mLaunchTransitionFadingAway = false; - mScrimController.forceHideScrims(false /* hide */, false /* animated */); updateMediaMetaData(true /* metaDataChanged */, true); } @@ -4114,7 +4142,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (beforeFading != null) { beforeFading.run(); } - mScrimController.forceHideScrims(true /* hide */, false /* animated */); + updateScrimController(); updateMediaMetaData(false, true); mNotificationPanel.setAlpha(1); mStackScroller.setParentNotFullyVisible(true); @@ -4145,6 +4173,13 @@ public class StatusBar extends SystemUI implements DemoMode, .setStartDelay(0) .setDuration(FADE_KEYGUARD_DURATION_PULSING) .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + hideKeyguard(); + mStatusBarKeyguardViewManager.onKeyguardFadedAway(); + } + }) .start(); } @@ -4152,7 +4187,6 @@ public class StatusBar extends SystemUI implements DemoMode, * Plays the animation when an activity that was occluding Keyguard goes away. */ public void animateKeyguardUnoccluding() { - mScrimController.animateKeyguardUnoccluding(500); mNotificationPanel.setExpandedFraction(0f); animateExpandNotificationsPanel(); } @@ -4340,11 +4374,6 @@ public class StatusBar extends SystemUI implements DemoMode, mAmbientIndicationContainer.setVisibility(View.INVISIBLE); } } - if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { - mScrimController.setKeyguardShowing(true); - } else { - mScrimController.setKeyguardShowing(false); - } mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade); updateTheme(); updateDozingState(); @@ -4352,6 +4381,7 @@ public class StatusBar extends SystemUI implements DemoMode, updateStackScrollerState(goingToFullShade, fromShadeLocked); updateNotifications(); checkBarModes(); + updateScrimController(); updateMediaMetaData(false, mState != StatusBarState.KEYGUARD); mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), mUnlockMethodCache.isMethodSecure(), @@ -4415,11 +4445,10 @@ public class StatusBar extends SystemUI implements DemoMode, boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup(); mNotificationPanel.setDozing(mDozing, animate); mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation); - mScrimController.setDozing(mDozing); + mDozeScrimController.setDozing(mDozing); mKeyguardIndicationController.setDozing(mDozing); mNotificationPanel.setDark(mDozing, animate); updateQsExpansionEnabled(); - mDozeScrimController.setDozing(mDozing, animate); updateRowStates(); Trace.endSection(); } @@ -4914,6 +4943,7 @@ public class StatusBar extends SystemUI implements DemoMode, if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing); updateHideIconsForBouncer(true /* animate */); recomputeDisableFlags(true /* animate */); + updateScrimController(); } public void cancelCurrentTouch() { @@ -4965,12 +4995,10 @@ public class StatusBar extends SystemUI implements DemoMode, mStackScroller.setAnimationsEnabled(true); mVisualStabilityManager.setScreenOn(true); mNotificationPanel.setTouchDisabled(false); - - maybePrepareWakeUpFromAod(); - mDozeServiceHost.stopDozing(); updateVisibleToUser(); updateIsKeyguard(); + updateScrimController(); } }; @@ -4980,18 +5008,16 @@ public class StatusBar extends SystemUI implements DemoMode, mFalsingManager.onScreenTurningOn(); mNotificationPanel.onScreenTurningOn(); - maybePrepareWakeUpFromAod(); - if (mLaunchCameraOnScreenTurningOn) { mNotificationPanel.launchCamera(false, mLastCameraLaunchSource); mLaunchCameraOnScreenTurningOn = false; } + + updateScrimController(); } @Override public void onScreenTurnedOn() { - mScrimController.wakeUpFromAod(); - mDozeScrimController.onScreenTurnedOn(); } @Override @@ -5009,13 +5035,6 @@ public class StatusBar extends SystemUI implements DemoMode, return mWakefulnessLifecycle.getWakefulness(); } - private void maybePrepareWakeUpFromAod() { - int wakefulness = mWakefulnessLifecycle.getWakefulness(); - if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) { - mScrimController.prepareWakeUpFromAod(); - } - } - private void vibrateForCameraGesture() { // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep. mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */); @@ -5098,12 +5117,12 @@ public class StatusBar extends SystemUI implements DemoMode, if (!mDeviceInteractive) { // Avoid flickering of the scrim when we instant launch the camera and the bouncer // comes on. - mScrimController.dontAnimateBouncerChangesUntilNextFrame(); mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L); } if (isScreenTurningOnOrOn()) { if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera"); mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source); + updateScrimController(); } else { // We need to defer the camera launch until the screen comes on, since otherwise // we will dismiss us too early since we are waiting on an activity to be drawn and @@ -5145,15 +5164,16 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateDozing() { Trace.beginSection("StatusBar#updateDozing"); // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked. - mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD + boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD || mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING; // When in wake-and-unlock we may not have received a change to mState // but we still should not be dozing, manually set to false. if (mFingerprintUnlockController.getMode() == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) { - mDozing = false; + dozing = false; } + mDozing = dozing; mStatusBarWindowManager.setDozing(mDozing); mStatusBarKeyguardViewManager.setDozing(mDozing); if (mAmbientIndicationContainer instanceof DozeReceiver) { @@ -5163,6 +5183,24 @@ public class StatusBar extends SystemUI implements DemoMode, Trace.endSection(); } + public void updateScrimController() { + if (mBouncerShowing) { + mScrimController.transitionTo(ScrimState.BOUNCER); + } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } else if (mBrightnessMirrorVisible) { + mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + } else if (isPulsing()) { + // Handled in DozeScrimController#setPulsing + } else if (mDozing) { + mScrimController.transitionTo(ScrimState.AOD); + } else if (mIsKeyguard) { + mScrimController.transitionTo(ScrimState.KEYGUARD); + } else { + mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + } + } + public boolean isKeyguardShowing() { if (mStatusBarKeyguardViewManager == null) { Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true"); @@ -5222,7 +5260,6 @@ public class StatusBar extends SystemUI implements DemoMode, } mDozeScrimController.pulse(new PulseCallback() { - @Override public void onPulseStarted() { callback.onPulseStarted(); @@ -5307,11 +5344,6 @@ public class StatusBar extends SystemUI implements DemoMode, } @Override - public void abortPulsing() { - mDozeScrimController.abortPulsing(); - } - - @Override public void extendPulse() { mDozeScrimController.extendPulse(); } @@ -5347,7 +5379,7 @@ public class StatusBar extends SystemUI implements DemoMode, @Override public void setAodDimmingScrim(float scrimOpacity) { - mDozeScrimController.setAodDimmingScrim(scrimOpacity); + ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity); } public void dispatchDoubleTap(float viewX, float viewY) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index bcde556a5954..ef05bbb4fe3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -24,7 +24,6 @@ import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Bundle; import android.os.SystemClock; -import android.os.Trace; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -71,17 +70,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb protected final Context mContext; private final StatusBarWindowManager mStatusBarWindowManager; - private final boolean mDisplayBlanksAfterDoze; protected LockPatternUtils mLockPatternUtils; protected ViewMediatorCallback mViewMediatorCallback; protected StatusBar mStatusBar; - private ScrimController mScrimController; private FingerprintUnlockController mFingerprintUnlockController; private ViewGroup mContainer; - private boolean mScreenTurnedOn; protected KeyguardBouncer mBouncer; protected boolean mShowing; protected boolean mOccluded; @@ -95,12 +91,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private boolean mLastBouncerDismissible; protected boolean mLastRemoteInputActive; private boolean mLastDozing; - private boolean mLastDeferScrimFadeOut; private int mLastFpMode; private OnDismissAction mAfterKeyguardGoneAction; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); - private boolean mDeferScrimFadeOut; // Dismiss action to be launched when we stop dozing or the keyguard is gone. private DismissWithActionRequest mPendingWakeupAction; @@ -125,18 +119,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLockPatternUtils = lockPatternUtils; mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class); KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback); - mDisplayBlanksAfterDoze = context.getResources().getBoolean( - com.android.internal.R.bool.config_displayBlanksAfterDoze); } public void registerStatusBar(StatusBar statusBar, ViewGroup container, - ScrimController scrimController, FingerprintUnlockController fingerprintUnlockController, DismissCallbackRegistry dismissCallbackRegistry) { mStatusBar = statusBar; mContainer = container; - mScrimController = scrimController; mFingerprintUnlockController = fingerprintUnlockController; mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry); @@ -149,7 +139,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { mShowing = true; mStatusBarWindowManager.setKeyguardShowing(true); - mScrimController.abortKeyguardFadingOut(); reset(true /* hideBouncerWhenShowing */); } @@ -253,15 +242,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOn() { - Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn"); - mScreenTurnedOn = true; - if (mDeferScrimFadeOut) { - mDeferScrimFadeOut = false; - animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - updateStates(); - } - Trace.endSection(); + // TODO: remove } @Override @@ -285,7 +266,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } public void onScreenTurnedOff() { - mScreenTurnedOn = false; + // TODO: remove } public void notifyDeviceWakeUpRequested() { @@ -374,10 +355,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBarWindowManager.setKeyguardFadingAway(true); hideBouncer(true /* destroyView */); updateStates(); - mScrimController.animateKeyguardFadingOut( - StatusBar.FADE_KEYGUARD_START_DELAY, - StatusBar.FADE_KEYGUARD_DURATION, null, - false /* skipFirstFrame */); } }, new Runnable() { @Override @@ -400,36 +377,16 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mFingerprintUnlockController.startKeyguardFadingAway(); hideBouncer(true /* destroyView */); if (wakeUnlockPulsing) { - mStatusBarWindowManager.setKeyguardFadingAway(true); mStatusBar.fadeKeyguardWhilePulsing(); - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - mStatusBar::hideKeyguard, false /* skipFirstFrame */); + wakeAndUnlockDejank(); } else { mFingerprintUnlockController.startKeyguardFadingAway(); mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); boolean staying = mStatusBar.hideKeyguard(); if (!staying) { mStatusBarWindowManager.setKeyguardFadingAway(true); - if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) { - boolean turnedOnSinceAuth = - mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating(); - if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) { - // Not ready to animate yet; either because the screen is not on yet, - // or it is on but will turn off before waking out of doze. - mDeferScrimFadeOut = true; - } else { - - // Screen is already on, don't defer with fading out. - animateScrimControllerKeyguardFadingOut(0, - WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, - true /* skipFirstFrame */); - } - } else { - animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, - false /* skipFirstFrame */); - } + wakeAndUnlockDejank(); } else { - mScrimController.animateGoingToFullShade(delay, fadeoutDuration); mStatusBar.finishKeyguardFadingAway(); mFingerprintUnlockController.finishKeyguardFadingAway(); } @@ -449,30 +406,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mBouncer.prepare(); } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - boolean skipFirstFrame) { - animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */, - skipFirstFrame); + public void onKeyguardFadedAway() { + mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), + 100); + mStatusBar.finishKeyguardFadingAway(); + mFingerprintUnlockController.finishKeyguardFadingAway(); + WindowManagerGlobal.getInstance().trimMemory( + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); + } - private void animateScrimControllerKeyguardFadingOut(long delay, long duration, - final Runnable endRunnable, boolean skipFirstFrame) { - Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0); - mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() { - @Override - public void run() { - if (endRunnable != null) { - endRunnable.run(); - } - mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false), - 100); - mStatusBar.finishKeyguardFadingAway(); - mFingerprintUnlockController.finishKeyguardFadingAway(); - WindowManagerGlobal.getInstance().trimMemory( - ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0); - } - }, skipFirstFrame); + private void wakeAndUnlockDejank() { if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK && LatencyTracker.isEnabled(mContext)) { DejankUtils.postAfterTraversal(() -> @@ -593,7 +537,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { mStatusBarWindowManager.setBouncerShowing(bouncerShowing); mStatusBar.setBouncerShowing(bouncerShowing); - mScrimController.setBouncerShowing(bouncerShowing); } KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); @@ -611,7 +554,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mLastBouncerDismissible = bouncerDismissible; mLastRemoteInputActive = remoteInputActive; mLastDozing = mDozing; - mLastDeferScrimFadeOut = mDeferScrimFadeOut; mLastFpMode = mFingerprintUnlockController.getMode(); mStatusBar.onKeyguardViewManagerStatesUpdated(); } @@ -624,7 +566,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mShowing && !mOccluded; boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing() - || mRemoteInputActive) && !mDeferScrimFadeOut; + || mRemoteInputActive); } /** @@ -634,7 +576,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb boolean keyguardShowing = mLastShowing && !mLastOccluded; boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING; return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing - || mLastRemoteInputActive) && !mLastDeferScrimFadeOut; + || mLastRemoteInputActive); } public boolean shouldDismissOnMenuPressed() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java index b0553d72dd49..a011952f1476 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.policy; +import android.annotation.NonNull; import android.util.ArraySet; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; @@ -26,46 +27,47 @@ import android.widget.FrameLayout; import com.android.internal.util.Preconditions; import com.android.systemui.Interpolators; import com.android.systemui.R; -import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarWindowView; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; +import java.util.function.Consumer; + /** * Controls showing and hiding of the brightness mirror. */ public class BrightnessMirrorController implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> { - private final NotificationStackScrollLayout mStackScroller; - public long TRANSITION_DURATION_OUT = 150; - public long TRANSITION_DURATION_IN = 200; + private final static long TRANSITION_DURATION_OUT = 150; + private final static long TRANSITION_DURATION_IN = 200; private final StatusBarWindowView mStatusBarWindow; - private final ScrimController mScrimController; + private final NotificationStackScrollLayout mStackScroller; + private final Consumer<Boolean> mVisibilityCallback; private final View mNotificationPanel; private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>(); private final int[] mInt2Cache = new int[2]; private View mBrightnessMirror; public BrightnessMirrorController(StatusBarWindowView statusBarWindow, - ScrimController scrimController) { + @NonNull Consumer<Boolean> visibilityCallback) { mStatusBarWindow = statusBarWindow; mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror); mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel); mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller); - mScrimController = scrimController; + mVisibilityCallback = visibilityCallback; } public void showMirror() { mBrightnessMirror.setVisibility(View.VISIBLE); mStackScroller.setFadingOut(true); - mScrimController.forceHideScrims(true /* hide */, true /* animated */); + mVisibilityCallback.accept(true); outAnimation(mNotificationPanel.animate()) .withLayer(); } public void hideMirror() { - mScrimController.forceHideScrims(false /* hide */, true /* animated */); + mVisibilityCallback.accept(false); inAnimation(mNotificationPanel.animate()) .withLayer() .withEndAction(() -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java index b0c9f32873ff..1c104cffc3c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java @@ -19,12 +19,13 @@ package com.android.systemui.doze; import android.annotation.NonNull; import android.app.PendingIntent; +import com.android.systemui.util.wakelock.WakeLock; + /** * A rudimentary fake for DozeHost. */ class DozeHostFake implements DozeHost { Callback callback; - boolean pulseAborted; boolean pulseExtended; boolean animateWakeup; boolean dozing; @@ -92,11 +93,6 @@ class DozeHostFake implements DozeHost { } @Override - public void abortPulsing() { - pulseAborted = true; - } - - @Override public void extendPulse() { pulseExtended = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 004ff29f7eac..1c9c7949a971 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -49,6 +49,7 @@ import com.android.systemui.qs.QSHost; import com.android.systemui.qs.QSTileHost; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; @@ -108,6 +109,7 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + @Ignore("flaky") public void testStaleTimeout() throws InterruptedException { when(mTile.getStaleTimeout()).thenReturn(5l); clearInvocations(mTile); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java index 4c3bf1081792..a4bcc69c0e10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java @@ -86,9 +86,9 @@ public class ScrimViewTest extends LeakCheckedTest { @Test public void testSetViewAlpha_propagatesToDrawable() { - float alpha = 0.5f; + final float alpha = 0.5f; mView.setViewAlpha(alpha); - assertEquals(mView.getViewAlpha(), alpha); + assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java new file mode 100644 index 000000000000..ca2f713d2b6f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.os.Debug; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.doze.DozeHost; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class DozeScrimControllerTest extends SysuiTestCase { + + private ScrimController mScrimController; + private DozeScrimController mDozeScrimController; + + @Before + public void setup() { + mScrimController = mock(ScrimController.class); + // Make sure callbacks will be invoked to complete the lifecycle. + doAnswer(invocationOnMock -> { + ScrimController.Callback callback = invocationOnMock.getArgument(1); + callback.onStart(); + callback.onDisplayBlanked(); + callback.onFinished(); + return null; + }).when(mScrimController).transitionTo(any(ScrimState.class), + any(ScrimController.Callback.class)); + + mDozeScrimController = new DozeScrimController(mScrimController, getContext()); + mDozeScrimController.setDozing(true); + } + + @Test + public void changesScrimControllerState() { + mDozeScrimController.pulse(mock(DozeHost.PulseCallback.class), 0); + verify(mScrimController).transitionTo(eq(ScrimState.PULSING), + any(ScrimController.Callback.class)); + } + + @Test + public void callsPulseCallback() { + DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class); + mDozeScrimController.pulse(callback, 0); + + verify(callback).onPulseStarted(); + mDozeScrimController.pulseOutNow(); + verify(callback).onPulseFinished(); + } + + @Test + public void secondPulseIsSuppressed() { + DozeHost.PulseCallback callback1 = mock(DozeHost.PulseCallback.class); + DozeHost.PulseCallback callback2 = mock(DozeHost.PulseCallback.class); + mDozeScrimController.pulse(callback1, 0); + mDozeScrimController.pulse(callback2, 0); + + verify(callback1, never()).onPulseFinished(); + verify(callback2).onPulseFinished(); + } + + @Test + public void suppressesPulseIfNotDozing() { + mDozeScrimController.setDozing(false); + DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class); + mDozeScrimController.pulse(callback, 0); + + verify(callback).onPulseFinished(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java new file mode 100644 index 000000000000..b9f695be90cf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.animation.Animator; +import android.graphics.Color; +import android.os.Handler; +import android.os.Looper; +import android.support.test.filters.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.ScrimView; +import com.android.systemui.util.wakelock.WakeLock; +import com.android.systemui.utils.os.FakeHandler; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.function.Consumer; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class ScrimControllerTest extends SysuiTestCase { + + private SynchronousScrimController mScrimController; + private ScrimView mScrimBehind; + private ScrimView mScrimInFront; + private View mHeadsUpScrim; + private Consumer<Boolean> mScrimVisibilityCallback; + private Boolean mScrimVisibile; + private LightBarController mLightBarController; + private DozeParameters mDozeParamenters; + private WakeLock mWakeLock; + private boolean mAlwaysOnEnabled; + + @Before + public void setup() { + mLightBarController = mock(LightBarController.class); + mScrimBehind = new ScrimView(getContext()); + mScrimInFront = new ScrimView(getContext()); + mHeadsUpScrim = mock(View.class); + mWakeLock = mock(WakeLock.class); + mAlwaysOnEnabled = true; + mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible; + mDozeParamenters = mock(DozeParameters.class); + when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled); + mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind, + mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters); + } + + @Test + public void initialState() { + Assert.assertEquals("ScrimController should start initialized", + mScrimController.getState(), ScrimState.UNINITIALIZED); + } + + @Test + public void transitionToKeyguard() { + mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be visible without tint + assertScrimVisibility(false /* front */, true /* behind */); + assertScrimTint(mScrimBehind, false /* tinted */); + } + + @Test + public void transitionToAod() { + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be visible with tint + assertScrimVisibility(false /* front */, true /* behind */); + assertScrimTint(mScrimBehind, true /* tinted */); + assertScrimTint(mScrimInFront, true /* tinted */); + } + + @Test + public void transitionToPulsing() { + mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be visible with tint + // Pulse callback should have been invoked + assertScrimVisibility(false /* front */, true /* behind */); + assertScrimTint(mScrimBehind, true /* tinted */); + } + + @Test + public void transitionToBouncer() { + mScrimController.transitionTo(ScrimState.BOUNCER); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be visible without tint + assertScrimVisibility(true /* front */, true /* behind */); + assertScrimTint(mScrimBehind, false /* tinted */); + } + + @Test + public void transitionToUnlocked() { + mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be transparent + assertScrimVisibility(false /* front */, false /* behind */); + assertScrimTint(mScrimBehind, false /* tinted */); + assertScrimTint(mScrimInFront, false /* tinted */); + + // Back scrim should be visible after start dragging + mScrimController.setPanelExpansion(0.5f); + assertScrimVisibility(false /* front */, true /* behind */); + } + + @Test + public void transitionToUnlockedFromAod() { + // Simulate unlock with fingerprint + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + mScrimController.transitionTo(ScrimState.UNLOCKED); + // Immediately tinted after the transition starts + assertScrimTint(mScrimInFront, true /* tinted */); + assertScrimTint(mScrimBehind, true /* tinted */); + mScrimController.finishAnimationsImmediately(); + // Front scrim should be transparent + // Back scrim should be transparent + // Neither scrims should be tinted anymore after the animation. + assertScrimVisibility(false /* front */, false /* behind */); + assertScrimTint(mScrimInFront, false /* tinted */); + assertScrimTint(mScrimBehind, false /* tinted */); + } + + @Test + public void scrimBlanksBeforeLeavingAoD() { + // Simulate unlock with fingerprint + mScrimController.transitionTo(ScrimState.AOD); + mScrimController.finishAnimationsImmediately(); + mScrimController.transitionTo(ScrimState.UNLOCKED, + new ScrimController.Callback() { + @Override + public void onDisplayBlanked() { + // Front scrim should be black in the middle of the transition + Assert.assertTrue("Scrim should be visible during transition. Alpha: " + + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0); + assertScrimTint(mScrimInFront, true /* tinted */); + Assert.assertTrue("Scrim should be visible during transition.", + mScrimVisibile); + } + }); + mScrimController.finishAnimationsImmediately(); + } + + @Test + public void testScrimCallback() { + int[] callOrder = {0, 0, 0}; + int[] currentCall = {0}; + mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() { + @Override + public void onStart() { + callOrder[0] = ++currentCall[0]; + } + + @Override + public void onDisplayBlanked() { + callOrder[1] = ++currentCall[0]; + } + + @Override + public void onFinished() { + callOrder[2] = ++currentCall[0]; + } + }); + mScrimController.finishAnimationsImmediately(); + Assert.assertEquals("onStart called in wrong order", 1, callOrder[0]); + Assert.assertEquals("onDisplayBlanked called in wrong order", 2, callOrder[1]); + Assert.assertEquals("onFinished called in wrong order", 3, callOrder[2]); + } + + @Test + public void testScrimCallbacksWithoutAmbientDisplay() { + mAlwaysOnEnabled = false; + testScrimCallback(); + } + + @Test + public void testScrimCallbackCancelled() { + boolean[] cancelledCalled = {false}; + mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() { + @Override + public void onCancelled() { + cancelledCalled[0] = true; + } + }); + mScrimController.transitionTo(ScrimState.PULSING); + Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]); + } + + @Test + public void testHoldsWakeLock() { + mScrimController.transitionTo(ScrimState.AOD); + verify(mWakeLock, times(1)).acquire(); + verify(mWakeLock, never()).release(); + mScrimController.finishAnimationsImmediately(); + verify(mWakeLock, times(1)).release(); + } + + private void assertScrimTint(ScrimView scrimView, boolean tinted) { + final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT; + final String name = scrimView == mScrimInFront ? "front" : "back"; + Assert.assertEquals("Tint test failed at state " + mScrimController.getState() + +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()), + tinted, viewIsTinted); + } + + private void assertScrimVisibility(boolean inFront, boolean behind) { + Assert.assertEquals("Unexpected front scrim visibility. Alpha is " + + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0); + Assert.assertEquals("Unexpected back scrim visibility. Alpha is " + + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0); + Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile); + } + + /** + * Special version of ScrimController where animations have 0 duration for test purposes. + */ + private class SynchronousScrimController extends ScrimController { + + private FakeHandler mHandler; + + public SynchronousScrimController(LightBarController lightBarController, + ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim, + Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) { + super(lightBarController, scrimBehind, scrimInFront, headsUpScrim, + scrimVisibleListener, dozeParameters); + mHandler = new FakeHandler(Looper.myLooper()); + } + + public void finishAnimationsImmediately() { + boolean[] animationFinished = {false}; + setOnAnimationFinished(()-> animationFinished[0] = true); + + // Execute code that will trigger animations. + onPreDraw(); + + // Force finish screen blanking. + endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK); + mHandler.dispatchQueuedMessages(); + // Force finish all animations. + endAnimation(mScrimBehind, TAG_KEY_ANIM); + endAnimation(mScrimInFront, TAG_KEY_ANIM); + + if (!animationFinished[0]) { + throw new IllegalStateException("Animation never finished"); + } + } + + private void endAnimation(ScrimView scrimView, int tag) { + Animator animator = (Animator) scrimView.getTag(tag); + if (animator != null) { + animator.end(); + } + } + + @Override + protected Handler getHandler() { + return mHandler; + } + + @Override + protected WakeLock createWakeLock() { + return mWakeLock; + } + } + +} diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 4330f5d892c7..ca1524959358 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -48,6 +48,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.provider.Settings; @@ -3051,6 +3052,11 @@ class AlarmManagerService extends SystemService { for (int i=0; i<triggerList.size(); i++) { Alarm alarm = triggerList.get(i); final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0; + if (alarm.wakeup) { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName); + } else { + Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName); + } try { if (localLOGV) { Slog.v(TAG, "sending alarm " + alarm); @@ -3070,6 +3076,7 @@ class AlarmManagerService extends SystemService { } catch (RuntimeException e) { Slog.w(TAG, "Failure sending alarm.", e); } + Trace.traceEnd(Trace.TRACE_TAG_POWER); } } diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java index 1ad14047adf9..2c24798204eb 100644 --- a/services/core/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java @@ -69,13 +69,10 @@ public class NetworkTimeUpdateService extends Binder { private static final String ACTION_POLL = "com.android.server.NetworkTimeUpdateService.action.POLL"; - private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000; - private static int POLL_REQUEST = 0; + private static final int POLL_REQUEST = 0; private static final long NOT_SET = -1; private long mNitzTimeSetTime = NOT_SET; - // TODO: Have a way to look up the timezone we are in - private long mNitzZoneSetTime = NOT_SET; private Network mDefaultNetwork = null; private Context mContext; @@ -144,7 +141,6 @@ public class NetworkTimeUpdateService extends Binder { private void registerForTelephonyIntents() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME); - intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE); mContext.registerReceiver(mNitzReceiver, intentFilter); } @@ -257,8 +253,6 @@ public class NetworkTimeUpdateService extends Binder { if (DBG) Log.d(TAG, "Received " + action); if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) { mNitzTimeSetTime = SystemClock.elapsedRealtime(); - } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) { - mNitzZoneSetTime = SystemClock.elapsedRealtime(); } } }; diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 3c955eb0f0db..66b3adb68ddd 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -2192,6 +2192,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return -1; + } + if (type == StorageManager.CRYPT_TYPE_DEFAULT) { password = ""; } else if (TextUtils.isEmpty(password)) { @@ -2268,6 +2273,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return; + } + try { mVold.fdeSetField(field, contents); return; @@ -2287,6 +2297,11 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER, "no permission to access the crypt keeper"); + if (StorageManager.isFileEncryptedNativeOnly()) { + // Not supported on FBE devices + return null; + } + try { return mVold.fdeGetField(field); } catch (Exception e) { @@ -2568,7 +2583,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon } @Override - public int mkdirs(String callingPkg, String appPath) { + public void mkdirs(String callingPkg, String appPath) { final int userId = UserHandle.getUserId(Binder.getCallingUid()); final UserEnvironment userEnv = new UserEnvironment(userId); @@ -2581,8 +2596,7 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon try { appFile = new File(appPath).getCanonicalFile(); } catch (IOException e) { - Slog.e(TAG, "Failed to resolve " + appPath + ": " + e); - return -1; + throw new IllegalStateException("Failed to resolve " + appPath + ": " + e); } // Try translating the app path into a vold path, but require that it @@ -2597,9 +2611,8 @@ class StorageManagerService extends IStorageManager.Stub implements Watchdog.Mon try { mVold.mkdirs(appPath); - return 0; } catch (Exception e) { - Slog.wtf(TAG, e); + throw new IllegalStateException("Failed to prepare " + appPath + ": " + e); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3c8f112cfab0..b2681935798a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -246,6 +246,7 @@ import android.app.admin.DevicePolicyManager; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.IBackupManager; +import android.app.servertransaction.ConfigurationChangeItem; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; @@ -317,6 +318,7 @@ import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.PowerManager; +import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; import android.os.Process; import android.os.RemoteCallbackList; @@ -631,6 +633,8 @@ public class ActivityManagerService extends IActivityManager.Stub final ActivityStarter mActivityStarter; + final ClientLifecycleManager mLifecycleManager; + final TaskChangeNotificationController mTaskChangeNotificationController; final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter(); @@ -1778,6 +1782,11 @@ public class ActivityManagerService extends IActivityManager.Stub final boolean mPermissionReviewRequired; + /** + * Whether to force background check on all apps (for battery saver) or not. + */ + boolean mForceBackgroundCheck; + private static String sTheRealBuildSerial = Build.UNKNOWN; /** @@ -2693,6 +2702,7 @@ public class ActivityManagerService extends IActivityManager.Stub mUserController = null; mVrController = null; mLockTaskController = null; + mLifecycleManager = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2795,6 +2805,7 @@ public class ActivityManagerService extends IActivityManager.Stub mRecentTasks = createRecentTasks(); mStackSupervisor.setRecentTasks(mRecentTasks); mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler); + mLifecycleManager = new ClientLifecycleManager(); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -5128,7 +5139,8 @@ public class ActivityManagerService extends IActivityManager.Stub // because we don't support returning them across task boundaries. Also, to // keep backwards compatibility we remove the task from recents when finishing // task with root activity. - res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity); + res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, + finishWithRootActivity, "finish-activity"); if (!res) { Slog.i(TAG, "Removing task failed to finish activity"); } @@ -8614,6 +8626,16 @@ public class ActivityManagerService extends IActivityManager.Stub } switch (appop) { case AppOpsManager.MODE_ALLOWED: + // If force-background-check is enabled, restrict all apps that aren't whitelisted. + if (mForceBackgroundCheck && + UserHandle.isApp(uid) && + !isOnDeviceIdleWhitelistLocked(uid)) { + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Force background check: " + + uid + "/" + packageName + " restricted"); + } + return ActivityManager.APP_START_MODE_DELAYED; + } return ActivityManager.APP_START_MODE_NORMAL; case AppOpsManager.MODE_IGNORED: return ActivityManager.APP_START_MODE_DELAYED; @@ -8713,6 +8735,9 @@ public class ActivityManagerService extends IActivityManager.Stub return ActivityManager.APP_START_MODE_NORMAL; } + /** + * @return whether a UID is in the system, user or temp doze whitelist. + */ boolean isOnDeviceIdleWhitelistLocked(int uid) { final int appId = UserHandle.getAppId(uid); return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0 @@ -10306,7 +10331,8 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { final long ident = Binder.clearCallingIdentity(); try { - return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS); + return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS, + "remove-task"); } finally { Binder.restoreCallingIdentity(ident); } @@ -14169,6 +14195,16 @@ public class ActivityManagerService extends IActivityManager.Stub readGrantedUriPermissionsLocked(); } + final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class); + if (pmi != null) { + pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK, + state -> updateForceBackgroundCheck(state.batterySaverEnabled)); + updateForceBackgroundCheck( + pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled); + } else { + Slog.wtf(TAG, "PowerManagerInternal not found."); + } + if (goingCallback != null) goingCallback.run(); traceLog.traceBegin("ActivityManagerStartApps"); mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START, @@ -14267,6 +14303,23 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void updateForceBackgroundCheck(boolean enabled) { + synchronized (this) { + if (mForceBackgroundCheck != enabled) { + mForceBackgroundCheck = enabled; + + if (DEBUG_BACKGROUND_CHECK) { + Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled")); + } + + if (mForceBackgroundCheck) { + // Stop background services for idle UIDs. + doStopUidForIdleUidsLocked(); + } + } + } + } + void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) { synchronized (this) { mAppErrors.killAppAtUserRequestLocked(app, fromDialog); @@ -15618,7 +15671,6 @@ public class ActivityManagerService extends IActivityManager.Stub void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, int dumpAppId) { boolean needSep = false; - boolean printedAnything = false; int numPers = 0; pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)"); @@ -15636,7 +15688,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (!needSep) { pw.println(" All known processes:"); needSep = true; - printedAnything = true; } pw.print(r.persistent ? " *PERS*" : " *APP*"); pw.print(" UID "); pw.print(procs.keyAt(ia)); @@ -15661,7 +15712,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } pw.println(" Isolated process list (sorted by uid):"); - printedAnything = true; printed = true; needSep = true; } @@ -15683,7 +15733,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } pw.println(" Active instrumentation:"); - printedAnything = true; printed = true; needSep = true; } @@ -15695,14 +15744,14 @@ public class ActivityManagerService extends IActivityManager.Stub if (mActiveUids.size() > 0) { if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) { - printedAnything = needSep = true; + needSep = true; } } if (dumpAll) { if (mValidateUids.size() > 0) { if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:", needSep)) { - printedAnything = needSep = true; + needSep = true; } } } @@ -15719,7 +15768,6 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println("):"); dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage); needSep = true; - printedAnything = true; } if (dumpAll || dumpPackage != null) { @@ -15735,7 +15783,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = true; pw.println(" PID mappings:"); printed = true; - printedAnything = true; } pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i)); pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); @@ -15758,7 +15805,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = true; pw.println(" Foreground Processes:"); printed = true; - printedAnything = true; } pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i)); pw.print(": "); pw.println(mImportantProcesses.valueAt(i)); @@ -15769,7 +15815,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mPersistentStartingProcesses.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Persisent processes that are starting:"); dumpProcessList(pw, this, mPersistentStartingProcesses, " ", "Starting Norm", "Restarting PERS", dumpPackage); @@ -15778,7 +15823,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mRemovedProcesses.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Processes that are being removed:"); dumpProcessList(pw, this, mRemovedProcesses, " ", "Removed Norm", "Removed PERS", dumpPackage); @@ -15787,7 +15831,6 @@ public class ActivityManagerService extends IActivityManager.Stub if (mProcessesOnHold.size() > 0) { if (needSep) pw.println(); needSep = true; - printedAnything = true; pw.println(" Processes that are on old until the system is ready:"); dumpProcessList(pw, this, mProcessesOnHold, " ", "OnHold Norm", "OnHold PERS", dumpPackage); @@ -15796,9 +15839,6 @@ public class ActivityManagerService extends IActivityManager.Stub needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage); needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage); - if (needSep) { - printedAnything = true; - } if (dumpPackage == null) { pw.println(); @@ -16070,10 +16110,7 @@ public class ActivityManagerService extends IActivityManager.Stub pw.println(); } } - - if (!printedAnything) { - pw.println(" (nothing)"); - } + pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck); } boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args, @@ -20562,9 +20599,11 @@ public class ActivityManagerService extends IActivityManager.Stub if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc " + app.processName + " new config " + configCopy); - app.thread.scheduleConfigurationChanged(configCopy); + mLifecycleManager.scheduleTransaction(app.thread, + new ConfigurationChangeItem(configCopy)); } } catch (Exception e) { + Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e); } } @@ -23332,6 +23371,24 @@ public class ActivityManagerService extends IActivityManager.Stub } } + /** + * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs. + */ + void doStopUidForIdleUidsLocked() { + final int size = mActiveUids.size(); + for (int i = 0; i < size; i++) { + final int uid = mActiveUids.keyAt(i); + if (!UserHandle.isApp(uid)) { + continue; + } + final UidRecord uidRec = mActiveUids.valueAt(i); + if (!uidRec.idle) { + continue; + } + doStopUidLocked(uidRec.uid, uidRec); + } + } + final void doStopUidLocked(int uid, final UidRecord uidRec) { mServices.stopInBackgroundLocked(uid); enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE); @@ -24330,6 +24387,11 @@ public class ActivityManagerService extends IActivityManager.Stub } } } + + @Override + public boolean isRuntimeRestarted() { + return mSystemServiceManager.isRuntimeRestarted(); + } } /** diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 11590d6ca83b..883019ea7b10 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -131,6 +131,12 @@ import android.app.ActivityOptions; import android.app.PendingIntent; import android.app.PictureInPictureParams; import android.app.ResultInfo; +import android.app.servertransaction.MoveToDisplayItem; +import android.app.servertransaction.MultiWindowModeChangeItem; +import android.app.servertransaction.NewIntentItem; +import android.app.servertransaction.PipModeChangeItem; +import android.app.servertransaction.WindowVisibilityItem; +import android.app.servertransaction.ActivityConfigurationChangeItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -611,8 +617,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo "Reporting activity moved to display" + ", activityRecord=" + this + ", displayId=" + displayId + ", config=" + config); - app.thread.scheduleActivityMovedToDisplay(appToken, displayId, - new Configuration(config)); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new MoveToDisplayItem(displayId, config)); } catch (RemoteException e) { // If process died, whatever. } @@ -629,7 +635,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: " + config); - app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config)); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new ActivityConfigurationChangeItem(config)); } catch (RemoteException e) { // If process died, whatever. } @@ -650,8 +657,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void scheduleMultiWindowModeChanged(Configuration overrideConfig) { try { - app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode, - overrideConfig); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new MultiWindowModeChangeItem(mLastReportedMultiWindowMode, + overrideConfig)); } catch (Exception e) { // If process died, I don't care. } @@ -677,8 +685,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private void schedulePictureInPictureModeChanged(Configuration overrideConfig) { try { - app.thread.schedulePictureInPictureModeChanged(appToken, - mLastReportedPictureInPictureMode, overrideConfig); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new PipModeChangeItem(mLastReportedPictureInPictureMode, + overrideConfig)); } catch (Exception e) { // If process died, no one cares. } @@ -1365,8 +1374,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo try { ArrayList<ReferrerIntent> ar = new ArrayList<>(1); ar.add(rintent); - app.thread.scheduleNewIntent( - ar, appToken, state == PAUSED /* andPause */); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new NewIntentItem(ar, state == PAUSED)); unsent = false; } catch (RemoteException e) { Slog.w(TAG, "Exception thrown sending new intent to " + this, e); @@ -1588,7 +1597,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo setVisible(true); sleeping = false; app.pendingUiClean = true; - app.thread.scheduleWindowVisibility(appToken, true /* showWindow */); + service.mLifecycleManager.scheduleTransaction(app.thread, appToken, + new WindowVisibilityItem(true /* showWindow */)); // The activity may be waiting for stop, but that is no longer appropriate for it. mStackSupervisor.mStoppingActivities.remove(this); mStackSupervisor.mGoingToSleepActivities.remove(this); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 481699833e41..3cf22833c101 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -104,6 +104,13 @@ import android.app.IActivityController; import android.app.ResultInfo; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; +import android.app.servertransaction.ActivityResultItem; +import android.app.servertransaction.NewIntentItem; +import android.app.servertransaction.WindowVisibilityItem; +import android.app.servertransaction.DestroyActivityItem; +import android.app.servertransaction.PauseActivityItem; +import android.app.servertransaction.ResumeActivityItem; +import android.app.servertransaction.StopActivityItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -1419,8 +1426,10 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai prev.userId, System.identityHashCode(prev), prev.shortComponentName); mService.updateUsageStats(prev, false); - prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, - userLeaving, prev.configChangeFlags, pauseImmediately); + + mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken, + new PauseActivityItem(prev.finishing, userLeaving, + prev.configChangeFlags, pauseImmediately)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -2055,7 +2064,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (r.app != null && r.app.thread != null) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r.appToken, false); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new WindowVisibilityItem(false /* showWindow */)); } // Reset the flag indicating that an app can enter picture-in-picture once the @@ -2575,13 +2585,15 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai if (!next.finishing && N > 0) { if (DEBUG_RESULTS) Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a); - next.app.thread.scheduleSendResult(next.appToken, a); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, + next.appToken, new ActivityResultItem(a)); } } if (next.newIntents != null) { - next.app.thread.scheduleNewIntent( - next.newIntents, next.appToken, false /* andPause */); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, + next.appToken, new NewIntentItem(next.newIntents, + false /* andPause */)); } // Well the app will no longer be stopped. @@ -2598,8 +2610,9 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.app.pendingUiClean = true; next.app.forceProcessStateUpTo(mService.mTopProcessState); next.clearOptionsLocked(); - next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState, - mService.isNextTransitionForward(), resumeAnimOptions); + mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken, + new ResumeActivityItem(next.app.repProcState, + mService.isNextTransitionForward())); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next); @@ -3249,7 +3262,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); - r.app.thread.scheduleSendResult(r.appToken, list); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new ActivityResultItem(list)); return; } catch (Exception e) { Slog.w(TAG, "Exception thrown sending result to " + r, e); @@ -3377,7 +3391,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai } EventLogTags.writeAmStopActivity( r.userId, System.identityHashCode(r), r.shortComponentName); - r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new StopActivityItem(r.visible, r.configChangeFlags)); if (shouldSleepOrShutDownActivities()) { r.setSleeping(true); } @@ -4009,7 +4024,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai // TODO: If the callers to removeTask() changes such that we have multiple places // where we are destroying the task, move this back into removeTask() mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, - !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY); + !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); } // We must keep the task around until all activities are destroyed. The following @@ -4174,8 +4189,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); - r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, - r.configChangeFlags); + mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken, + new DestroyActivityItem(r.finishing, r.configChangeFlags)); } catch (Exception e) { // We can just ignore exceptions here... if the process // has crashed, our death notification will clean things @@ -4988,13 +5003,22 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, boolean toTop) { + return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop, + null /*activity*/, null /*source*/, null /*options*/); + } + + TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, + IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, + boolean toTop, ActivityRecord activity, ActivityRecord source, + ActivityOptions options) { final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession, voiceInteractor); // add the task to stack first, mTaskPositioner might need the stack association addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController() .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); - if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout) + if (!mStackSupervisor.getLaunchingBoundsController() + .layoutTask(task, info.windowLayout, activity, source, options) && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) { task.updateOverrideConfiguration(getOverrideBounds()); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 5525cdb380b7..52ee67e628e3 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -114,6 +114,7 @@ import android.app.ResultInfo; import android.app.WaitResult; import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; +import android.app.servertransaction.LaunchActivityItem; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1392,7 +1393,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D r.setLastReportedConfiguration(mergedConfiguration); logIfTransactionTooLarge(r.intent, r.icicle); - app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, + mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken, + new LaunchActivityItem(new Intent(r.intent), System.identityHashCode(r), r.info, // TODO: Have this take the merged configuration instead of separate global // and override configs. @@ -1400,7 +1402,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mergedConfiguration.getOverrideConfiguration(), r.compat, r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results, newIntents, !andResume, - mService.isNextTransitionForward(), profilerInfo); + mService.isNextTransitionForward(), profilerInfo)); if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { // This may be a heavy-weight process! Note that the package @@ -2737,7 +2739,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } else { for (int i = tasks.size() - 1; i >= 0; i--) { removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */, - REMOVE_FROM_RECENTS); + REMOVE_FROM_RECENTS, "remove-stack"); } } } @@ -2770,8 +2772,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D /** * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)} */ - boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) { - return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY); + boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents, + String reason) { + return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY, + reason); } /** @@ -2785,10 +2789,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D * @return Returns true if the given task was found and removed. */ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents, - boolean pauseImmediately) { + boolean pauseImmediately, String reason) { final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS); if (tr != null) { - tr.removeTaskActivitiesLocked(pauseImmediately); + tr.removeTaskActivitiesLocked(pauseImmediately, reason); cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents); mService.mLockTaskController.clearLockedTask(tr); if (tr.isPersistable) { @@ -2922,7 +2926,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D @Override public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) { - // TODO: Trim active task once b/68045330 is fixed + if (wasTrimmed) { + // Task was trimmed from the recent tasks list -- remove the active task record as well + // since the user won't really be able to go back to it + removeTaskByIdLocked(task.taskId, false /* killProcess */, + false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed"); + } task.removedFromRecents(); } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 03162bbe0199..6010422bf8b8 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1756,7 +1756,8 @@ class ActivityStarter { mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info, mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession, - mVoiceInteractor, !mLaunchTaskBehind /* toTop */); + mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord, + mOptions); addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask"); updateBounds(mStartActivity.getTask(), mLaunchBounds); @@ -1965,7 +1966,7 @@ class ActivityStarter { final ActivityRecord prev = mTargetStack.getTopActivity(); final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord( mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info, - mIntent, null, null, true); + mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions); addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask"); mTargetStack.positionChildWindowContainerAtTop(task); if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java index 17626ea166af..ab86dbdbd106 100644 --- a/services/core/java/com/android/server/am/AppTaskImpl.java +++ b/services/core/java/com/android/server/am/AppTaskImpl.java @@ -61,7 +61,7 @@ class AppTaskImpl extends IAppTask.Stub { try { // We remove the task from recents to preserve backwards if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false, - REMOVE_FROM_RECENTS)) { + REMOVE_FROM_RECENTS, "finish-and-remove-task")) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } } finally { diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java new file mode 100644 index 000000000000..c04d103c7ff5 --- /dev/null +++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2017 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.annotation.NonNull; +import android.app.IApplicationThread; +import android.app.servertransaction.ClientTransaction; +import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.ActivityLifecycleItem; +import android.os.IBinder; +import android.os.RemoteException; + +/** + * Class that is able to combine multiple client lifecycle transition requests and/or callbacks, + * and execute them as a single transaction. + * + * @see ClientTransaction + */ +class ClientLifecycleManager { + // TODO(lifecycler): Implement building transactions or global transaction. + // TODO(lifecycler): Use object pools for transactions and transaction items. + + /** + * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request. + * @param transaction A sequence of client transaction items. + * @throws RemoteException + * + * @see ClientTransaction + */ + void scheduleTransaction(ClientTransaction transaction) throws RemoteException { + transaction.schedule(); + } + + /** + * Schedule a single lifecycle request or callback to client activity. + * @param client Target client. + * @param activityToken Target activity token. + * @param stateRequest A request to move target activity to a desired lifecycle state. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, + @NonNull ActivityLifecycleItem stateRequest) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithState(client, activityToken, + stateRequest); + scheduleTransaction(clientTransaction); + } + + /** + * Schedule a single callback delivery to client activity. + * @param client Target client. + * @param activityToken Target activity token. + * @param callback A request to deliver a callback. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken, + @NonNull ClientTransactionItem callback) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken, + callback); + scheduleTransaction(clientTransaction); + } + + /** + * Schedule a single callback delivery to client application. + * @param client Target client. + * @param callback A request to deliver a callback. + * @throws RemoteException + * + * @see ClientTransactionItem + */ + void scheduleTransaction(@NonNull IApplicationThread client, + @NonNull ClientTransactionItem callback) throws RemoteException { + final ClientTransaction clientTransaction = transactionWithCallback(client, + null /* activityToken */, callback); + scheduleTransaction(clientTransaction); + } + + /** + * @return A new instance of {@link ClientTransaction} with a single lifecycle state request. + * + * @see ClientTransaction + * @see ClientTransactionItem + */ + private static ClientTransaction transactionWithState(@NonNull IApplicationThread client, + @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) { + final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken); + clientTransaction.setLifecycleStateRequest(stateRequest); + return clientTransaction; + } + + /** + * @return A new instance of {@link ClientTransaction} with a single callback invocation. + * + * @see ClientTransaction + * @see ClientTransactionItem + */ + private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client, + IBinder activityToken, @NonNull ClientTransactionItem callback) { + final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken); + clientTransaction.addCallback(callback); + return clientTransaction; + } +} diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java index 82019fde30b7..65c4a42108ec 100644 --- a/services/core/java/com/android/server/am/CompatModePackages.java +++ b/services/core/java/com/android/server/am/CompatModePackages.java @@ -58,10 +58,6 @@ public final class CompatModePackages { public static final int COMPAT_FLAG_DONT_ASK = 1<<0; // Compatibility state: compatibility mode is enabled. public static final int COMPAT_FLAG_ENABLED = 1<<1; - // Unsupported zoom state: don't warn the user about unsupported zoom mode. - public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2; - // Unsupported compile SDK state: don't warn the user about unsupported compile SDK. - public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3; private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>(); @@ -235,14 +231,6 @@ public final class CompatModePackages { return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0; } - public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) { - return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0; - } - - public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) { - return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0; - } - public void setFrontActivityAskCompatModeLocked(boolean ask) { ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(); if (r != null) { @@ -254,14 +242,6 @@ public final class CompatModePackages { setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask); } - public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) { - setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify); - } - - public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) { - setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify); - } - private void setPackageFlagLocked(String packageName, int flag, boolean set) { final int curFlags = getPackageFlags(packageName); final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag); diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java index c762f7f4a507..5aa7f58f0968 100644 --- a/services/core/java/com/android/server/am/LaunchingBoundsController.java +++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java @@ -101,8 +101,12 @@ class LaunchingBoundsController { * @return {@code true} if bounds were set on the task. {@code false} otherwise. */ boolean layoutTask(TaskRecord task, WindowLayout layout) { - calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/, - mTmpRect); + return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/); + } + + boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityRecord source, ActivityOptions options) { + calculateBounds(task, layout, activity, source, options, mTmpRect); if (mTmpRect.isEmpty()) { return false; diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java index 6e6d7e9f2945..ebcf8c28079f 100644 --- a/services/core/java/com/android/server/am/RecentTasks.java +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -80,6 +80,20 @@ import java.util.concurrent.TimeUnit; /** * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the * least recent. + * + * The trimming logic can be boiled down to the following. For recent task list with a number of + * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to + * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a + * sub-range are presented to the user, based on the device type, last task active time, or other + * task state. Tasks that are not in the visible range and are not returnable from the SystemUI + * (considering the back stack) are considered trimmable. If the device does not support recent + * tasks, then trimming is completely disabled. + * + * eg. + * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks + * [VVV VV VVVV V V V ] // Visible tasks + * [RRR RR XXXX X X X ] // Visible range tasks, eg. if the device only shows 5 tasks, + * // 'X' tasks are trimmed. */ class RecentTasks { private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM; @@ -496,7 +510,8 @@ class RecentTasks { if (tr.userId != userId) return; if (!taskPackageName.equals(packageName)) return; - mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS); + mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS, + "remove-package-task"); } } @@ -513,7 +528,7 @@ class RecentTasks { && (filterByClasses == null || filterByClasses.contains(cn.getClassName())); if (sameComponent) { mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, - REMOVE_FROM_RECENTS); + REMOVE_FROM_RECENTS, "disabled-package"); } } } @@ -1001,12 +1016,13 @@ class RecentTasks { continue; } else { numVisibleTasks++; - if (isInVisibleRange(task, numVisibleTasks)) { + if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) { // Keep visible tasks in range i++; continue; } else { - // Fall through to trim visible tasks that are no longer in range + // Fall through to trim visible tasks that are no longer in range and + // trimmable if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG, "Trimming out-of-range visible task=" + task); } @@ -1122,6 +1138,28 @@ class RecentTasks { } /** + * @return whether the given task can be trimmed even if it is outside the visible range. + */ + protected boolean isTrimmable(TaskRecord task) { + final ActivityStack stack = task.getStack(); + final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack; + + // No stack for task, just trim it + if (stack == null) { + return true; + } + + // Ignore tasks from different displays + if (stack.getDisplay() != homeStack.getDisplay()) { + return false; + } + + // Trim tasks that are in stacks that are behind the home stack + final ActivityDisplay display = stack.getDisplay(); + return display.getIndexOf(stack) < display.getIndexOf(homeStack); + } + + /** * If needed, remove oldest existing entries in recents that are for the same kind * of task as the given one. */ diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 365990f15835..fd6d618bc62d 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1311,7 +1311,8 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi * Completely remove all activities associated with an existing * task starting at a specified index. */ - final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) { + final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately, + String reason) { int numActivities = mActivities.size(); for ( ; activityNdx < numActivities; ++activityNdx) { final ActivityRecord r = mActivities.get(activityNdx); @@ -1325,7 +1326,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi --activityNdx; --numActivities; } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, - "clear-task-index", false, pauseImmediately)) { + reason, false, pauseImmediately)) { --activityNdx; --numActivities; } @@ -1337,7 +1338,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi */ void performClearTaskLocked() { mReuseTask = true; - performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY); + performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all"); mReuseTask = false; } @@ -1408,9 +1409,9 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi return null; } - void removeTaskActivitiesLocked(boolean pauseImmediately) { + void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) { // Just remove the entire task. - performClearTaskAtIndexLocked(0, pauseImmediately); + performClearTaskAtIndexLocked(0, pauseImmediately, reason); } String lockTaskAuthToString() { diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java index 600589a40bb8..b6f6ae6b508c 100644 --- a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java +++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java @@ -39,7 +39,7 @@ public class UnsupportedCompileSdkDialog { final PackageManager pm = context.getPackageManager(); final CharSequence label = appInfo.loadSafeLabel(pm); final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message, - label, appInfo.compileSdkVersionCodename); + label); final AlertDialog.Builder builder = new AlertDialog.Builder(context) .setPositiveButton(R.string.ok, null) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 8a9b45b2c555..2d5f7c7124c4 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -8045,24 +8045,24 @@ public class PackageManagerService extends IPackageManager.Stub return finalList; } - private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]"); + private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]"); try { - scanDirLI(dir, parseFlags, scanFlags, currentTime); + scanDirLI(scanDir, parseFlags, scanFlags, currentTime); } finally { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } } - private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) { - final File[] files = dir.listFiles(); + private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) { + final File[] files = scanDir.listFiles(); if (ArrayUtils.isEmpty(files)) { - Log.d(TAG, "No files in app dir " + dir); + Log.d(TAG, "No files in app dir " + scanDir); return; } if (DEBUG_PACKAGE_SCANNING) { - Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags + Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags + " flags=0x" + Integer.toHexString(parseFlags)); } try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser( @@ -8094,7 +8094,7 @@ public class PackageManagerService extends IPackageManager.Stub } try { if (errorCode == PackageManager.INSTALL_SUCCEEDED) { - scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags, + scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags, currentTime, null); } } catch (PackageManagerException e) { @@ -8126,14 +8126,14 @@ public class PackageManagerService extends IPackageManager.Stub logCriticalInfo(priority, msg); } - private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile, + private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, final @ParseFlags int parseFlags) throws PackageManagerException { // When upgrading from pre-N MR1, verify the package time stamp using the package // directory and not the APK file. final long lastModifiedTime = mIsPreNMR1Upgrade - ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile); + ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg); if (ps != null - && ps.codePath.equals(srcFile) + && ps.codePathString.equals(pkg.codePath) && ps.timeStamp == lastModifiedTime && !isCompatSignatureUpdateNeeded(pkg) && !isRecoverSignatureUpdateNeeded(pkg)) { @@ -8156,7 +8156,7 @@ public class PackageManagerService extends IPackageManager.Stub Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures. Collecting certs again to recover them."); } else { - Slog.i(TAG, srcFile.toString() + " changed; collecting certs"); + Slog.i(TAG, toString() + " changed; collecting certs"); } try { @@ -8211,14 +8211,14 @@ public class PackageManagerService extends IPackageManager.Stub renameStaticSharedLibraryPackage(pkg); } - return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user); + return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user); } /** * Scans a package and returns the newly parsed package. * @throws PackageManagerException on a parse error. */ - private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile, + private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg, final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { @@ -8236,20 +8236,20 @@ public class PackageManagerService extends IPackageManager.Stub } // Scan the parent - PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, parseFlags, + PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags, scanFlags, currentTime, user); // Scan the children final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0; for (int i = 0; i < childCount; i++) { PackageParser.Package childPackage = pkg.childPackages.get(i); - scanPackageInternalLI(childPackage, scanFile, parseFlags, scanFlags, + scanPackageInternalLI(childPackage, parseFlags, scanFlags, currentTime, user); } if ((scanFlags & SCAN_CHECK_ONLY) != 0) { - return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user); + return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user); } return scannedPkg; @@ -8259,12 +8259,12 @@ public class PackageManagerService extends IPackageManager.Stub * Scans a package and returns the newly parsed package. * @throws PackageManagerException on a parse error. */ - private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile, + private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime, @Nullable UserHandle user) throws PackageManagerException { PackageSetting ps = null; - PackageSetting updatedPkg; + PackageSetting updatedPs; // reader synchronized (mPackages) { // Look to see if we already know about this package. @@ -8281,8 +8281,8 @@ public class PackageManagerService extends IPackageManager.Stub // Check to see if this package could be hiding/updating a system // package. Must look for it either under the original or real // package name depending on our state. - updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName); - if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg); + updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName); + if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs); // If this is a package we don't know about on the system partition, we // may need to remove disabled child packages on the system partition @@ -8316,27 +8316,27 @@ public class PackageManagerService extends IPackageManager.Stub } } - final boolean isUpdatedPkg = updatedPkg != null; + final boolean isUpdatedPkg = updatedPs != null; final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0; boolean isUpdatedPkgBetter = false; // First check if this is a system package that may involve an update if (isUpdatedSystemPkg) { // If new package is not located in "/system/priv-app" (e.g. due to an OTA), // it needs to drop FLAG_PRIVILEGED. - if (locationIsPrivileged(scanFile)) { - updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + if (locationIsPrivileged(pkg.codePath)) { + updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } else { - updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; + updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } // If new package is not located in "/oem" (e.g. due to an OTA), // it needs to drop FLAG_OEM. - if (locationIsOem(scanFile)) { - updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM; + if (locationIsOem(pkg.codePath)) { + updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM; } else { - updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM; + updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM; } - if (ps != null && !ps.codePath.equals(scanFile)) { + if (ps != null && !ps.codePathString.equals(pkg.codePath)) { // The path has changed from what was last scanned... check the // version of the new path against what we have stored to determine // what to do. @@ -8344,26 +8344,27 @@ public class PackageManagerService extends IPackageManager.Stub if (pkg.mVersionCode <= ps.versionCode) { // The system package has been updated and the code path does not match // Ignore entry. Skip it. - if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile + if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); - if (!updatedPkg.codePath.equals(scanFile)) { + if (!updatedPs.codePathString.equals(pkg.codePath)) { Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg " - + ps.name + " changing from " + updatedPkg.codePathString - + " to " + scanFile); - updatedPkg.codePath = scanFile; - updatedPkg.codePathString = scanFile.toString(); - updatedPkg.resourcePath = scanFile; - updatedPkg.resourcePathString = scanFile.toString(); + + ps.name + " changing from " + updatedPs.codePathString + + " to " + pkg.codePath); + final File codePath = new File(pkg.codePath); + updatedPs.codePath = codePath; + updatedPs.codePathString = pkg.codePath; + updatedPs.resourcePath = codePath; + updatedPs.resourcePathString = pkg.codePath; } - updatedPkg.pkg = pkg; - updatedPkg.versionCode = pkg.mVersionCode; + updatedPs.pkg = pkg; + updatedPs.versionCode = pkg.mVersionCode; // Update the disabled system child packages to point to the package too. - final int childCount = updatedPkg.childPackageNames != null - ? updatedPkg.childPackageNames.size() : 0; + final int childCount = updatedPs.childPackageNames != null + ? updatedPs.childPackageNames.size() : 0; for (int i = 0; i < childCount; i++) { - String childPackageName = updatedPkg.childPackageNames.get(i); + String childPackageName = updatedPs.childPackageNames.get(i); PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr( childPackageName); if (updatedChildPkg != null) { @@ -8384,7 +8385,7 @@ public class PackageManagerService extends IPackageManager.Stub mPackages.remove(ps.name); } - logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile + logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath + " reverting from " + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); @@ -8431,16 +8432,16 @@ public class PackageManagerService extends IPackageManager.Stub if (isUpdatedSystemPkg && !isUpdatedPkgBetter) { // Set CPU Abis to application info. if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { - final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg); - derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir); + final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs); + derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir); } else { - pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString; - pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString; + pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString; + pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString; } - pkg.mExtras = updatedPkg; + pkg.mExtras = updatedPs; throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at " - + scanFile + " ignored: updated version " + ps.versionCode + + pkg.codePath + " ignored: updated version " + ps.versionCode + " better than this " + pkg.mVersionCode); } @@ -8450,19 +8451,19 @@ public class PackageManagerService extends IPackageManager.Stub // An updated privileged application will not have the PARSE_IS_PRIVILEGED // flag set initially - if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { + if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) { scanFlags |= SCAN_AS_PRIVILEGED; } // An updated OEM app will not have the PARSE_IS_OEM // flag set initially - if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) { + if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) { scanFlags |= SCAN_AS_OEM; } } // Verify certificates against what was last scanned - collectCertificatesLI(ps, pkg, scanFile, parseFlags); + collectCertificatesLI(ps, pkg, parseFlags); /* * A new system app appeared, but we already had a non-system one of the @@ -8492,7 +8493,7 @@ public class PackageManagerService extends IPackageManager.Stub */ if (pkg.mVersionCode <= ps.versionCode) { shouldHideSystemApp = true; - logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile + logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath + " but new version " + pkg.mVersionCode + " better than installed " + ps.versionCode + "; hiding system"); } else { @@ -8502,7 +8503,7 @@ public class PackageManagerService extends IPackageManager.Stub * already-installed application and replace it with our own * while keeping the application data. */ - logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile + logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath + " reverting from " + ps.codePathString + ": new version " + pkg.mVersionCode + " better than installed " + ps.versionCode); InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps), @@ -9976,8 +9977,7 @@ public class PackageManagerService extends IPackageManager.Stub if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi"); final boolean extractNativeLibs = !pkg.isLibrary(); - derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs, - mAppLib32InstallDir); + derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir); Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); // Some system apps still use directory structure for native libraries @@ -10084,7 +10084,7 @@ public class PackageManagerService extends IPackageManager.Stub } // Take care of first install / last update times. - final long scanFileTime = getLastModifiedTime(pkg, scanFile); + final long scanFileTime = getLastModifiedTime(pkg); if (currentTime != 0) { if (pkgSetting.firstInstallTime == 0) { pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime; @@ -10864,10 +10864,9 @@ public class PackageManagerService extends IPackageManager.Stub * * If {@code extractLibs} is true, native libraries are extracted from the app if required. */ - private static void derivePackageAbi(PackageParser.Package pkg, File scanFile, - String cpuAbiOverride, boolean extractLibs, - File appLib32InstallDir) - throws PackageManagerException { + private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride, + boolean extractLibs, File appLib32InstallDir) + throws PackageManagerException { // Give ourselves some initial paths; we'll come back for another // pass once we've determined ABI below. setNativeLibraryPaths(pkg, appLib32InstallDir); @@ -15000,7 +14999,12 @@ public class PackageManagerService extends IPackageManager.Stub resourceFile = afterCodeFile; // Reflect the rename in scanned details - pkg.setCodePath(afterCodeFile.getAbsolutePath()); + try { + pkg.setCodePath(afterCodeFile.getCanonicalPath()); + } catch (IOException e) { + Slog.e(TAG, "Failed to get path: " + afterCodeFile, e); + return false; + } pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile, afterCodeFile, pkg.baseCodePath)); pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile, @@ -16482,8 +16486,7 @@ public class PackageManagerService extends IPackageManager.Stub String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ? args.abiOverride : pkg.cpuAbiOverride); final boolean extractNativeLibs = !pkg.isLibrary(); - derivePackageAbi(pkg, new File(pkg.codePath), abiOverride, - extractNativeLibs, mAppLib32InstallDir); + derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir); } catch (PackageManagerException pme) { Slog.e(TAG, "Error deriving application ABI", pme); res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI"); @@ -17487,21 +17490,19 @@ public class PackageManagerService extends IPackageManager.Stub } } - static boolean locationIsPrivileged(File path) { + static boolean locationIsPrivileged(String path) { try { - final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app") - .getCanonicalPath(); - return path.getCanonicalPath().startsWith(privilegedAppDir); + final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); + return path.startsWith(privilegedAppDir.getCanonicalPath()); } catch (IOException e) { Slog.e(TAG, "Unable to access code path " + path); } return false; } - static boolean locationIsOem(File path) { + static boolean locationIsOem(String path) { try { - return path.getCanonicalPath().startsWith( - Environment.getOemDirectory().getCanonicalPath()); + return path.startsWith(Environment.getOemDirectory().getCanonicalPath()); } catch (IOException e) { Slog.e(TAG, "Unable to access code path " + path); } @@ -17597,7 +17598,7 @@ public class PackageManagerService extends IPackageManager.Stub // Install the system package if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); try { - installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles, + installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles, outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings); } catch (PackageManagerException e) { Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": " @@ -17614,7 +17615,7 @@ public class PackageManagerService extends IPackageManager.Stub /** * Installs a package that's already on the system partition. */ - private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath, + private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString, boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles, @Nullable PermissionsState origPermissionState, boolean writeSettings) throws PackageManagerException { @@ -17623,13 +17624,14 @@ public class PackageManagerService extends IPackageManager.Stub | PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM_DIR; @ScanFlags int scanFlags = SCAN_AS_SYSTEM; - if (isPrivileged || locationIsPrivileged(codePath)) { + if (isPrivileged || locationIsPrivileged(codePathString)) { scanFlags |= SCAN_AS_PRIVILEGED; } - if (locationIsOem(codePath)) { + if (locationIsOem(codePathString)) { scanFlags |= SCAN_AS_OEM; } + final File codePath = new File(codePathString); final PackageParser.Package pkg = scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null); @@ -19741,7 +19743,7 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); // until we can disable the package later. enableSystemPackageLPw(deletedPkg); } - installPackageFromSystemLIF(new File(deletedPkg.codePath), + installPackageFromSystemLIF(deletedPkg.codePath, false /*isPrivileged*/, null /*allUserHandles*/, null /*origUserHandles*/, null /*origPermissionsState*/, true /*writeSettings*/); diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 758abd76950b..fb6278a43950 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -295,19 +295,20 @@ public class PackageManagerServiceUtils { return false; } - public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) { - if (srcFile.isDirectory()) { - final File baseFile = new File(pkg.baseCodePath); - long maxModifiedTime = baseFile.lastModified(); - if (pkg.splitCodePaths != null) { - for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { - final File splitFile = new File(pkg.splitCodePaths[i]); - maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); - } + public static long getLastModifiedTime(PackageParser.Package pkg) { + final File srcFile = new File(pkg.codePath); + if (!srcFile.isDirectory()) { + return srcFile.lastModified(); + } + final File baseFile = new File(pkg.baseCodePath); + long maxModifiedTime = baseFile.lastModified(); + if (pkg.splitCodePaths != null) { + for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) { + final File splitFile = new File(pkg.splitCodePaths[i]); + maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified()); } - return maxModifiedTime; } - return srcFile.lastModified(); + return maxModifiedTime; } /** diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 7077fd166987..ddad6774871d 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -3572,11 +3572,10 @@ public final class Settings { int pkgFlags = 0; int pkgPrivateFlags = 0; pkgFlags |= ApplicationInfo.FLAG_SYSTEM; - final File codePathFile = new File(codePathStr); - if (PackageManagerService.locationIsPrivileged(codePathFile)) { + if (PackageManagerService.locationIsPrivileged(codePathStr)) { pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED; } - PackageSetting ps = new PackageSetting(name, realName, codePathFile, + PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr, secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ddd3bbdf561b..7415ec3881b1 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -240,6 +240,7 @@ import android.view.inputmethod.InputMethodManagerInternal; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IShortcutService; @@ -1053,7 +1054,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { private ImmersiveModeConfirmation mImmersiveModeConfirmation; - private SystemGesturesPointerEventListener mSystemGestures; + @VisibleForTesting + SystemGesturesPointerEventListener mSystemGestures; IStatusBarService getStatusBarService() { synchronized (mServiceAquireLock) { @@ -2664,17 +2666,21 @@ public class PhoneWindowManager implements WindowManagerPolicy { // The status bar is the only window allowed to exhibit keyguard behavior. attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD; } + } + private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) { + int impliedFlags = 0; if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) { - attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } final boolean forceWindowDrawsStatusBarBackground = (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0; if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 || forceWindowDrawsStatusBarBackground && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) { - attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; } + return impliedFlags; } void readLidState() { @@ -2724,7 +2730,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onConfigurationChanged() { // TODO(multi-display): Define policy for secondary displays. - Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext(); + Context uiContext = getSystemUiContext(); final Resources res = uiContext.getResources(); mStatusBarHeight = @@ -2765,6 +2771,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @VisibleForTesting + Context getSystemUiContext() { + return ActivityThread.currentActivityThread().getSystemUiContext(); + } + @Override public int getMaxWallpaperLayer() { return getWindowLayerFromTypeLw(TYPE_STATUS_BAR); @@ -4825,7 +4836,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int fl = PolicyControl.getWindowFlags(win, attrs); final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; - final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null); + final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null); + final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs); final Rect pf = mTmpParentFrame; final Rect df = mTmpDisplayFrame; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 8d56eb80181a..5f067d496e9a 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -213,7 +213,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { */ public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame, - Rect stableFrame, Rect outsetFrame); + Rect stableFrame, @Nullable Rect outsetFrame); /** * Retrieve the current frame of the window that has been assigned by @@ -808,11 +808,11 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. return 15; - case TYPE_STATUS_BAR_SUB_PANEL: - return 17; case TYPE_STATUS_BAR: - return 18; + return 17; case TYPE_STATUS_BAR_PANEL: + return 18; + case TYPE_STATUS_BAR_SUB_PANEL: return 19; case TYPE_KEYGUARD_DIALOG: return 20; diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java index 87c92744cdab..7c234f96ee13 100644 --- a/services/core/java/com/android/server/power/BatterySaverPolicy.java +++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java @@ -69,6 +69,7 @@ public class BatterySaverPolicy extends ContentObserver { private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred"; private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred"; private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby"; + private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check"; private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled"; private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i"; @@ -190,6 +191,12 @@ public class BatterySaverPolicy extends ContentObserver { private boolean mForceAllAppsStandby; /** + * Whether to put all apps in the stand-by mode. + */ + @GuardedBy("mLock") + private boolean mForceBackgroundCheck; + + /** * Weather to show non-essential sensors (e.g. edge sensors) or not. */ @GuardedBy("mLock") @@ -326,6 +333,7 @@ public class BatterySaverPolicy extends ContentObserver { mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true); mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true); mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true); + mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true); mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true); // Get default value from Settings.Secure @@ -395,6 +403,12 @@ public class BatterySaverPolicy extends ContentObserver { case ServiceType.VIBRATION: return builder.setBatterySaverEnabled(mVibrationDisabled) .build(); + case ServiceType.FORCE_ALL_APPS_STANDBY: + return builder.setBatterySaverEnabled(mForceAllAppsStandby) + .build(); + case ServiceType.FORCE_BACKGROUND_CHECK: + return builder.setBatterySaverEnabled(mForceBackgroundCheck) + .build(); case ServiceType.OPTIONAL_SENSORS: return builder.setBatterySaverEnabled(mOptionalSensorsDisabled) .build(); @@ -438,6 +452,7 @@ public class BatterySaverPolicy extends ContentObserver { pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor); pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode); pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby); + pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck); pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled); pw.println(); diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java index 483d5181f9c9..80bc935959a2 100644 --- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java +++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java @@ -16,6 +16,7 @@ package com.android.server.power.batterysaver; import android.Manifest; +import android.app.ActivityManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -109,6 +110,9 @@ public class BatterySaverController implements BatterySaverPolicyListener { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); mContext.registerReceiver(mReceiver, filter); + + mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class) + .isRuntimeRestarted()); } private PowerManager getPowerManager() { diff --git a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java index cc1b540e669b..e0ab9e93a8a6 100644 --- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java +++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java @@ -16,19 +16,34 @@ package com.android.server.power.batterysaver; import android.content.Context; +import android.os.Environment; import android.os.Handler; import android.os.Looper; +import android.os.SystemProperties; import android.util.ArrayMap; +import android.util.AtomicFile; import android.util.Slog; +import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.XmlUtils; import com.android.server.IoThread; import libcore.io.IoUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Map; @@ -47,6 +62,14 @@ public class FileUpdater { private static final boolean DEBUG = BatterySaverController.DEBUG; + /** + * If this system property is set to 1, it'll skip all file writes. This can be used when + * one needs to change max CPU frequency for benchmarking, for example. + */ + private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files"; + + private static final String TAG_DEFAULT_ROOT = "defaults"; + // Don't do disk access with this lock held. private final Object mLock = new Object(); @@ -93,6 +116,21 @@ public class FileUpdater { RETRY_INTERVAL_MS = retryIntervalMs; } + public void systemReady(boolean runtimeRestarted) { + synchronized (mLock) { + if (runtimeRestarted) { + // If it runtime restarted, read the original values from the disk and apply. + if (loadDefaultValuesLocked()) { + Slog.d(TAG, "Default values loaded after runtime restart; writing them..."); + restoreDefault(); + } + } else { + // Delete it, without checking the result. (file-not-exist is not an exception.) + injectDefaultValuesFilename().delete(); + } + } + } + /** * Write values to files. (Note the actual writes happen ASAP but asynchronously.) */ @@ -241,6 +279,7 @@ public class FileUpdater { } synchronized (mLock) { mDefaultValues.put(file, originalValue); + saveDefaultValuesLocked(); } return true; } @@ -252,17 +291,92 @@ public class FileUpdater { @VisibleForTesting void injectWriteToFile(String file, String value) throws IOException { + if (injectShouldSkipWrite()) { + Slog.i(TAG, "Skipped writing to '" + file + "'"); + return; + } if (DEBUG) { Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'"); } try (FileWriter out = new FileWriter(file)) { out.write(value); - } catch (IOException e) { + } catch (IOException | RuntimeException e) { Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage()); throw e; } } + private void saveDefaultValuesLocked() { + final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); + + FileOutputStream outs = null; + try { + file.getBaseFile().getParentFile().mkdirs(); + outs = file.startWrite(); + + // Write to XML + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(outs, StandardCharsets.UTF_8.name()); + out.startDocument(null, true); + out.startTag(null, TAG_DEFAULT_ROOT); + + XmlUtils.writeMapXml(mDefaultValues, out, null); + + // Epilogue. + out.endTag(null, TAG_DEFAULT_ROOT); + out.endDocument(); + + // Close. + file.finishWrite(outs); + } catch (IOException | XmlPullParserException | RuntimeException e) { + Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e); + file.failWrite(outs); + } + } + + @VisibleForTesting + boolean loadDefaultValuesLocked() { + final AtomicFile file = new AtomicFile(injectDefaultValuesFilename()); + if (DEBUG) { + Slog.d(TAG, "Loading from " + file.getBaseFile()); + } + Map<String, String> read = null; + try (FileInputStream in = file.openRead()) { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, StandardCharsets.UTF_8.name()); + + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + // Check the root tag + final String tag = parser.getName(); + if (depth == 1) { + if (!TAG_DEFAULT_ROOT.equals(tag)) { + Slog.e(TAG, "Invalid root tag: " + tag); + return false; + } + continue; + } + final String[] tagName = new String[1]; + read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser, + TAG_DEFAULT_ROOT, tagName, null); + } + } catch (FileNotFoundException e) { + read = null; + } catch (IOException | XmlPullParserException | RuntimeException e) { + Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e); + } + if (read != null) { + mDefaultValues.clear(); + mDefaultValues.putAll(read); + return true; + } + return false; + } + private void doWtf(String message) { injectWtf(message, null); } @@ -271,4 +385,20 @@ public class FileUpdater { void injectWtf(String message, Throwable e) { Slog.wtf(TAG, message, e); } + + File injectDefaultValuesFilename() { + final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver"); + dir.mkdirs(); + return new File(dir, "default-values.xml"); + } + + @VisibleForTesting + boolean injectShouldSkipWrite() { + return SystemProperties.getBoolean(PROP_SKIP_WRITE, false); + } + + @VisibleForTesting + ArrayMap<String, String> getDefaultValuesForTest() { + return mDefaultValues; + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 409e04b94b04..41348ba4b041 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -181,8 +181,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final TaskStackContainers mTaskStackContainers = new TaskStackContainers(); // Contains all non-app window containers that should be displayed above the app containers // (e.g. Status bar) - private final NonAppWindowContainers mAboveAppWindowsContainers = - new NonAppWindowContainers("mAboveAppWindowsContainers"); + private final AboveAppWindowContainers mAboveAppWindowsContainers = + new AboveAppWindowContainers("mAboveAppWindowsContainers"); // Contains all non-app window containers that should be displayed below the app containers // (e.g. Wallpaper). private final NonAppWindowContainers mBelowAppWindowsContainers = @@ -356,7 +356,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** * We organize all top-level Surfaces in to the following layers. * mOverlayLayer contains a few Surfaces which are always on top of others - * and omitted from Screen-Magnification ({@link WindowState#isScreenOverlay}) + * and omitted from Screen-Magnification, for example the strict mode flash or + * the magnification overlay itself. * {@link #mWindowingLayer} contains everything else. */ private SurfaceControl mOverlayLayer; @@ -2968,227 +2969,30 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo * Takes a snapshot of the display. In landscape mode this grabs the whole screen. * In portrait mode, it grabs the full screenshot. * - * @param width the width of the target bitmap - * @param height the height of the target bitmap - * @param includeFullDisplay true if the screen should not be cropped before capture - * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1 * @param config of the output bitmap * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot - * @param includeDecor whether to include window decors, like the status or navigation bar - * background of the window */ - Bitmap screenshotApplications(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor) { - Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay, - frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot); - if (bitmap == null) { - return null; - } - - if (DEBUG_SCREENSHOT) { - // TEST IF IT's ALL BLACK - int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()]; - bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), - bitmap.getHeight()); - boolean allBlack = true; - final int firstColor = buffer[0]; - for (int i = 0; i < buffer.length; i++) { - if (buffer[i] != firstColor) { - allBlack = false; - break; - } - } - if (allBlack) { - final WindowState appWin = mScreenshotApplicationState.appWin; - final int maxLayer = mScreenshotApplicationState.maxLayer; - final int minLayer = mScreenshotApplicationState.minLayer; - Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" + - Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + - (appWin != null ? - appWin.mWinAnimator.mSurfaceController.getLayer() : "null") + - " minLayer=" + minLayer + " maxLayer=" + maxLayer); - } - } - - // Create a copy of the screenshot that is immutable and backed in ashmem. - // This greatly reduces the overhead of passing the bitmap between processes. - Bitmap ret = bitmap.createAshmemBitmap(config); - bitmap.recycle(); - return ret; - } - - GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, - boolean includeDecor) { - return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale, - wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer); - } - - private <E> E screenshotApplications(IBinder appToken, int width, int height, - boolean includeFullDisplay, float frameScale, boolean wallpaperOnly, - boolean includeDecor, Screenshoter<E> screenshoter) { - int dw = mDisplayInfo.logicalWidth; - int dh = mDisplayInfo.logicalHeight; - if (dw == 0 || dh == 0) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken - + ": returning null. logical widthxheight=" + dw + "x" + dh); - return null; - } - - E bitmap; - - mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly); - final Rect frame = new Rect(); - final Rect stackBounds = new Rect(); - - final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1) - * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET; - final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay); - synchronized(mService.mWindowMap) { + Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) { + synchronized (mService.mWindowMap) { if (!mService.mPolicy.isScreenOn()) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display" - + " was off."); - return null; - } - // Figure out the part of the screen that is actually the app. - mScreenshotApplicationState.appWin = null; - forAllWindows(w -> { - if (!w.mHasSurface) { - return false; - } - if (w.mLayer >= aboveAppLayer) { - return false; + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); } - if (wallpaperOnly && !w.mIsWallpaper) { - return false; - } - if (w.mIsImWindow) { - return false; - } else if (w.mIsWallpaper) { - // If this is the wallpaper layer and we're only looking for the wallpaper layer - // then the target window state is this one. - if (wallpaperOnly) { - mScreenshotApplicationState.appWin = w; - } - - if (mScreenshotApplicationState.appWin == null) { - // We have not ran across the target window yet, so it is probably behind - // the wallpaper. This can happen when the keyguard is up and all windows - // are moved behind the wallpaper. We don't want to include the wallpaper - // layer in the screenshot as it will cover-up the layer of the target - // window. - return false; - } - // Fall through. The target window is in front of the wallpaper. For this - // case we want to include the wallpaper layer in the screenshot because - // the target window might have some transparent areas. - } else if (appToken != null) { - if (w.mAppToken == null || w.mAppToken.token != appToken) { - // This app window is of no interest if it is not associated with the - // screenshot app. - return false; - } - mScreenshotApplicationState.appWin = w; - } - - // Include this window. - - final WindowStateAnimator winAnim = w.mWinAnimator; - - // Don't include wallpaper in bounds calculation - if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) { - if (includeDecor) { - final Task task = w.getTask(); - if (task != null) { - task.getBounds(frame); - } else { - - // No task bounds? Too bad! Ain't no screenshot then. - return true; - } - } else { - final Rect wf = w.mFrame; - final Rect cr = w.mContentInsets; - int left = wf.left + cr.left; - int top = wf.top + cr.top; - int right = wf.right - cr.right; - int bottom = wf.bottom - cr.bottom; - frame.union(left, top, right, bottom); - w.getVisibleBounds(stackBounds); - if (!Rect.intersects(frame, stackBounds)) { - // Set frame empty if there's no intersection. - frame.setEmpty(); - } - } - } - - final boolean foundTargetWs = - (w.mAppToken != null && w.mAppToken.token == appToken) - || (mScreenshotApplicationState.appWin != null && wallpaperOnly); - if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) { - mScreenshotApplicationState.screenshotReady = true; - } - - if (w.isObscuringDisplay()){ - return true; - } - return false; - }, true /* traverseTopToBottom */); - - final WindowState appWin = mScreenshotApplicationState.appWin; - final boolean screenshotReady = mScreenshotApplicationState.screenshotReady; - - if (appToken != null && appWin == null) { - // Can't find a window to snapshot. - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, - "Screenshot: Couldn't find a surface matching " + appToken); return null; } - if (!screenshotReady) { - Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken + - " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" + - appWin.mWinAnimator.mDrawState))); + if (wallpaperOnly && !shouldScreenshotWallpaper()) { return null; } - // Screenshot is ready to be taken. Everything from here below will continue - // through the bottom of the loop and return a value. We only stay in the loop - // because we don't want to release the mWindowMap lock until the screenshot is - // taken. - + int dw = mDisplayInfo.logicalWidth; + int dh = mDisplayInfo.logicalHeight; - if (!mutableIncludeFullDisplay.value) { - // Constrain frame to the screen size. - if (!frame.intersect(0, 0, dw, dh)) { - frame.setEmpty(); - } - } else { - // Caller just wants entire display. - frame.set(0, 0, dw, dh); - } - if (frame.isEmpty()) { + if (dw <= 0 || dh <= 0) { return null; } - if (width < 0) { - width = (int) (frame.width() * frameScale); - } - if (height < 0) { - height = (int) (frame.height() * frameScale); - } - - // Tell surface flinger what part of the image to crop. Take the top - // right part of the application, and crop the larger dimension to fit. - Rect crop = new Rect(frame); - if (width / (float) frame.width() < height / (float) frame.height()) { - int cropWidth = (int)((float)width / (float)height * frame.height()); - crop.right = crop.left + cropWidth; - } else { - int cropHeight = (int)((float)height / (float)width * frame.width()); - crop.bottom = crop.top + cropHeight; - } + final Rect frame = new Rect(0, 0, dw, dh); // The screenshot API does not apply the current screen rotation. int rot = mDisplay.getRotation(); @@ -3197,43 +3001,52 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90; } - // Surfaceflinger is not aware of orientation, so convert our logical - // crop to surfaceflinger's portrait orientation. - convertCropForSurfaceFlinger(crop, rot, dw, dh); - - if (DEBUG_SCREENSHOT) { - forAllWindows(w -> { - final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController; - Slog.i(TAG_WM, w + ": " + w.mLayer - + " animLayer=" + w.mWinAnimator.mAnimLayer - + " surfaceLayer=" + ((controller == null) - ? "null" : controller.getLayer())); - }, false /* traverseTopToBottom */); - } + // SurfaceFlinger is not aware of orientation, so convert our logical + // crop to SurfaceFlinger's portrait orientation. + convertCropForSurfaceFlinger(frame, rot, dw, dh); final ScreenRotationAnimation screenRotationAnimation = mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY); final boolean inRotation = screenRotationAnimation != null && screenRotationAnimation.isAnimating(); - if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, - "Taking screenshot while rotating"); - - // We force pending transactions to flush before taking - // the screenshot by pushing an empty synchronous transaction. - SurfaceControl.openTransaction(); - SurfaceControl.closeTransactionSync(); + if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating"); // TODO(b/68392460): We should screenshot Task controls directly // but it's difficult at the moment as the Task doesn't have the // correct size set. - bitmap = screenshoter.screenshot(crop, width, height, 0, 1, - inRotation, rot); + final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot); if (bitmap == null) { Slog.w(TAG_WM, "Failed to take screenshot"); return null; } + + // Create a copy of the screenshot that is immutable and backed in ashmem. + // This greatly reduces the overhead of passing the bitmap between processes. + final Bitmap ret = bitmap.createAshmemBitmap(config); + bitmap.recycle(); + return ret; } - return bitmap; + } + + private boolean shouldScreenshotWallpaper() { + MutableBoolean screenshotReady = new MutableBoolean(false); + + forAllWindows(w -> { + if (!w.mIsWallpaper) { + return false; + } + + // Found the wallpaper window + final WindowStateAnimator winAnim = w.mWinAnimator; + + if (winAnim.getShown() && winAnim.mLastAlpha > 0f) { + screenshotReady.value = true; + } + + return true; + }, true /* traverseTopToBottom */); + + return screenshotReady.value; } // TODO: Can this use createRotationMatrix()? @@ -3746,11 +3559,39 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + private final class AboveAppWindowContainers extends NonAppWindowContainers { + AboveAppWindowContainers(String name) { + super(name); + } + + void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) { + boolean needAssignIme = imeContainer != null + && imeContainer.getSurfaceControl() != null; + for (int j = 0; j < mChildren.size(); ++j) { + final WindowToken wt = mChildren.get(j); + wt.assignLayer(t, j); + wt.assignChildLayers(t); + + int layer = mService.mPolicy.getWindowLayerFromTypeLw( + wt.windowType, wt.mOwnerCanManageAppTokens); + if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) { + t.setRelativeLayer(imeContainer.getSurfaceControl(), + wt.getSurfaceControl(), -1); + needAssignIme = false; + } + } + if (needAssignIme) { + t.setRelativeLayer(imeContainer.getSurfaceControl(), + getSurfaceControl(), Integer.MIN_VALUE); + } + } + } + /** * Window container class that contains all containers on this display that are not related to * Apps. E.g. status bar. */ - private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> { + private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> { /** * Compares two child window tokens returns -1 if the first is lesser than the second in * terms of z-order and 1 otherwise. @@ -3819,15 +3660,6 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } - /** - * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}. - */ - @FunctionalInterface - private interface Screenshoter<E> { - E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer, - boolean useIdentityTransform, int rotation); - } - SurfaceControl.Builder makeSurface(SurfaceSession s) { return mService.makeSurfaceBuilder(s) .setParent(mWindowingLayer); @@ -3848,12 +3680,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return b; } - b.setName(child.getName()); - if (child.isScreenOverlay()) { - return b.setParent(mOverlayLayer); - } else { - return b.setParent(mWindowingLayer); - } + return b.setName(child.getName()) + .setParent(mWindowingLayer); } /** @@ -3891,39 +3719,36 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mAboveAppWindowsContainers.assignLayer(t, 2); WindowState imeTarget = mService.mInputMethodTarget; + boolean needAssignIme = true; - // A brief summary of IME layer assignment: + // In the case where we have an IME target that is not in split-screen + // mode IME assignment is easy. We just need the IME to go directly above + // the target. This way children of the target will naturally go above the IME + // and everyone is happy. // - // In case we have no IME target but we have an IME we are typically in - // a transition state and keeping the IME on top of everything except overlays - // seems to work best. + // In the case of split-screen windowing mode, we need to elevate the IME above the + // docked divider while keeping the app itself below the docked divider, so instead + // we use relative layering of the IME targets child windows, and place the + // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}). // - // In split-screen windowing mode we can't layer the - // IME relative to the IME target because it needs to - // go over the docked divider, so instead we place it on top - // of everything and use relative layering of windows which need - // to go above it (see special logic in WindowState#assignLayer) - // - // There is a third case, where the IME target has no SurfaceControl, for - // example if Layer assignment were triggered due to removal of the - // IME target while it was still the IME target. - if (imeTarget == null || - imeTarget.inSplitScreenWindowingMode() || - imeTarget.getSurfaceControl() == null) { - mImeWindowsContainers.assignLayer(t, 3); - } else { + // In the case where we have no IME target we assign it where it's base layer would + // place it in the AboveAppWindowContainers. + if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode() + && (imeTarget.getSurfaceControl() != null)) { t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(), imeTarget.getSurfaceControl(), // TODO: We need to use an extra level on the app surface to ensure // this is always above SurfaceView but always below attached window. 1); + needAssignIme = false; } // Above we have assigned layers to our children, now we ask them to assign // layers to their children. mBelowAppWindowsContainers.assignChildLayers(t); mTaskStackContainers.assignChildLayers(t); - mAboveAppWindowsContainers.assignChildLayers(t); + mAboveAppWindowsContainers.assignChildLayers(t, + needAssignIme == true ? mImeWindowsContainers : null); mImeWindowsContainers.assignChildLayers(t); } diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index e81d366bd85a..112e62f27ea2 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -645,15 +645,15 @@ class DragState { try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { transaction.setPosition( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X), - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X), + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y)); transaction.setAlpha( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); transaction.setMatrix( mSurfaceControl, - (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, - 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); + (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, + 0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); transaction.apply(); } } diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java index 5d4ba0992f16..87d0a4016634 100644 --- a/services/core/java/com/android/server/wm/TaskPositioner.java +++ b/services/core/java/com/android/server/wm/TaskPositioner.java @@ -382,6 +382,27 @@ class TaskPositioner { mStartOrientationWasLandscape = startBounds.width() >= startBounds.height(); mWindowOriginalBounds.set(startBounds); + // Notify the app that resizing has started, even though we haven't received any new + // bounds yet. This will guarantee that the app starts the backdrop renderer before + // configuration changes which could cause an activity restart. + if (mResizing) { + synchronized (mService.mWindowMap) { + notifyMoveLocked(startX, startY); + } + + // Perform the resize on the WMS handler thread when we don't have the WMS lock held + // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver + // callbacks are delivered on the same handler so this initial resize is always + // guaranteed to happen before subsequent drag resizes. + mService.mH.post(() -> { + try { + mService.mActivityManager.resizeTask( + mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED); + } catch (RemoteException e) { + } + }); + } + // Make sure we always have valid drag bounds even if the drag ends before any move events // have been handled. mWindowDragBounds.set(startBounds); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index e6444179d13c..43a089373a35 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -18,12 +18,12 @@ package com.android.server.wm; import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS; import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE; +import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityManager.StackId; import android.app.ActivityManager.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -35,6 +35,7 @@ import android.util.ArraySet; import android.util.Slog; import android.view.DisplayListCanvas; import android.view.RenderNode; +import android.view.SurfaceControl; import android.view.ThreadedRenderer; import android.view.WindowManager.LayoutParams; @@ -210,11 +211,28 @@ class TaskSnapshotController { if (mainWindow == null) { return null; } + if (!mService.mPolicy.isScreenOn()) { + if (DEBUG_SCREENSHOT) { + Slog.i(TAG_WM, "Attempted to take screenshot while display was off."); + } + return null; + } + if (task.getSurfaceControl() == null) { + return null; + } + final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f; - final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token, - -1, -1, false, scaleFraction, false, true); + final Rect taskFrame = new Rect(); + task.getBounds(taskFrame); + + final GraphicBuffer buffer = SurfaceControl.captureLayers( + task.getSurfaceControl().getHandle(), taskFrame, scaleFraction); + if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) { + if (DEBUG_SCREENSHOT) { + Slog.w(TAG_WM, "Failed to take screenshot"); + } return null; } return new TaskSnapshot(buffer, top.getConfiguration().orientation, diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 610453e6e92b..64675825243e 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -731,29 +731,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< final WindowContainer p = getParent(); // Give the parent a chance to set properties. In hierarchy v1 we rely // on this to set full-screen dimensions on all our Surface-less Layers. - final SurfaceControl.Builder b = p.makeChildSurface(child); - if (child != null && child.isScreenOverlay()) { - // If it's a screen overlay it's been promoted in the hierarchy (wrt to the - // WindowContainer hierarchy vs the SurfaceControl hierarchy) - // and we shouldn't set ourselves as the parent. - return b; - } else { - return b.setParent(mSurfaceControl); - } - } - - /** - * There are various layers which require promotion from the WindowContainer - * hierarchy to the Overlay layer described in {@link DisplayContent}. See {@link WindowState} - * for the particular usage. - * - * TODO: Perhaps this should be eliminated, either through modifying - * the window container hierarchy or through modifying the way we express these overlay - * Surfaces (for example, the Magnification Overlay could be implemented like the Strict-mode - * Flash and not actually use a WindowState). - */ - boolean isScreenOverlay() { - return false; + return p.makeChildSurface(child) + .setParent(mSurfaceControl); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1cd40806619f..82f842c0fd85 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -3691,9 +3691,8 @@ public class WindowManagerService extends IWindowManager.Stub } try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper"); - return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */, - -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */, - Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */); + return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, + true /* wallpaperOnly */); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -3712,10 +3711,8 @@ public class WindowManagerService extends IWindowManager.Stub } FgThread.getHandler().post(() -> { - Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, - -1 /* width */, -1 /* height */, true /* includeFullDisplay */, - 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */, - false /* includeDecor */); + Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888, + false /* wallpaperOnly */); try { receiver.onHandleAssistScreenshot(bm); } catch (RemoteException e) { @@ -3749,29 +3746,21 @@ public class WindowManagerService extends IWindowManager.Stub * In portrait mode, it grabs the full screenshot. * * @param displayId the Display to take a screenshot of. - * @param width the width of the target bitmap - * @param height the height of the target bitmap - * @param includeFullDisplay true if the screen should not be cropped before capture - * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1 * @param config of the output bitmap * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot - * @param includeDecor whether to include window decors, like the status or navigation bar - * background of the window */ - private Bitmap screenshotApplications(IBinder appToken, int displayId, int width, - int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config, - boolean wallpaperOnly, boolean includeDecor) { + private Bitmap screenshotApplications(int displayId, Bitmap.Config config, + boolean wallpaperOnly) { final DisplayContent displayContent; synchronized(mWindowMap) { displayContent = mRoot.getDisplayContentOrCreate(displayId); if (displayContent == null) { - if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken - + ": returning null. No Display for displayId=" + displayId); + if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for " + + "displayId=" + displayId); return null; } } - return displayContent.screenshotApplications(appToken, width, height, - includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor); + return displayContent.screenshotDisplay(config, wallpaperOnly); } /** diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 90e9c230b501..c2ac905219ea 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -58,6 +58,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; @@ -4110,9 +4111,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top, displayInfo.logicalWidth - mCompatFrame.left, displayInfo.logicalHeight - mCompatFrame.top); - } else if (mLayer >= mService.mSystemDecorLayer) { - // Above the decor layer is easy, just use the entire window - policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height()); } else if (mDecorFrame.isEmpty()) { // Windows without policy decor aren't cropped. policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height()); @@ -4346,24 +4344,16 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override boolean shouldMagnify() { if (mAttrs.type == TYPE_INPUT_METHOD || - mAttrs.type == TYPE_INPUT_METHOD_DIALOG) { - return false; - } else if (isScreenOverlay()) { - return false; - } - return true; - } - - @Override - boolean isScreenOverlay() { - // It's tempting to wonder: Have we forgotten the rounded corners overlay? - // worry not: it's a fake TYPE_NAVIGATION_BAR. - if (mAttrs.type == TYPE_MAGNIFICATION_OVERLAY || + mAttrs.type == TYPE_INPUT_METHOD_DIALOG || + mAttrs.type == TYPE_MAGNIFICATION_OVERLAY || mAttrs.type == TYPE_NAVIGATION_BAR || + // It's tempting to wonder: Have we forgotten the rounded corners overlay? + // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL + mAttrs.type == TYPE_NAVIGATION_BAR_PANEL || mAttrs.type == TYPE_STATUS_BAR) { - return true; + return false; } - return false; + return true; } @Override @@ -4382,7 +4372,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override SurfaceControl.Builder makeSurface() { - return mToken.makeChildSurface(this); + return getParent().makeChildSurface(this); } private void applyDims(Dimmer dimmer) { diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index d2b37481a429..4b5661f5d04d 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -871,11 +871,9 @@ class WindowStateAnimator { void computeShownFrameLocked() { final boolean selfTransformation = mHasLocalTransformation; - Transformation attachedTransformation = - (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation) - ? mParentWinAnimator.mTransformation : null; Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation) ? mAppAnimator.transformation : null; + Transformation wallpaperTargetTransformation = null; // Wallpapers are animated based on the "real" window they // are currently targeting. @@ -885,9 +883,9 @@ class WindowStateAnimator { if (wallpaperAnimator.mHasLocalTransformation && wallpaperAnimator.mAnimation != null && !wallpaperAnimator.mAnimation.getDetachWallpaper()) { - attachedTransformation = wallpaperAnimator.mTransformation; - if (DEBUG_WALLPAPER && attachedTransformation != null) { - Slog.v(TAG, "WP target attached xform: " + attachedTransformation); + wallpaperTargetTransformation = wallpaperAnimator.mTransformation; + if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) { + Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation); } } final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ? @@ -909,7 +907,7 @@ class WindowStateAnimator { screenRotationAnimation != null && screenRotationAnimation.isAnimating(); mHasClipRect = false; - if (selfTransformation || attachedTransformation != null + if (selfTransformation || wallpaperTargetTransformation != null || appTransformation != null || screenAnimation) { // cache often used attributes locally final Rect frame = mWin.mFrame; @@ -939,18 +937,27 @@ class WindowStateAnimator { if (selfTransformation) { tmpMatrix.postConcat(mTransformation.getMatrix()); } - if (attachedTransformation != null) { - tmpMatrix.postConcat(attachedTransformation.getMatrix()); + + if (wallpaperTargetTransformation != null) { + tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix()); } if (appTransformation != null) { tmpMatrix.postConcat(appTransformation.getMatrix()); } + int left = frame.left; + int top = frame.top; + if (mWin.isChildWindow()) { + WindowState parent = mWin.getParentWindow(); + left -= parent.mFrame.left; + top -= parent.mFrame.top; + } + // The translation that applies the position of the window needs to be applied at the // end in case that other translations include scaling. Otherwise the scaling will // affect this translation. But it needs to be set before the screen rotation animation // so the pivot point is at the center of the screen for all windows. - tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset); + tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset); if (screenAnimation) { tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix()); } @@ -985,8 +992,8 @@ class WindowStateAnimator { if (selfTransformation) { mShownAlpha *= mTransformation.getAlpha(); } - if (attachedTransformation != null) { - mShownAlpha *= attachedTransformation.getAlpha(); + if (wallpaperTargetTransformation != null) { + mShownAlpha *= wallpaperTargetTransformation.getAlpha(); } if (appTransformation != null) { mShownAlpha *= appTransformation.getAlpha(); @@ -1017,8 +1024,8 @@ class WindowStateAnimator { && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v( TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null") - + " attached=" + (attachedTransformation == null ? - "null" : attachedTransformation.getAlpha()) + + " attached=" + (wallpaperTargetTransformation == null ? + "null" : wallpaperTargetTransformation.getAlpha()) + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha()) + " screen=" + (screenAnimation ? screenRotationAnimation.getEnterTransformation().getAlpha() : "null")); diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp index df85e318619a..35e65bc3110f 100644 --- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp +++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp @@ -17,16 +17,18 @@ #define LOG_TAG "UsbHostManagerJNI" #include "utils/Log.h" +#include <stdlib.h> + #include "jni.h" #include <nativehelper/JNIHelp.h> #include <usbhost/usbhost.h> -#define MAX_DESCRIPTORS_LENGTH 16384 +#define MAX_DESCRIPTORS_LENGTH 4096 // com.android.server.usb.descriptors extern "C" { -jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors( +jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors_1native( JNIEnv* env, jobject thiz, jstring deviceAddr) { const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL); struct usb_device* device = usb_device_open(deviceAddrStr); @@ -39,6 +41,7 @@ jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_g int fd = usb_device_get_fd(device); if (fd < 0) { + usb_device_close(device); return NULL; } @@ -46,17 +49,48 @@ jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_g jbyte buffer[MAX_DESCRIPTORS_LENGTH]; lseek(fd, 0, SEEK_SET); int numBytes = read(fd, buffer, sizeof(buffer)); - + jbyteArray ret = NULL; usb_device_close(device); - jbyteArray ret = NULL; - if (numBytes != 0) { + if (numBytes > 0) { ret = env->NewByteArray(numBytes); env->SetByteArrayRegion(ret, 0, numBytes, buffer); + } else { + ALOGE("error reading descriptors\n"); } + return ret; } +jstring JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getDescriptorString_1native( + JNIEnv* env, jobject thiz, jstring deviceAddr, jint stringId) { + + const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL); + struct usb_device* device = usb_device_open(deviceAddrStr); + env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr); + + if (!device) { + ALOGE("usb_device_open failed"); + return NULL; + } + + int fd = usb_device_get_fd(device); + if (fd < 0) { + ALOGE("usb_device_get_fd failed"); + usb_device_close(device); + return NULL; + } + + char* c_str = usb_device_get_string(device, stringId, 0 /*timeout*/); + + jstring j_str = env->NewStringUTF(c_str); + + free(c_str); + usb_device_close(device); + + return j_str; +} + } // extern "C" diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp index 88ae824c1447..24f2014afa30 100644 --- a/services/core/jni/com_android_server_UsbHostManager.cpp +++ b/services/core/jni/com_android_server_UsbHostManager.cpp @@ -22,8 +22,6 @@ #include "android_runtime/AndroidRuntime.h" #include "android_runtime/Log.h" -#include <usbhost/usbhost.h> - #include <stdio.h> #include <asm/byteorder.h> #include <sys/types.h> @@ -31,22 +29,20 @@ #include <fcntl.h> #include <sys/ioctl.h> +#include <usbhost/usbhost.h> + +#define MAX_DESCRIPTORS_LENGTH 4096 + namespace android { -static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200; - static struct parcel_file_descriptor_offsets_t { jclass mClass; jmethodID mConstructor; } gParcelFileDescriptorOffsets; -static jmethodID method_beginUsbDeviceAdded; -static jmethodID method_addUsbConfiguration; -static jmethodID method_addUsbInterface; -static jmethodID method_addUsbEndpoint; -static jmethodID method_endUsbDeviceAdded; +static jmethodID method_usbDeviceAdded; static jmethodID method_usbDeviceRemoved; static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) { @@ -57,101 +53,52 @@ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodNa } } -static int usb_device_added(const char *devname, void* client_data) { - struct usb_descriptor_header* desc; - struct usb_descriptor_iter iter; - - struct usb_device *device = usb_device_open(devname); +static int usb_device_added(const char *devAddress, void* clientData) { + struct usb_device *device = usb_device_open(devAddress); if (!device) { ALOGE("usb_device_open failed\n"); return 0; } - JNIEnv* env = AndroidRuntime::getJNIEnv(); - jobject thiz = (jobject)client_data; const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device); - - char *manufacturer = usb_device_get_manufacturer_name(device, - USB_CONTROL_TRANSFER_TIMEOUT_MS); - char *product = usb_device_get_product_name(device, - USB_CONTROL_TRANSFER_TIMEOUT_MS); - int version = usb_device_get_version(device); - char *serial = usb_device_get_serial(device, - USB_CONTROL_TRANSFER_TIMEOUT_MS); - - jstring deviceName = env->NewStringUTF(devname); - jstring manufacturerName = AndroidRuntime::NewStringLatin1(env, manufacturer); - jstring productName = AndroidRuntime::NewStringLatin1(env, product); - jstring serialNumber = AndroidRuntime::NewStringLatin1(env, serial); - - jboolean result = env->CallBooleanMethod(thiz, method_beginUsbDeviceAdded, - deviceName, usb_device_get_vendor_id(device), usb_device_get_product_id(device), - deviceDesc->bDeviceClass, deviceDesc->bDeviceSubClass, deviceDesc->bDeviceProtocol, - manufacturerName, productName, version, serialNumber); - - env->DeleteLocalRef(serialNumber); - env->DeleteLocalRef(productName); - env->DeleteLocalRef(manufacturerName); - env->DeleteLocalRef(deviceName); - free(manufacturer); - free(product); - free(serial); - - if (!result) goto fail; - - usb_descriptor_iter_init(device, &iter); - - while ((desc = usb_descriptor_iter_next(&iter)) != NULL) { - if (desc->bDescriptorType == USB_DT_CONFIG) { - struct usb_config_descriptor *config = (struct usb_config_descriptor *)desc; - char *name = usb_device_get_string(device, config->iConfiguration, - USB_CONTROL_TRANSFER_TIMEOUT_MS); - jstring configName = AndroidRuntime::NewStringLatin1(env, name); - - env->CallVoidMethod(thiz, method_addUsbConfiguration, - config->bConfigurationValue, configName, config->bmAttributes, - config->bMaxPower); - - env->DeleteLocalRef(configName); - free(name); - } else if (desc->bDescriptorType == USB_DT_INTERFACE) { - struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc; - char *name = usb_device_get_string(device, interface->iInterface, - USB_CONTROL_TRANSFER_TIMEOUT_MS); - jstring interfaceName = AndroidRuntime::NewStringLatin1(env, name); - - env->CallVoidMethod(thiz, method_addUsbInterface, - interface->bInterfaceNumber, interfaceName, interface->bAlternateSetting, - interface->bInterfaceClass, interface->bInterfaceSubClass, - interface->bInterfaceProtocol); - - env->DeleteLocalRef(interfaceName); - free(name); - } else if (desc->bDescriptorType == USB_DT_ENDPOINT) { - struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)desc; - - env->CallVoidMethod(thiz, method_addUsbEndpoint, - endpoint->bEndpointAddress, endpoint->bmAttributes, - __le16_to_cpu(endpoint->wMaxPacketSize), endpoint->bInterval); - } + int classID = deviceDesc->bDeviceClass; + int subClassID = deviceDesc->bDeviceSubClass; + + // get the raw descriptors + int numBytes = usb_device_get_descriptors_length(device); + if (numBytes > 0) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + jobject thiz = (jobject)clientData; + jstring deviceAddress = env->NewStringUTF(devAddress); + + jbyteArray descriptorsArray = env->NewByteArray(numBytes); + const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device); + env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors); + + env->CallBooleanMethod(thiz, method_usbDeviceAdded, + deviceAddress, classID, subClassID, descriptorsArray); + + env->DeleteLocalRef(descriptorsArray); + env->DeleteLocalRef(deviceAddress); + + checkAndClearExceptionFromCallback(env, __FUNCTION__); + } else { + // TODO return an error code here? + ALOGE("error reading descriptors\n"); } - env->CallVoidMethod(thiz, method_endUsbDeviceAdded); - -fail: usb_device_close(device); - checkAndClearExceptionFromCallback(env, __FUNCTION__); return 0; } -static int usb_device_removed(const char *devname, void* client_data) { +static int usb_device_removed(const char *devAddress, void* clientData) { JNIEnv* env = AndroidRuntime::getJNIEnv(); - jobject thiz = (jobject)client_data; + jobject thiz = (jobject)clientData; - jstring deviceName = env->NewStringUTF(devname); - env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceName); - env->DeleteLocalRef(deviceName); + jstring deviceAddress = env->NewStringUTF(devAddress); + env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceAddress); + env->DeleteLocalRef(deviceAddress); checkAndClearExceptionFromCallback(env, __FUNCTION__); return 0; } @@ -168,11 +115,11 @@ static void android_server_UsbHostManager_monitorUsbHostBus(JNIEnv* /* env */, j } static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */, - jstring deviceName) + jstring deviceAddress) { - const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL); - struct usb_device* device = usb_device_open(deviceNameStr); - env->ReleaseStringUTFChars(deviceName, deviceNameStr); + const char *deviceAddressStr = env->GetStringUTFChars(deviceAddress, NULL); + struct usb_device* device = usb_device_open(deviceAddressStr); + env->ReleaseStringUTFChars(deviceAddress, deviceAddressStr); if (!device) return NULL; @@ -206,34 +153,12 @@ int register_android_server_UsbHostManager(JNIEnv *env) ALOGE("Can't find com/android/server/usb/UsbHostManager"); return -1; } - method_beginUsbDeviceAdded = env->GetMethodID(clazz, "beginUsbDeviceAdded", - "(Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;ILjava/lang/String;)Z"); - if (method_beginUsbDeviceAdded == NULL) { + method_usbDeviceAdded = + env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;II[B)Z"); + if (method_usbDeviceAdded == NULL) { ALOGE("Can't find beginUsbDeviceAdded"); return -1; } - method_addUsbConfiguration = env->GetMethodID(clazz, "addUsbConfiguration", - "(ILjava/lang/String;II)V"); - if (method_addUsbConfiguration == NULL) { - ALOGE("Can't find addUsbConfiguration"); - return -1; - } - method_addUsbInterface = env->GetMethodID(clazz, "addUsbInterface", - "(ILjava/lang/String;IIII)V"); - if (method_addUsbInterface == NULL) { - ALOGE("Can't find addUsbInterface"); - return -1; - } - method_addUsbEndpoint = env->GetMethodID(clazz, "addUsbEndpoint", "(IIII)V"); - if (method_addUsbEndpoint == NULL) { - ALOGE("Can't find addUsbEndpoint"); - return -1; - } - method_endUsbDeviceAdded = env->GetMethodID(clazz, "endUsbDeviceAdded", "()V"); - if (method_endUsbDeviceAdded == NULL) { - ALOGE("Can't find endUsbDeviceAdded"); - return -1; - } method_usbDeviceRemoved = env->GetMethodID(clazz, "usbDeviceRemoved", "(Ljava/lang/String;)V"); if (method_usbDeviceRemoved == NULL) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index 608635491849..4a6bee5c7365 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -64,6 +64,8 @@ final class NetworkLoggingHandler extends Handler { private final DevicePolicyManagerService mDpm; private final AlarmManager mAlarmManager; + private long mId; + private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() { @Override public void onAlarm() { @@ -185,6 +187,10 @@ final class NetworkLoggingHandler extends Handler { private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() { Bundle notificationExtras = null; if (mNetworkEvents.size() > 0) { + // Assign ids to the events. + for (NetworkEvent event : mNetworkEvents) { + event.setId(mId++); + } // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { // Remove the oldest batch if we hit the limit. diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index a2ec23496a03..e5ab44ffaf6b 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -26,6 +26,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ platform-test-annotations \ ShortcutManagerTestUtils \ truth-prebuilt \ + testables \ testng LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 9a5ebedcf361..0499bf0eccc7 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -58,6 +58,7 @@ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" /> <uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" /> + <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <!-- Uses API introduced in O (26) --> <uses-sdk android:minSdkVersion="1" diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java index b4919b6f9c52..1cec0d93cabe 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java @@ -277,4 +277,10 @@ public class ActivityStarterTests extends ActivityTestsBase { verify(options, times(1)).abort(); } } + +// TODO(b/69270257): Add test to verify task layout is passed additional data such as activity and +// source. +// @Test +// public void testCreateTaskLayout() { +// } } 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 2fffb892c5f0..d74d994844ef 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -191,6 +191,7 @@ public class ActivityTestsBase { private String mPackage; private int mFlags = 0; private int mTaskId = 0; + private int mUserId = 0; private IVoiceInteractionSession mVoiceSession; private ActivityStack mStack; @@ -224,6 +225,11 @@ public class ActivityTestsBase { return this; } + TaskBuilder setUserId(int userId) { + mUserId = userId; + return this; + } + TaskBuilder setStack(ActivityStack stack) { mStack = stack; return this; @@ -245,10 +251,12 @@ public class ActivityTestsBase { final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo, intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/); + task.userId = mUserId; mSupervisor.setFocusStackUnchecked("test", mStack); mStack.addTask(task, true, "creating test task"); task.setStack(mStack); task.setWindowContainerController(mock(TaskWindowContainerController.class)); + task.touchActiveTime(); return task; } 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 afece5d21940..13ca10c545ca 100644 --- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java +++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java @@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; +import static android.view.Display.DEFAULT_DISPLAY; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -30,6 +31,8 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static java.lang.Integer.MAX_VALUE; @@ -42,6 +45,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.graphics.Rect; import android.os.Bundle; +import android.os.Debug; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; @@ -64,6 +68,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.Set; /** @@ -78,17 +83,19 @@ public class RecentTasksTest extends ActivityTestsBase { private static final int TEST_QUIET_USER_ID = 20; private static final UserInfo DEFAULT_USER_INFO = new UserInfo(); private static final UserInfo QUIET_USER_INFO = new UserInfo(); - private static final ComponentName MY_COMPONENT = new ComponentName( - RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName()); private static int LAST_TASK_ID = 1; + private static int LAST_STACK_ID = 1; private static int INVALID_STACK_ID = 999; private Context mContext = InstrumentationRegistry.getContext(); private ActivityManagerService mService; + private ActivityDisplay mDisplay; + private ActivityDisplay mOtherDisplay; private ActivityStack mStack; + private ActivityStack mHomeStack; private TestTaskPersister mTaskPersister; - private RecentTasks mRecentTasks; - private RunningTasks mRunningTasks; + private TestRecentTasks mRecentTasks; + private TestRunningTasks mRunningTasks; private static ArrayList<TaskRecord> mTasks = new ArrayList<>(); private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>(); @@ -133,22 +140,25 @@ public class RecentTasksTest extends ActivityTestsBase { mTaskPersister = new TestTaskPersister(mContext.getFilesDir()); mService = setupActivityManagerService(new MyTestActivityManagerService(mContext)); - mRecentTasks = mService.getRecentTasks(); + mRecentTasks = (TestRecentTasks) mService.getRecentTasks(); mRecentTasks.loadParametersFromResources(mContext.getResources()); + mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack); mCallbacksRecorder = new CallbacksRecorder(); mRecentTasks.registerCallback(mCallbacksRecorder); QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE; - mTasks.add(createTask(".Task1")); - mTasks.add(createTask(".Task2")); - mTasks.add(createTask(".Task3")); - mTasks.add(createTask(".Task4")); - mTasks.add(createTask(".Task5")); + mTasks.add(createTaskBuilder(".Task1").build()); + mTasks.add(createTaskBuilder(".Task2").build()); + mTasks.add(createTaskBuilder(".Task3").build()); + mTasks.add(createTaskBuilder(".Task4").build()); + mTasks.add(createTaskBuilder(".Task5").build()); - mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */)); - mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */)); + mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); + mSameDocumentTasks.add(createDocumentTask(".DocumentTask1")); } @Test @@ -172,9 +182,9 @@ public class RecentTasksTest extends ActivityTestsBase { mCallbacksRecorder.clear(); // Add a task which will trigger the trimming of another - TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", null /* affinity */); + TaskRecord documentTask1 = createDocumentTask(".DocumentTask1"); documentTask1.maxRecents = 1; - TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", null /* affinity */); + TaskRecord documentTask2 = createDocumentTask(".DocumentTask1"); mRecentTasks.add(documentTask1); mRecentTasks.add(documentTask2); assertTrue(mCallbacksRecorder.added.contains(documentTask1)); @@ -194,13 +204,15 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testUsersTasks() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + // Setup some tasks for the users mTaskPersister.userTaskIdsOverride = new SparseBooleanArray(); mTaskPersister.userTaskIdsOverride.put(1, true); mTaskPersister.userTaskIdsOverride.put(2, true); mTaskPersister.userTasksOverride = new ArrayList<>(); - mTaskPersister.userTasksOverride.add(createTask(".UserTask1")); - mTaskPersister.userTasksOverride.add(createTask(".UserTask2")); + mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build()); + mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build()); // Assert no user tasks are initially loaded assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0); @@ -235,6 +247,20 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testOrderedIteration() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + TaskRecord task1 = createTaskBuilder(".Task1").build(); + task1.lastActiveTime = new Random().nextInt(); + TaskRecord task2 = createTaskBuilder(".Task1").build(); + task2.lastActiveTime = new Random().nextInt(); + TaskRecord task3 = createTaskBuilder(".Task1").build(); + task3.lastActiveTime = new Random().nextInt(); + TaskRecord task4 = createTaskBuilder(".Task1").build(); + task4.lastActiveTime = new Random().nextInt(); + mRecentTasks.add(task1); + mRecentTasks.add(task2); + mRecentTasks.add(task3); + mRecentTasks.add(task4); + MutableLong prevLastActiveTime = new MutableLong(0); final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks(); for (int i = 0; i < tasks.size(); i++) { @@ -246,6 +272,8 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testTrimToGlobalMaxNumRecents() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); + // Limit the global maximum number of recent tasks to a fixed size mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */); @@ -260,8 +288,9 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testTrimQuietProfileTasks() throws Exception { - TaskRecord qt1 = createTask(".QuietTask1", TEST_QUIET_USER_ID); - TaskRecord qt2 = createTask(".QuietTask2", TEST_QUIET_USER_ID); + mRecentTasks.setOnlyTestVisibleRange(); + TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build(); + TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build(); mRecentTasks.add(qt1); mRecentTasks.add(qt2); @@ -274,16 +303,17 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testSessionDuration() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */); - TaskRecord t1 = createTask(".Task1"); + TaskRecord t1 = createTaskBuilder(".Task1").build(); t1.touchActiveTime(); mRecentTasks.add(t1); // Force a small sleep just beyond the session duration SystemClock.sleep(75); - TaskRecord t2 = createTask(".Task2"); + TaskRecord t2 = createTaskBuilder(".Task2").build(); t2.touchActiveTime(); mRecentTasks.add(t2); @@ -293,12 +323,15 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testVisibleTasks_excludedFromRecents() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */); - TaskRecord excludedTask1 = createTask(".ExcludedTask1", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS, - TEST_USER_0_ID); - TaskRecord excludedTask2 = createTask(".ExcludedTask2", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS, - TEST_USER_0_ID); + TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .build(); + TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2") + .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .build(); mRecentTasks.add(excludedTask1); mRecentTasks.add(mTasks.get(0)); @@ -312,6 +345,7 @@ public class RecentTasksTest extends ActivityTestsBase { @Test public void testVisibleTasks_minNum() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */); for (int i = 0; i < 4; i++) { @@ -327,12 +361,12 @@ public class RecentTasksTest extends ActivityTestsBase { mRecentTasks.add(mTasks.get(4)); // Ensure that there are a minimum number of tasks regardless of session length - assertTrue(mCallbacksRecorder.trimmed.isEmpty()); - assertTrue(mCallbacksRecorder.removed.isEmpty()); + assertNoTasksTrimmed(); } @Test public void testVisibleTasks_maxNum() throws Exception { + mRecentTasks.setOnlyTestVisibleRange(); mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */); for (int i = 0; i < 5; i++) { @@ -346,16 +380,78 @@ public class RecentTasksTest extends ActivityTestsBase { } @Test + public void testBackStackTasks_expectNoTrim() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all + // the tasks belong in stacks above the home stack + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build()); + + assertNoTasksTrimmed(); + } + + @Test + public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind + // the home stack is trimmed once a new task is added + final TaskRecord behindHomeTask = createTaskBuilder(".Task1") + .setStack(behindHomeStack) + .build(); + mRecentTasks.add(behindHomeTask); + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build()); + + assertTrimmed(behindHomeTask); + } + + @Test + public void testOtherDisplayTasks_expectNoTrim() throws Exception { + mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */); + + final MyTestActivityStackSupervisor supervisor = + (MyTestActivityStackSupervisor) mService.mStackSupervisor; + final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor); + final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor); + supervisor.setHomeStack(homeStack); + + // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not + // removed + mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build()); + mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build()); + mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build()); + mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build()); + + assertNoTasksTrimmed(); + } + + @Test public void testNotRecentsComponent_denyApiAccess() throws Exception { doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(), anyInt(), anyInt()); // Expect the following methods to fail due to recents component not being set - ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride( - TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION); + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION); testRecentTasksApis(false /* expectNoSecurityException */); // Don't throw for the following tests - ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY); + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY); testGetTasksApis(false /* expectNoSecurityException */); } @@ -365,7 +461,7 @@ public class RecentTasksTest extends ActivityTestsBase { anyInt(), anyInt()); // Set the recents component and ensure that the following calls do not fail - ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT); + mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT); testRecentTasksApis(true /* expectNoSecurityException */); testGetTasksApis(true /* expectNoSecurityException */); } @@ -436,38 +532,27 @@ public class RecentTasksTest extends ActivityTestsBase { mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID); mService.getTasks(MAX_VALUE); if (expectCallable) { - assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed); - assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed); + assertTrue(mRecentTasks.lastAllowed); + assertTrue(mRunningTasks.lastAllowed); } else { - assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed); - assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed); + assertFalse(mRecentTasks.lastAllowed); + assertFalse(mRunningTasks.lastAllowed); } } - private ComponentName createComponent(String className) { - return new ComponentName(mContext.getPackageName(), className); - } - - private TaskRecord createTask(String className) { - return createTask(className, TEST_USER_0_ID); - } - - private TaskRecord createTask(String className, int userId) { - return createTask(className, 0 /* flags */, userId); - } - - private TaskRecord createTask(String className, int flags, int userId) { - TaskRecord task = new TaskBuilder(mService.mStackSupervisor) - .setComponent(createComponent(className)) - .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build(); - task.userId = userId; - task.touchActiveTime(); - return task; + private TaskBuilder createTaskBuilder(String className) { + return new TaskBuilder(mService.mStackSupervisor) + .setComponent(new ComponentName(mContext.getPackageName(), className)) + .setStack(mStack) + .setTaskId(LAST_TASK_ID++) + .setUserId(TEST_USER_0_ID); } - private TaskRecord createDocumentTask(String className, String affinity) { - TaskRecord task = createTask(className, FLAG_ACTIVITY_NEW_DOCUMENT, TEST_USER_0_ID); - task.affinity = affinity; + private TaskRecord createDocumentTask(String className) { + TaskRecord task = createTaskBuilder(className) + .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT) + .build(); + task.affinity = null; return task; } @@ -476,6 +561,10 @@ public class RecentTasksTest extends ActivityTestsBase { return Arrays.binarySearch(userIds, targetUserId) >= 0; } + private void assertNoTasksTrimmed() { + assertTrimmed(); + } + private void assertTrimmed(TaskRecord... tasks) { final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed; final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed; @@ -532,10 +621,41 @@ public class RecentTasksTest extends ActivityTestsBase { } @Override + public void initialize() { + super.initialize(); + mDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); + mOtherDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY); + attachDisplay(mOtherDisplay); + attachDisplay(mDisplay); + } + + @Override RunningTasks createRunningTasks() { mRunningTasks = new TestRunningTasks(); return mRunningTasks; } + + void setHomeStack(ActivityStack stack) { + mHomeStack = stack; + } + } + + private class MyTestActivityStack extends TestActivityStack { + private ActivityDisplay mDisplay = null; + + MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) { + super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN, + ACTIVITY_TYPE_STANDARD, true); + mDisplay = display; + } + + @Override + ActivityDisplay getDisplay() { + if (mDisplay != null) { + return mDisplay; + } + return super.getDisplay(); + } } private static class CallbacksRecorder implements Callbacks { @@ -564,7 +684,6 @@ public class RecentTasksTest extends ActivityTestsBase { } private static class TestTaskPersister extends TaskPersister { - SparseBooleanArray userTaskIdsOverride; ArrayList<TaskRecord> userTasksOverride; @@ -595,8 +714,10 @@ public class RecentTasksTest extends ActivityTestsBase { static final int DENY_THROW_SECURITY_EXCEPTION = 2; private boolean mOverrideIsCallerRecents; + private boolean mIsTrimmableOverride; private int mIsCallerRecentsPolicy; - boolean mLastAllowed; + + boolean lastAllowed; TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister, UserController userController) { @@ -623,24 +744,39 @@ public class RecentTasksTest extends ActivityTestsBase { mIsCallerRecentsPolicy = policy; } + /** + * To simplify the setup for some tests, the caller can request that we only rely on the + * visible range test to determine what is trimmable. In this case, we don't try to + * use the stack order to determine additionally if the task is trimmable when it is not + * in the visible range. + */ + void setOnlyTestVisibleRange() { + mIsTrimmableOverride = true; + } + @Override ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags, boolean getTasksAllowed, boolean getDetailedTasks, int userId, int callingUid) { - mLastAllowed = getTasksAllowed; + lastAllowed = getTasksAllowed; return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId, callingUid); } + + @Override + protected boolean isTrimmable(TaskRecord task) { + return mIsTrimmableOverride || super.isTrimmable(task); + } } private static class TestRunningTasks extends RunningTasks { - boolean mLastAllowed; + boolean lastAllowed; @Override void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType, int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays, int callingUid, boolean allowed) { - mLastAllowed = allowed; + lastAllowed = allowed; super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays, callingUid, allowed); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java index db317a0e4cca..a92d1db2616b 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java @@ -20,8 +20,6 @@ import android.app.admin.DnsEvent; import android.os.Parcel; import android.test.suitebuilder.annotation.SmallTest; -import static junit.framework.Assert.assertEquals; - @SmallTest public class NetworkEventTest extends DpmTestBase { @@ -30,6 +28,7 @@ public class NetworkEventTest extends DpmTestBase { */ public void testConnectEventParceling() { ConnectEvent event = new ConnectEvent("127.0.0.1", 80, "com.android.whateverdude", 100000); + event.setId(5L); Parcel p = Parcel.obtain(); p.writeParcelable(event, 0); p.setDataPosition(0); @@ -39,6 +38,7 @@ public class NetworkEventTest extends DpmTestBase { assertEquals(event.getPort(), unparceledEvent.getPort()); assertEquals(event.getPackageName(), unparceledEvent.getPackageName()); assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp()); + assertEquals(event.getId(), unparceledEvent.getId()); } /** @@ -47,6 +47,7 @@ public class NetworkEventTest extends DpmTestBase { public void testDnsEventParceling() { DnsEvent event = new DnsEvent("d.android.com", new String[]{"192.168.0.1", "127.0.0.1"}, 2, "com.android.whateverdude", 100000); + event.setId(5L); Parcel p = Parcel.obtain(); p.writeParcelable(event, 0); p.setDataPosition(0); @@ -59,5 +60,6 @@ public class NetworkEventTest extends DpmTestBase { unparceledEvent.getTotalResolvedAddressCount()); assertEquals(event.getPackageName(), unparceledEvent.getPackageName()); assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp()); + assertEquals(event.getId(), unparceledEvent.getId()); } } diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java new file mode 100644 index 000000000000..a70441d5b84b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2017 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.policy; + +import android.annotation.Nullable; +import android.graphics.Point; +import android.graphics.Rect; +import android.view.Display; +import android.view.IApplicationToken; +import android.view.WindowManager; + +public class FakeWindowState implements WindowManagerPolicy.WindowState { + + public final Rect parentFrame = new Rect(); + public final Rect displayFrame = new Rect(); + public final Rect overscanFrame = new Rect(); + public final Rect contentFrame = new Rect(); + public final Rect visibleFrame = new Rect(); + public final Rect decorFrame = new Rect(); + public final Rect stableFrame = new Rect(); + public Rect outsetFrame = new Rect(); + + public WindowManager.LayoutParams attrs; + public int displayId; + public boolean isVoiceInteraction; + public boolean inMultiWindowMode; + public boolean visible = true; + public int surfaceLayer = 1; + + public boolean policyVisible = true; + + @Override + public int getOwningUid() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public String getOwningPackage() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overlayFrame, + Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame, + @Nullable Rect outsetFrame) { + this.parentFrame.set(parentFrame); + this.displayFrame.set(displayFrame); + this.overscanFrame.set(overlayFrame); + this.contentFrame.set(contentFrame); + this.visibleFrame.set(visibleFrame); + this.decorFrame.set(decorFrame); + this.stableFrame.set(stableFrame); + this.outsetFrame = outsetFrame == null ? null : new Rect(outsetFrame); + } + + @Override + public Rect getFrameLw() { + return parentFrame; + } + + @Override + public Point getShownPositionLw() { + return new Point(parentFrame.left, parentFrame.top); + } + + @Override + public Rect getDisplayFrameLw() { + return displayFrame; + } + + @Override + public Rect getOverscanFrameLw() { + return overscanFrame; + } + + @Override + public Rect getContentFrameLw() { + return contentFrame; + } + + @Override + public Rect getVisibleFrameLw() { + return visibleFrame; + } + + @Override + public boolean getGivenInsetsPendingLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Rect getGivenContentInsetsLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public Rect getGivenVisibleInsetsLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public WindowManager.LayoutParams getAttrs() { + return attrs; + } + + @Override + public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getSystemUiVisibility() { + return attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility; + } + + @Override + public int getSurfaceLayer() { + return surfaceLayer; + } + + @Override + public int getBaseType() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public IApplicationToken getAppToken() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isVoiceInteraction() { + return isVoiceInteraction; + } + + @Override + public boolean hasAppShownWindows() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isVisibleLw() { + return visible && policyVisible; + } + + @Override + public boolean isDisplayedLw() { + return isVisibleLw(); + } + + @Override + public boolean isAnimatingLw() { + return false; + } + + @Override + public boolean canAffectSystemUiFlags() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isGoneForLayoutLw() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isDrawnLw() { + return true; + } + + @Override + public boolean hasDrawnLw() { + return true; + } + + @Override + public boolean hideLw(boolean doAnimation) { + if (!policyVisible) { + return false; + } + policyVisible = false; + return true; + } + + @Override + public boolean showLw(boolean doAnimation) { + if (policyVisible) { + return false; + } + policyVisible = true; + return true; + } + + @Override + public boolean isAlive() { + return true; + } + + @Override + public boolean isDefaultDisplay() { + return displayId == Display.DEFAULT_DISPLAY; + } + + @Override + public boolean isDimming() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getWindowingMode() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInMultiWindowMode() { + return inMultiWindowMode; + } + + @Override + public int getRotationAnimationHint() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public boolean isInputMethodWindow() { + throw new UnsupportedOperationException("not implemented"); + } + + @Override + public int getDisplayId() { + return displayId; + } + + @Override + public boolean canAcquireSleepToken() { + throw new UnsupportedOperationException("not implemented"); + } +} diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java new file mode 100644 index 000000000000..0941d4fd4fc9 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 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.policy; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; +import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import static org.junit.Assert.assertEquals; + +import android.graphics.PixelFormat; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.WindowManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +@Presubmit +public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { + + private FakeWindowState mAppWindow; + + @Before + public void setUp() throws Exception { + mAppWindow = new FakeWindowState(); + mAppWindow.attrs = new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT, + TYPE_APPLICATION, + FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, + PixelFormat.TRANSLUCENT); + + addStatusBar(); + addNavigationBar(); + } + + @Test + public void layoutWindowLw_appDrawsBars() throws Exception { + mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_appWontDrawBars() throws Exception { + mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + } + + @Test + public void layoutWindowLw_appWontDrawBars_forceStatus() throws Exception { + mAppWindow.attrs.flags &= ~FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mAppWindow.attrs.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, NAV_BAR_HEIGHT); + } + + @Test + public void addingWindow_doesNotTamperWithSysuiFlags() { + mAppWindow.attrs.flags |= FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; + mPolicy.addWindow(mAppWindow); + + assertEquals(0, mAppWindow.attrs.systemUiVisibility); + assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility); + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java new file mode 100644 index 000000000000..9c5570725baa --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2017 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.policy; + +import static android.view.Surface.ROTATION_0; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.UserHandle; +import android.support.test.InstrumentationRegistry; +import android.testing.TestableResources; +import android.view.Display; +import android.view.DisplayInfo; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.IAccessibilityManager; + +import com.android.server.policy.keyguard.KeyguardServiceDelegate; +import com.android.server.wm.DisplayFrames; + +import org.junit.Before; + +public class PhoneWindowManagerTestBase { + static final int DISPLAY_WIDTH = 500; + static final int DISPLAY_HEIGHT = 1000; + + static final int STATUS_BAR_HEIGHT = 10; + static final int NAV_BAR_HEIGHT = 15; + + TestablePhoneWindowManager mPolicy; + TestContextWrapper mContext; + DisplayFrames mFrames; + + FakeWindowState mStatusBar; + FakeWindowState mNavigationBar; + + @Before + public void setUpBase() throws Exception { + mContext = new TestContextWrapper(InstrumentationRegistry.getTargetContext()); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.status_bar_height, STATUS_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_height, NAV_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_height_landscape, NAV_BAR_HEIGHT); + mContext.getResourceMocker().addOverride( + com.android.internal.R.dimen.navigation_bar_width, NAV_BAR_HEIGHT); + + mPolicy = TestablePhoneWindowManager.create(mContext); + + DisplayInfo info = new DisplayInfo(); + info.logicalWidth = DISPLAY_WIDTH; + info.logicalHeight = DISPLAY_HEIGHT; + info.rotation = ROTATION_0; + mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info); + } + + public void addStatusBar() { + mStatusBar = new FakeWindowState(); + mStatusBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT, + TYPE_STATUS_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); + mStatusBar.attrs.gravity = Gravity.TOP; + + mPolicy.addWindow(mStatusBar); + mPolicy.mLastSystemUiFlags |= View.STATUS_BAR_TRANSPARENT; + } + + public void addNavigationBar() { + mNavigationBar = new FakeWindowState(); + mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT, + TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); + mNavigationBar.attrs.gravity = Gravity.BOTTOM; + + mPolicy.addWindow(mNavigationBar); + mPolicy.mHasNavigationBar = true; + mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT; + } + + /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */ + public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop, + int expectedInsetRight, int expectedInsetBottom) { + assertEquals(new Rect(expectedInsetLeft, expectedInsetTop, + DISPLAY_WIDTH - expectedInsetRight, DISPLAY_HEIGHT - expectedInsetBottom), actual); + } + + /** + * Asserts that {@code actual} is inset by the given amounts from the full display rect. + * + * Convenience wrapper for when only the top and bottom inset are non-zero. + */ + public void assertInsetByTopBottom(Rect actual, int expectedInsetTop, int expectedInsetBottom) { + assertInsetBy(actual, 0, expectedInsetTop, 0, expectedInsetBottom); + } + + static class TestContextWrapper extends ContextWrapper { + private final TestableResources mResourceMocker; + + public TestContextWrapper(Context targetContext) { + super(targetContext); + mResourceMocker = new TestableResources(targetContext.getResources()); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + return PackageManager.PERMISSION_GRANTED; + } + + @Override + public Resources getResources() { + return mResourceMocker.getResources(); + } + + public TestableResources getResourceMocker() { + return mResourceMocker; + } + } + + static class TestablePhoneWindowManager extends PhoneWindowManager { + + public TestablePhoneWindowManager() { + } + + @Override + void initializeHdmiState() { + // Do nothing. + } + + @Override + Context getSystemUiContext() { + return mContext; + } + + void addWindow(WindowState state) { + if (state instanceof FakeWindowState) { + ((FakeWindowState) state).surfaceLayer = + getWindowLayerFromTypeLw(state.getAttrs().type); + } + adjustWindowParamsLw(state, state.getAttrs(), true /* hasStatusBarPermission */); + assertEquals(WindowManagerGlobal.ADD_OKAY, prepareAddWindowLw(state, state.getAttrs())); + } + + public static TestablePhoneWindowManager create(Context context) { + TestablePhoneWindowManager[] policy = new TestablePhoneWindowManager[1]; + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + policy[0] = new TestablePhoneWindowManager(); + policy[0].mContext = context; + policy[0].mKeyguardDelegate = mock(KeyguardServiceDelegate.class); + policy[0].mAccessibilityManager = new AccessibilityManager(context, + mock(IAccessibilityManager.class), UserHandle.USER_CURRENT); + policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class); + policy[0].onConfigurationChanged(); + }); + return policy[0]; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java index 7e2a7d221c22..7324fe6de9d8 100644 --- a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java +++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java @@ -15,8 +15,9 @@ */ package com.android.server.power.batterysaver; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -26,6 +27,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; +import android.hardware.camera2.impl.GetCommand; import android.os.Handler; import android.os.Looper; import android.support.test.InstrumentationRegistry; @@ -40,6 +42,7 @@ import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -72,6 +75,17 @@ public class FileUpdaterTest { void injectWtf(String message, Throwable e) { mInjector.injectWtf(message, e); } + + @Override + File injectDefaultValuesFilename() { + return new File(InstrumentationRegistry.getContext().getCacheDir() + + "/test-default.xml"); + } + + @Override + boolean injectShouldSkipWrite() { + return false; + } } private interface Injector { @@ -334,4 +348,57 @@ public class FileUpdaterTest { reset(mInjector); testMultiWrites(); } + + @Test + public void testWriteReadDefault() throws Exception { + doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1"); + doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2"); + doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3"); + + // Write + final ArrayMap<String, String> values = new ArrayMap<>(); + values.put("file1", "11"); + values.put("file2", "22"); + values.put("file3", "33"); + + mInstance.writeFiles(values); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "11"); + verify(mInjector, times(1)).injectWriteToFile("file2", "22"); + verify(mInjector, times(1)).injectWriteToFile("file3", "33"); + + // Clear and reload the default. + assertEquals(3, mInstance.getDefaultValuesForTest().size()); + mInstance.getDefaultValuesForTest().clear(); + assertEquals(0, mInstance.getDefaultValuesForTest().size()); + + mInstance.systemReady(/*runtimeRestarted=*/ true); + + assertEquals(3, mInstance.getDefaultValuesForTest().size()); + + // Reset to default + mInstance.restoreDefault(); + waitUntilMainHandlerDrain(); + + verify(mInjector, times(1)).injectWriteToFile("file1", "111"); + verify(mInjector, times(1)).injectWriteToFile("file2", "222"); + verify(mInjector, times(1)).injectWriteToFile("file3", "333"); + + // Make sure the default file still exists. + assertTrue(mInstance.injectDefaultValuesFilename().exists()); + + // Simulate a clean boot. + mInstance.getDefaultValuesForTest().clear(); + assertEquals(0, mInstance.getDefaultValuesForTest().size()); + + mInstance.systemReady(/*runtimeRestarted=*/ false); + + // Default is empty, and the file is gone. + assertEquals(0, mInstance.getDefaultValuesForTest().size()); + assertFalse(mInstance.injectDefaultValuesFilename().exists()); + + // No WTF should have happened. + veriryWtf(0); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java index 503e1ac4563d..fdcf57b18241 100644 --- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java @@ -23,6 +23,7 @@ import org.junit.runner.RunWith; import android.app.ActivityManager.TaskDescription; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Debug; import android.platform.test.annotations.Presubmit; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; @@ -339,11 +340,8 @@ public class WindowFrameTests extends WindowTestsBase { w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); w.calculatePolicyCrop(policyCrop); - // If we were above system decor we wouldnt' get any cropping though - w.mLayer = sWm.mSystemDecorLayer + 1; - w.calculatePolicyCrop(policyCrop); - assertRect(policyCrop, 0, 0, logicalWidth, logicalHeight); - w.mLayer = 1; + assertRect(policyCrop, 0, cf.top, logicalWidth, cf.bottom); + dcf.setEmpty(); // Likewise with no decor frame we would get no crop w.computeFrameLw(pf, df, of, cf, vf, dcf, sf, null); diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java index f7c4b1f51c46..04f5e5e919aa 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java @@ -42,6 +42,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; /** * Tests for the {@link WindowLayersController} class. @@ -185,7 +188,6 @@ public class ZOrderingTests extends WindowTestsBase { // target. assertWindowLayerGreaterThan(mTransaction, mImeWindow, mChildAppWindowAbove); assertWindowLayerGreaterThan(mTransaction, mImeWindow, mAppWindow); - assertWindowLayerGreaterThan(mTransaction, mImeWindow, mDockedDividerWindow); assertWindowLayerGreaterThan(mTransaction, mNavBarWindow, mImeWindow); assertWindowLayerGreaterThan(mTransaction, mStatusBarWindow, mImeWindow); @@ -324,4 +326,21 @@ public class ZOrderingTests extends WindowTestsBase { assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow); assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow); } + + @Test + public void testAssignWindowLayers_ForSysUiPanels() throws Exception { + final WindowState navBarPanel = + createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel"); + final WindowState statusBarPanel = + createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel"); + final WindowState statusBarSubPanel = + createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel"); + mDisplayContent.assignChildLayers(mTransaction); + + // Ime should be above all app windows and below system windows if it is targeting an app + // window. + assertWindowLayerGreaterThan(mTransaction, navBarPanel, mNavBarWindow); + assertWindowLayerGreaterThan(mTransaction, statusBarPanel, mStatusBarWindow); + assertWindowLayerGreaterThan(mTransaction, statusBarSubPanel, statusBarPanel); + } } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index d359b7045007..7bea8a11133b 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -255,6 +255,7 @@ public final class UsbAlsaManager { } private void alsaFileAdded(String name) { + Slog.i(TAG, "alsaFileAdded(" + name + ")"); int type = AlsaDevice.TYPE_UNKNOWN; int card = -1, device = -1; diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java index 9bc9cd04957c..dd2e19297272 100644 --- a/services/usb/java/com/android/server/usb/UsbHostManager.java +++ b/services/usb/java/com/android/server/usb/UsbHostManager.java @@ -19,13 +19,8 @@ package com.android.server.usb; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; -import android.hardware.usb.UsbConfiguration; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.text.TextUtils; @@ -37,7 +32,6 @@ import com.android.server.usb.descriptors.UsbDescriptorParser; import com.android.server.usb.descriptors.report.TextReportCanvas; import com.android.server.usb.descriptors.tree.UsbDescriptorsTree; -import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -50,28 +44,23 @@ public class UsbHostManager { private final Context mContext; - // contains all connected USB devices - private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); - // USB busses to exclude from USB host support private final String[] mHostBlacklist; - private final Object mLock = new Object(); - - private UsbDevice mNewDevice; - private UsbConfiguration mNewConfiguration; - private UsbInterface mNewInterface; - private ArrayList<UsbConfiguration> mNewConfigurations; - private ArrayList<UsbInterface> mNewInterfaces; - private ArrayList<UsbEndpoint> mNewEndpoints; - private final UsbAlsaManager mUsbAlsaManager; private final UsbSettingsManager mSettingsManager; + private final Object mLock = new Object(); @GuardedBy("mLock") + // contains all connected USB devices + private final HashMap<String, UsbDevice> mDevices = new HashMap<>(); + + private Object mSettingsLock = new Object(); + @GuardedBy("mSettingsLock") private UsbProfileGroupSettingsManager mCurrentSettings; - @GuardedBy("mLock") + private Object mHandlerLock = new Object(); + @GuardedBy("mHandlerLock") private ComponentName mUsbDeviceConnectionHandler; public UsbHostManager(Context context, UsbAlsaManager alsaManager, @@ -91,33 +80,33 @@ public class UsbHostManager { } public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) { - synchronized (mLock) { + synchronized (mSettingsLock) { mCurrentSettings = settings; } } private UsbProfileGroupSettingsManager getCurrentUserSettings() { - synchronized (mLock) { + synchronized (mSettingsLock) { return mCurrentSettings; } } public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) { - synchronized (mLock) { + synchronized (mHandlerLock) { mUsbDeviceConnectionHandler = usbDeviceConnectionHandler; } } private @Nullable ComponentName getUsbDeviceConnectionHandler() { - synchronized (mLock) { + synchronized (mHandlerLock) { return mUsbDeviceConnectionHandler; } } - private boolean isBlackListed(String deviceName) { + private boolean isBlackListed(String deviceAddress) { int count = mHostBlacklist.length; for (int i = 0; i < count; i++) { - if (deviceName.startsWith(mHostBlacklist[i])) { + if (deviceAddress.startsWith(mHostBlacklist[i])) { return true; } } @@ -136,166 +125,73 @@ public class UsbHostManager { } /* Called from JNI in monitorUsbHostBus() to report new USB devices - Returns true if successful, in which case the JNI code will continue adding configurations, - interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors - have been processed + Returns true if successful, i.e. the USB Audio device descriptors are + correctly parsed and the unique device is added to the audio device list. */ @SuppressWarnings("unused") - private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID, - int deviceClass, int deviceSubclass, int deviceProtocol, - String manufacturerName, String productName, int version, String serialNumber) { - + private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass, + byte[] descriptors) { if (DEBUG) { - Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")"); - // Audio Class Codes: - // Audio: 0x01 - // Audio Subclass Codes: - // undefined: 0x00 - // audio control: 0x01 - // audio streaming: 0x02 - // midi streaming: 0x03 - - // some useful debugging info - Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:" - + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol); + Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start"); } - // OK this is non-obvious, but true. One can't tell if the device being attached is even - // potentially an audio device without parsing the interface descriptors, so punt on any - // such test until endUsbDeviceAdded() when we have that info. - - if (isBlackListed(deviceName) || - isBlackListed(deviceClass, deviceSubclass)) { + // check class/subclass first as it is more likely to be blacklisted + if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) { + if (DEBUG) { + Slog.d(TAG, "device is black listed"); + } return false; } synchronized (mLock) { - if (mDevices.get(deviceName) != null) { - Slog.w(TAG, "device already on mDevices list: " + deviceName); + if (mDevices.get(deviceAddress) != null) { + Slog.w(TAG, "device already on mDevices list: " + deviceAddress); + //TODO If this is the same peripheral as is being connected, replace + // it with the new connection. return false; } - if (mNewDevice != null) { - Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded"); - return false; - } - - // Create version string in "%.%" format - String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF); + UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress); + if (parser.parseDescriptors(descriptors)) { - mNewDevice = new UsbDevice(deviceName, vendorID, productID, - deviceClass, deviceSubclass, deviceProtocol, - manufacturerName, productName, versionString, serialNumber); - - mNewConfigurations = new ArrayList<>(); - mNewInterfaces = new ArrayList<>(); - mNewEndpoints = new ArrayList<>(); - } - - return true; - } - - /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbConfiguration(int id, String name, int attributes, int maxPower) { - if (mNewConfiguration != null) { - mNewConfiguration.setInterfaces( - mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); - mNewInterfaces.clear(); - } - - mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower); - mNewConfigurations.add(mNewConfiguration); - } - - /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbInterface(int id, String name, int altSetting, - int Class, int subClass, int protocol) { - if (mNewInterface != null) { - mNewInterface.setEndpoints( - mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); - mNewEndpoints.clear(); - } - - mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol); - mNewInterfaces.add(mNewInterface); - } - - /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device - currently being added. Returns true if successful, false in case of error. - */ - @SuppressWarnings("unused") - private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) { - mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval)); - } - - /* Called from JNI in monitorUsbHostBus() to finish adding a new device */ - @SuppressWarnings("unused") - private void endUsbDeviceAdded() { - if (DEBUG) { - Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()"); - } - if (mNewInterface != null) { - mNewInterface.setEndpoints( - mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()])); - } - if (mNewConfiguration != null) { - mNewConfiguration.setInterfaces( - mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()])); - } - - - synchronized (mLock) { - if (mNewDevice != null) { - mNewDevice.setConfigurations( - mNewConfigurations.toArray( - new UsbConfiguration[mNewConfigurations.size()])); - mDevices.put(mNewDevice.getDeviceName(), mNewDevice); - Slog.d(TAG, "Added device " + mNewDevice); + UsbDevice newDevice = parser.toAndroidUsbDevice(); + mDevices.put(deviceAddress, newDevice); // It is fine to call this only for the current user as all broadcasts are sent to // all profiles of the user and the dialogs should only show once. ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler(); if (usbDeviceConnectionHandler == null) { - getCurrentUserSettings().deviceAttached(mNewDevice); + getCurrentUserSettings().deviceAttached(newDevice); } else { - getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice, + getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice, usbDeviceConnectionHandler); } - // deviceName is something like: "/dev/bus/usb/001/001" - UsbDescriptorParser parser = new UsbDescriptorParser(); - boolean isInputHeadset = false; - boolean isOutputHeadset = false; - if (parser.parseDevice(mNewDevice.getDeviceName())) { - isInputHeadset = parser.isInputHeadset(); - isOutputHeadset = parser.isOutputHeadset(); - Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset - + " , out: " + isOutputHeadset + "]"); - } - mUsbAlsaManager.usbDeviceAdded(mNewDevice, - isInputHeadset, isOutputHeadset); + + // Headset? + boolean isInputHeadset = parser.isInputHeadset(); + boolean isOutputHeadset = parser.isOutputHeadset(); + Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset + + " , out: " + isOutputHeadset + "]"); + + mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset); } else { - Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded"); + Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress); + return false; } - mNewDevice = null; - mNewConfigurations = null; - mNewInterfaces = null; - mNewEndpoints = null; - mNewConfiguration = null; - mNewInterface = null; } + + if (DEBUG) { + Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end"); + } + + return true; } /* Called from JNI in monitorUsbHostBus to report USB device removal */ @SuppressWarnings("unused") - private void usbDeviceRemoved(String deviceName) { + private void usbDeviceRemoved(String deviceAddress) { synchronized (mLock) { - UsbDevice device = mDevices.remove(deviceName); + UsbDevice device = mDevices.remove(deviceAddress); if (device != null) { mUsbAlsaManager.usbDeviceRemoved(device); mSettingsManager.usbDeviceRemoved(device); @@ -323,32 +219,35 @@ public class UsbHostManager { } /* Opens the specified USB device */ - public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings, + public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings, String packageName, int uid) { synchronized (mLock) { - if (isBlackListed(deviceName)) { + if (isBlackListed(deviceAddress)) { throw new SecurityException("USB device is on a restricted bus"); } - UsbDevice device = mDevices.get(deviceName); + UsbDevice device = mDevices.get(deviceAddress); if (device == null) { // if it is not in mDevices, it either does not exist or is blacklisted throw new IllegalArgumentException( - "device " + deviceName + " does not exist or is restricted"); + "device " + deviceAddress + " does not exist or is restricted"); } + settings.checkPermission(device, packageName, uid); - return nativeOpenDevice(deviceName); + return nativeOpenDevice(deviceAddress); } } public void dump(IndentingPrintWriter pw) { + pw.println("USB Host State:"); + synchronized (mHandlerLock) { + if (mUsbDeviceConnectionHandler != null) { + pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); + } + } synchronized (mLock) { - pw.println("USB Host State:"); for (String name : mDevices.keySet()) { pw.println(" " + name + ": " + mDevices.get(name)); } - if (mUsbDeviceConnectionHandler != null) { - pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler); - } Collection<UsbDevice> devices = mDevices.values(); if (devices.size() != 0) { @@ -356,17 +255,12 @@ public class UsbHostManager { for (UsbDevice device : devices) { StringBuilder stringBuilder = new StringBuilder(); - UsbDescriptorParser parser = new UsbDescriptorParser(); - if (parser.parseDevice(device.getDeviceName())) { + UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName()); + if (parser.parseDevice()) { UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree(); descriptorTree.parse(parser); - UsbManager usbManager = - (UsbManager) mContext.getSystemService(Context.USB_SERVICE); - UsbDeviceConnection connection = usbManager.openDevice(device); - - descriptorTree.report(new TextReportCanvas(connection, stringBuilder)); - connection.close(); + descriptorTree.report(new TextReportCanvas(parser, stringBuilder)); stringBuilder.append("isHeadset[in: " + parser.isInputHeadset() + " , out: " + parser.isOutputHeadset() + "]"); @@ -382,5 +276,5 @@ public class UsbHostManager { } private native void monitorUsbHostBus(); - private native ParcelFileDescriptor nativeOpenDevice(String deviceName); + private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java index a35b46318e23..7763150ad6b3 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java @@ -32,7 +32,7 @@ public final class Usb10ACHeader extends UsbACHeaderInterface { // numbers associate with this endpoint private byte mControls; // Vers 2.0 thing - public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) { + public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) { super(length, type, subtype, subclass, spec); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java index 2363c4dd8009..75531d172761 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java @@ -32,7 +32,7 @@ public final class Usb10ACInputTerminal extends UsbACTerminal { private byte mChannelNames; // 10:1 Unused (0x00) private byte mTerminal; // 11:1 Unused (0x00) - public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) { + public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java index d3486643ede2..c7634ba7dc63 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java @@ -30,7 +30,7 @@ public final class Usb10ACMixerUnit extends UsbACMixerUnit { private byte[] mControls; // bitmasks of which controls are present for each channel private byte mNameID; // string descriptor ID of mixer name - public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java index 9f2f09ec146c..468ae57135cb 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java @@ -28,7 +28,7 @@ public final class Usb10ACOutputTerminal extends UsbACTerminal { private byte mSourceID; // 7:1 From Input Terminal. (0x01) private byte mTerminal; // 8:1 Unused. - public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) { + public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java index 1523bb528a03..1d8498ae33ff 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java @@ -33,7 +33,7 @@ public final class Usb10ASFormatI extends UsbASFormat { // min & max rates otherwise mSamFreqType rates. // All 3-byte values. All rates in Hz - public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java index b1e7680ee1b9..3c45790a8bfc 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java @@ -38,7 +38,7 @@ public final class Usb10ASFormatII extends UsbASFormat { // the min & max rates. otherwise mSamFreqType rates. // All 3-byte values. All rates in Hz - public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java index 2d4f604ed1a1..4fbbb2137e39 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java @@ -34,7 +34,7 @@ public final class Usb10ASGeneral extends UsbACInterface { private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate // with this interface. - public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) { + public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java index eefae3d51b3f..fe1b5023958c 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java @@ -29,7 +29,7 @@ public final class Usb20ACHeader extends UsbACHeaderInterface { // See audio20.pdf Appendix A.7, “Audio Function Category Codes.” private byte mControls; // 8:1 See audio20.pdf Table 4-5. - public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) { + public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) { super(length, type, subtype, subclass, spec); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java index 3e2ac39c0aca..ee1b32c1b544 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java @@ -39,7 +39,7 @@ public final class Usb20ACInputTerminal extends UsbACTerminal { private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the // Input Terminal. - public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) { + public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java index 1b267a67752b..ab965856bb5d 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java @@ -33,7 +33,7 @@ public final class Usb20ACMixerUnit extends UsbACMixerUnit { private byte mNameID; // 12+p+N:1 Index of a string descriptor, describing the // Mixer Unit. - public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java index 67478aad8a59..20a97af49bc5 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java @@ -34,7 +34,7 @@ public final class Usb20ACOutputTerminal extends UsbACTerminal { private int mControls; // 9:2 - see Audio20.pdf Table 4-10 private byte mTerminalID; // 11:1 - Index of a string descriptor, describing the - public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) { + public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java index c03199619e74..7310a3e2c5ab 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java @@ -31,7 +31,7 @@ public final class Usb20ASFormatI extends UsbASFormat { private byte mBitResolution; // 5:1 The number of effectively used bits from // the available bits in an audio subslot. - public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java index dc44ff063964..fe8874328e77 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java @@ -34,7 +34,7 @@ public final class Usb20ASFormatII extends UsbASFormat { /** * TBD */ - public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java index b44a216703f8..b0ba02ff22d3 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java @@ -31,7 +31,7 @@ public final class Usb20ASFormatIII extends UsbASFormat { private byte mBitResolution; // 5:1 The number of effectively used bits from // the available bits in an audio subframe. - public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) { + public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) { super(length, type, subtype, formatType, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java index 18d48a009098..de2073882e3a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java +++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java @@ -41,7 +41,7 @@ public final class Usb20ASGeneral extends UsbACInterface { private byte mChannelNames; // 15:1 Index of a string descriptor, describing the // name of the first physical channel. - public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) { + public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java index 6e1ce07536c5..409e605c3c2f 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java @@ -38,7 +38,7 @@ public class UsbACAudioControlEndpoint extends UsbACEndpoint { static final byte ATTRIBSMASK_SYNC = 0x0C; static final byte ATTRIBMASK_TRANS = 0x03; - public UsbACAudioControlEndpoint(int length, byte type, byte subclass) { + public UsbACAudioControlEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java index d35190298df6..e63bb74abdf7 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java @@ -24,7 +24,7 @@ public class UsbACAudioStreamEndpoint extends UsbACEndpoint { private static final String TAG = "UsbACAudioStreamEndpoint"; //TODO data fields... - public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) { + public UsbACAudioStreamEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java index 4a6839d943ff..7ebccf39868c 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java @@ -25,16 +25,16 @@ import android.util.Log; abstract class UsbACEndpoint extends UsbDescriptor { private static final String TAG = "UsbACEndpoint"; - protected final byte mSubclass; // from the mSubclass member of the "enclosing" - // Interface Descriptor, not the stream. - protected byte mSubtype; // 2:1 HEADER descriptor subtype + protected final int mSubclass; // from the mSubclass member of the "enclosing" + // Interface Descriptor, not the stream. + protected byte mSubtype; // 2:1 HEADER descriptor subtype - UsbACEndpoint(int length, byte type, byte subclass) { + UsbACEndpoint(int length, byte type, int subclass) { super(length, type); mSubclass = subclass; } - public byte getSubclass() { + public int getSubclass() { return mSubclass; } @@ -52,7 +52,7 @@ abstract class UsbACEndpoint extends UsbDescriptor { public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, int length, byte type) { UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); - byte subClass = interfaceDesc.getUsbSubclass(); + int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: return new UsbACAudioControlEndpoint(length, type, subClass); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java index ab3903b402d9..2c7ef7988385 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java @@ -46,7 +46,7 @@ public final class UsbACFeatureUnit extends UsbACInterface { // logical channel private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit. - public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java index 01a355e2c6e4..88d026eb02a3 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java @@ -31,7 +31,7 @@ public abstract class UsbACHeaderInterface extends UsbACInterface { // of this descriptor header and all Unit and Terminal descriptors. public UsbACHeaderInterface( - int length, byte type, byte subtype, byte subclass, int adcRelease) { + int length, byte type, byte subtype, int subclass, int adcRelease) { super(length, type, subtype, subclass); mADCRelease = adcRelease; } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java index df6c53fa9f52..38c12a1f6c16 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java @@ -78,10 +78,10 @@ public abstract class UsbACInterface extends UsbDescriptor { public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005; protected final byte mSubtype; // 2:1 HEADER descriptor subtype - protected final byte mSubclass; // from the mSubclass member of the + protected final int mSubclass; // from the mSubclass member of the // "enclosing" Interface Descriptor - public UsbACInterface(int length, byte type, byte subtype, byte subclass) { + public UsbACInterface(int length, byte type, byte subtype, int subclass) { super(length, type); mSubtype = subtype; mSubclass = subclass; @@ -91,12 +91,12 @@ public abstract class UsbACInterface extends UsbDescriptor { return mSubtype; } - public byte getSubclass() { + public int getSubclass() { return mSubclass; } private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, byte subtype, byte subClass) { + ByteStream stream, int length, byte type, byte subtype, int subClass) { switch (subtype) { case ACI_HEADER: { @@ -157,7 +157,7 @@ public abstract class UsbACInterface extends UsbDescriptor { } private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, byte subtype, byte subClass) { + ByteStream stream, int length, byte type, byte subtype, int subClass) { //int spec = parser.getUsbSpec(); int acInterfaceSpec = parser.getACInterfaceSpec(); switch (subtype) { @@ -182,7 +182,7 @@ public abstract class UsbACInterface extends UsbDescriptor { } private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type, - byte subtype, byte subClass) { + byte subtype, int subClass) { switch (subtype) { case MSI_HEADER: return new UsbMSMidiHeader(length, type, subtype, subClass); @@ -212,7 +212,7 @@ public abstract class UsbACInterface extends UsbDescriptor { int length, byte type) { byte subtype = stream.getByte(); UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface(); - byte subClass = interfaceDesc.getUsbSubclass(); + int subClass = interfaceDesc.getUsbSubclass(); switch (subClass) { case AUDIO_AUDIOCONTROL: return allocAudioControlDescriptor( @@ -236,7 +236,7 @@ public abstract class UsbACInterface extends UsbDescriptor { public void report(ReportCanvas canvas) { super.report(canvas); - byte subClass = getSubclass(); + int subClass = getSubclass(); String subClassName = UsbStrings.getACInterfaceSubclassName(subClass); byte subtype = getSubtype(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java index 9e00a7976dfd..bd027aebff45 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java @@ -22,7 +22,7 @@ package com.android.server.usb.descriptors; public final class UsbACInterfaceUnparsed extends UsbACInterface { private static final String TAG = "UsbACInterfaceUnparsed"; - public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) { + public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java index 9c314575ccc4..42ee88922edd 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java @@ -28,7 +28,7 @@ public final class UsbACMidiEndpoint extends UsbACEndpoint { private byte mNumJacks; private byte[] mJackIds; - public UsbACMidiEndpoint(int length, byte type, byte subclass) { + public UsbACMidiEndpoint(int length, byte type, int subclass) { super(length, type, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java index 88faed962a54..606fa2b4c7f8 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java @@ -24,7 +24,7 @@ public class UsbACMixerUnit extends UsbACInterface { // are connected. protected byte mNumOutputs; // The number of output channels - public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java index b16bc575e806..4644fe14969f 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java @@ -32,7 +32,7 @@ public final class UsbACSelectorUnit extends UsbACInterface { // Input Pin of this Selector Unit is connected. private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit. - public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) { + public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) { super(length, type, subtype, subClass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java index 2836508581d8..36139d6c6900 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java @@ -32,7 +32,7 @@ public abstract class UsbACTerminal extends UsbACInterface { protected int mTerminalType; // 4:2 USB Streaming. (0x0101) protected byte mAssocTerminal; // 6:1 Unused (0x00) - public UsbACTerminal(int length, byte type, byte subtype, byte subclass) { + public UsbACTerminal(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java index 7a92f9e197ec..5e515a147f33 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java @@ -40,7 +40,7 @@ public class UsbASFormat extends UsbACInterface { public static final byte EXT_FORMAT_TYPE_II = (byte) 0x82; public static final byte EXT_FORMAT_TYPE_III = (byte) 0x83; - public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) { + public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) { super(length, type, subtype, mSubclass); mFormatType = formatType; } @@ -66,8 +66,8 @@ public class UsbASFormat extends UsbACInterface { * stream. */ public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser, - ByteStream stream, int length, byte type, - byte subtype, byte subclass) { + ByteStream stream, int length, byte type, + byte subtype, int subclass) { byte formatType = stream.getByte(); int acInterfaceSpec = parser.getACInterfaceSpec(); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java index 75279c61c4f0..993778fa3991 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java @@ -15,8 +15,13 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbInterface; + import com.android.server.usb.descriptors.report.ReportCanvas; +import java.util.ArrayList; + /** * @hide * An USB Config Descriptor. @@ -25,15 +30,18 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbConfigDescriptor extends UsbDescriptor { private static final String TAG = "UsbConfigDescriptor"; - private int mTotalLength; // 2:2 Total length in bytes of data returned + private int mTotalLength; // 2:2 Total length in bytes of data returned private byte mNumInterfaces; // 4:1 Number of Interfaces - private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration - private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration - private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered) - // D6 Self Powered - // D5 Remote Wakeup - // D4..0 Reserved, set to 0. - private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + private int mConfigValue; // 5:1 Value to use as an argument to select this configuration + private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration + private int mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered) + // D6 Self Powered + // D5 Remote Wakeup + // D4..0 Reserved, set to 0. + private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units + + private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors = + new ArrayList<UsbInterfaceDescriptor>(); UsbConfigDescriptor(int length, byte type) { super(length, type); @@ -48,7 +56,7 @@ public final class UsbConfigDescriptor extends UsbDescriptor { return mNumInterfaces; } - public byte getConfigValue() { + public int getConfigValue() { return mConfigValue; } @@ -56,22 +64,38 @@ public final class UsbConfigDescriptor extends UsbDescriptor { return mConfigIndex; } - public byte getAttribs() { + public int getAttribs() { return mAttribs; } - public byte getMaxPower() { + public int getMaxPower() { return mMaxPower; } + void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) { + mInterfaceDescriptors.add(interfaceDesc); + } + + UsbConfiguration toAndroid(UsbDescriptorParser parser) { + String name = parser.getDescriptorString(mConfigIndex); + UsbConfiguration config = new + UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower); + UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()]; + for (int index = 0; index < mInterfaceDescriptors.size(); index++) { + interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser); + } + config.setInterfaces(interfaces); + return config; + } + @Override public int parseRawDescriptors(ByteStream stream) { mTotalLength = stream.unpackUsbShort(); mNumInterfaces = stream.getByte(); - mConfigValue = stream.getByte(); + mConfigValue = stream.getUnsignedByte(); mConfigIndex = stream.getByte(); - mAttribs = stream.getByte(); - mMaxPower = stream.getByte(); + mAttribs = stream.getUnsignedByte(); + mMaxPower = stream.getUnsignedByte(); return mLength; } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java index 8c7565b790d2..3fc5fe320574 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java @@ -85,36 +85,36 @@ public abstract class UsbDescriptor implements Reporting { public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48 // Class IDs - public static final byte CLASSID_DEVICE = 0x00; - public static final byte CLASSID_AUDIO = 0x01; - public static final byte CLASSID_COM = 0x02; - public static final byte CLASSID_HID = 0x03; - // public static final byte CLASSID_??? = 0x04; - public static final byte CLASSID_PHYSICAL = 0x05; - public static final byte CLASSID_IMAGE = 0x06; - public static final byte CLASSID_PRINTER = 0x07; - public static final byte CLASSID_STORAGE = 0x08; - public static final byte CLASSID_HUB = 0x09; - public static final byte CLASSID_CDC_CONTROL = 0x0A; - public static final byte CLASSID_SMART_CARD = 0x0B; - //public static final byte CLASSID_??? = 0x0C; - public static final byte CLASSID_SECURITY = 0x0D; - public static final byte CLASSID_VIDEO = 0x0E; - public static final byte CLASSID_HEALTHCARE = 0x0F; - public static final byte CLASSID_AUDIOVIDEO = 0x10; - public static final byte CLASSID_BILLBOARD = 0x11; - public static final byte CLASSID_TYPECBRIDGE = 0x12; - public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC; - public static final byte CLASSID_WIRELESS = (byte) 0xE0; - public static final byte CLASSID_MISC = (byte) 0xEF; - public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE; - public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF; + public static final int CLASSID_DEVICE = 0x00; + public static final int CLASSID_AUDIO = 0x01; + public static final int CLASSID_COM = 0x02; + public static final int CLASSID_HID = 0x03; + // public static final int CLASSID_??? = 0x04; + public static final int CLASSID_PHYSICAL = 0x05; + public static final int CLASSID_IMAGE = 0x06; + public static final int CLASSID_PRINTER = 0x07; + public static final int CLASSID_STORAGE = 0x08; + public static final int CLASSID_HUB = 0x09; + public static final int CLASSID_CDC_CONTROL = 0x0A; + public static final int CLASSID_SMART_CARD = 0x0B; + //public static final int CLASSID_??? = 0x0C; + public static final int CLASSID_SECURITY = 0x0D; + public static final int CLASSID_VIDEO = 0x0E; + public static final int CLASSID_HEALTHCARE = 0x0F; + public static final int CLASSID_AUDIOVIDEO = 0x10; + public static final int CLASSID_BILLBOARD = 0x11; + public static final int CLASSID_TYPECBRIDGE = 0x12; + public static final int CLASSID_DIAGNOSTIC = 0xDC; + public static final int CLASSID_WIRELESS = 0xE0; + public static final int CLASSID_MISC = 0xEF; + public static final int CLASSID_APPSPECIFIC = 0xFE; + public static final int CLASSID_VENDSPECIFIC = 0xFF; // Audio Subclass codes - public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00; - public static final byte AUDIO_AUDIOCONTROL = 0x01; - public static final byte AUDIO_AUDIOSTREAMING = 0x02; - public static final byte AUDIO_MIDISTREAMING = 0x03; + public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00; + public static final int AUDIO_AUDIOCONTROL = 0x01; + public static final int AUDIO_AUDIOSTREAMING = 0x02; + public static final int AUDIO_MIDISTREAMING = 0x03; // Request IDs public static final int REQUEST_GET_STATUS = 0x00; diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java index ad7bde5c275e..6c6bd01316af 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java @@ -15,6 +15,7 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbDevice; import android.util.Log; import java.util.ArrayList; @@ -25,11 +26,16 @@ import java.util.ArrayList; */ public final class UsbDescriptorParser { private static final String TAG = "UsbDescriptorParser"; + private static final boolean DEBUG = false; + + private final String mDeviceAddr; // Descriptor Objects + private static final int DESCRIPTORS_ALLOC_SIZE = 128; private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>(); private UsbDeviceDescriptor mDeviceDescriptor; + private UsbConfigDescriptor mCurConfigDescriptor; private UsbInterfaceDescriptor mCurInterfaceDescriptor; // The AudioClass spec implemented by the AudioClass Interfaces @@ -37,7 +43,13 @@ public final class UsbDescriptorParser { // Obtained from the first AudioClass Header descriptor. private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0; - public UsbDescriptorParser() {} + public UsbDescriptorParser(String deviceAddr) { + mDeviceAddr = deviceAddr; + } + + public String getDeviceAddr() { + return mDeviceAddr; + } /** * @return the USB Spec value associated with the Device descriptor for the @@ -60,6 +72,18 @@ public final class UsbDescriptorParser { public int getACInterfaceSpec() { return mACInterfacesSpec; } + + private class UsbDescriptorsStreamFormatException extends Exception { + String mMessage; + UsbDescriptorsStreamFormatException(String message) { + mMessage = message; + } + + public String toString() { + return "Descriptor Stream Format Exception: " + mMessage; + } + } + /** * The probability (as returned by getHeadsetProbability() at which we conclude * the peripheral is a headset. @@ -67,7 +91,8 @@ public final class UsbDescriptorParser { private static final float IN_HEADSET_TRIGGER = 0.75f; private static final float OUT_HEADSET_TRIGGER = 0.75f; - private UsbDescriptor allocDescriptor(ByteStream stream) { + private UsbDescriptor allocDescriptor(ByteStream stream) + throws UsbDescriptorsStreamFormatException { stream.resetReadCount(); int length = stream.getUnsignedByte(); @@ -83,15 +108,38 @@ public final class UsbDescriptorParser { break; case UsbDescriptor.DESCRIPTORTYPE_CONFIG: - descriptor = new UsbConfigDescriptor(length, type); + descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type); + if (mDeviceDescriptor != null) { + mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor); + } else { + Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Config Descriptor found with no associated Device Descriptor!"); + } break; case UsbDescriptor.DESCRIPTORTYPE_INTERFACE: descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type); + if (mCurConfigDescriptor != null) { + mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor); + } else { + Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Interface Descriptor found with no associated Config Descriptor!"); + } break; case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT: descriptor = new UsbEndpointDescriptor(length, type); + if (mCurInterfaceDescriptor != null) { + mCurInterfaceDescriptor.addEndpointDescriptor( + (UsbEndpointDescriptor) descriptor); + } else { + Log.e(TAG, + "Endpoint Descriptor found with no associated Interface Descriptor!"); + throw new UsbDescriptorsStreamFormatException( + "Endpoint Descriptor found with no associated Interface Descriptor!"); + } break; /* @@ -144,8 +192,12 @@ public final class UsbDescriptorParser { /** * @hide */ - public void parseDescriptors(byte[] descriptors) { - mDescriptors.clear(); + public boolean parseDescriptors(byte[] descriptors) { + if (DEBUG) { + Log.d(TAG, "parseDescriptors() - start"); + } + // This will allow us to (probably) alloc mDescriptors just once. + mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE); ByteStream stream = new ByteStream(descriptors); while (stream.available() > 0) { @@ -173,21 +225,36 @@ public final class UsbDescriptorParser { } } } + if (DEBUG) { + Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors."); + } + return true; } /** * @hide */ - public boolean parseDevice(String deviceAddr) { - byte[] rawDescriptors = getRawDescriptors(deviceAddr); - if (rawDescriptors != null) { - parseDescriptors(rawDescriptors); - return true; - } - return false; + public boolean parseDevice() { + byte[] rawDescriptors = getRawDescriptors(); + + return rawDescriptors != null + ? parseDescriptors(rawDescriptors) : false; + } + + private byte[] getRawDescriptors() { + return getRawDescriptors_native(mDeviceAddr); + } + + private native byte[] getRawDescriptors_native(String deviceAddr); + + /** + * @hide + */ + public String getDescriptorString(int stringId) { + return getDescriptorString_native(mDeviceAddr, stringId); } - private native byte[] getRawDescriptors(String deviceAddr); + private native String getDescriptorString_native(String deviceAddr, int stringId); public int getParsingSpec() { return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0; @@ -200,6 +267,17 @@ public final class UsbDescriptorParser { /** * @hide */ + public UsbDevice toAndroidUsbDevice() { + if (mDeviceDescriptor == null) { + return null; + } + + return mDeviceDescriptor.toAndroid(this); + } + + /** + * @hide + */ public ArrayList<UsbDescriptor> getDescriptors(byte type) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { @@ -213,7 +291,7 @@ public final class UsbDescriptorParser { /** * @hide */ - public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) { + public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE @@ -235,7 +313,7 @@ public final class UsbDescriptorParser { /** * @hide */ - public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) { + public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) { ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>(); for (UsbDescriptor descriptor : mDescriptors) { if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) { @@ -355,8 +433,6 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isInputHeadset() { - // TEMP - Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%"); return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER; } @@ -410,8 +486,6 @@ public final class UsbDescriptorParser { * to count on the peripheral being a headset. */ public boolean isOutputHeadset() { - // TEMP - Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%"); return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER; } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java index d5cb89ea82e6..8e7f0fde1537 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java @@ -15,9 +15,14 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbConfiguration; +import android.hardware.usb.UsbDevice; + import com.android.server.usb.descriptors.report.ReportCanvas; import com.android.server.usb.descriptors.report.UsbStrings; +import java.util.ArrayList; + /** * @hide * A USB Device Descriptor. @@ -31,9 +36,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { public static final int USBSPEC_2_0 = 0x0200; private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD - private byte mDevClass; // 4:1 class code - private byte mDevSubClass; // 5:1 subclass code - private byte mProtocol; // 6:1 protocol + private int mDevClass; // 4:1 class code + private int mDevSubClass; // 5:1 subclass code + private int mProtocol; // 6:1 protocol private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint. // Valid Sizes are 8, 16, 32, 64 private int mVendorID; // 8:2 vendor ID @@ -44,6 +49,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor private byte mNumConfigs; // 17:1 Number of Possible Configurations + private ArrayList<UsbConfigDescriptor> mConfigDescriptors = + new ArrayList<UsbConfigDescriptor>(); + UsbDeviceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 1; @@ -53,15 +61,15 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { return mSpec; } - public byte getDevClass() { + public int getDevClass() { return mDevClass; } - public byte getDevSubClass() { + public int getDevSubClass() { return mDevSubClass; } - public byte getProtocol() { + public int getProtocol() { return mProtocol; } @@ -97,12 +105,41 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { return mNumConfigs; } + void addConfigDescriptor(UsbConfigDescriptor config) { + mConfigDescriptors.add(config); + } + + /** + * @hide + */ + public UsbDevice toAndroid(UsbDescriptorParser parser) { + String mfgName = parser.getDescriptorString(mMfgIndex); + String prodName = parser.getDescriptorString(mProductIndex); + + // Create version string in "%.%" format + String versionString = + Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF); + String serialStr = parser.getDescriptorString(mSerialNum); + + UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID, + mDevClass, mDevSubClass, + mProtocol, mfgName, prodName, + versionString, serialStr); + UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()]; + for (int index = 0; index < mConfigDescriptors.size(); index++) { + configs[index] = mConfigDescriptors.get(index).toAndroid(parser); + } + device.setConfigurations(configs); + + return device; + } + @Override public int parseRawDescriptors(ByteStream stream) { mSpec = stream.unpackUsbShort(); - mDevClass = stream.getByte(); - mDevSubClass = stream.getByte(); - mProtocol = stream.getByte(); + mDevClass = stream.getUnsignedByte(); + mDevSubClass = stream.getUnsignedByte(); + mProtocol = stream.getUnsignedByte(); mPacketSize = stream.getByte(); mVendorID = stream.unpackUsbShort(); mProductID = stream.unpackUsbShort(); @@ -124,9 +161,9 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { int spec = getSpec(); canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec)); - byte devClass = getDevClass(); + int devClass = getDevClass(); String classStr = UsbStrings.getClassName(devClass); - byte devSubClass = getDevSubClass(); + int devSubClass = getDevSubClass(); String subClasStr = UsbStrings.getClassName(devSubClass); canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass" + devSubClass + ": " + subClasStr); @@ -134,12 +171,11 @@ public final class UsbDeviceDescriptor extends UsbDescriptor { + " Product ID: " + ReportCanvas.getHexString(getProductID()) + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease())); + UsbDescriptorParser parser = canvas.getParser(); byte mfgIndex = getMfgIndex(); - String manufacturer = - UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex); + String manufacturer = parser.getDescriptorString(mfgIndex); byte productIndex = getProductIndex(); - String product = - UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex); + String product = parser.getDescriptorString(productIndex); canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer + " Product " + productIndex + ": " + product); diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java index 6322fbe8b45b..11302380b589 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java @@ -15,6 +15,8 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbEndpoint; + import com.android.server.usb.descriptors.report.ReportCanvas; /** @@ -25,16 +27,16 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public class UsbEndpointDescriptor extends UsbDescriptor { private static final String TAG = "UsbEndpointDescriptor"; - public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111; - public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000; - public static final byte DIRECTION_OUTPUT = 0x00; - public static final byte DIRECTION_INPUT = (byte) 0x80; + public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111; + public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000; + public static final int DIRECTION_OUTPUT = 0x0000; + public static final int DIRECTION_INPUT = (byte) 0x0080; - public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011; - public static final byte TRANSTYPE_CONTROL = 0x00; - public static final byte TRANSTYPE_ISO = 0x01; - public static final byte TRANSTYPE_BULK = 0x02; - public static final byte TRANSTYPE_INTERRUPT = 0x03; + public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011; + public static final int TRANSTYPE_CONTROL = 0x00; + public static final int TRANSTYPE_ISO = 0x01; + public static final int TRANSTYPE_BULK = 0x02; + public static final int TRANSTYPE_INTERRUPT = 0x03; public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100; public static final byte SYNCTYPE_NONE = 0b00000000; @@ -42,18 +44,18 @@ public class UsbEndpointDescriptor extends UsbDescriptor { public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000; public static final byte SYNCTYPE_RESERVED = 0b00001100; - public static final byte MASK_ATTRIBS_USEAGE = 0b00110000; - public static final byte USEAGE_DATA = 0b00000000; - public static final byte USEAGE_FEEDBACK = 0b00010000; - public static final byte USEAGE_EXPLICIT = 0b00100000; - public static final byte USEAGE_RESERVED = 0b00110000; + public static final int MASK_ATTRIBS_USEAGE = 0b00110000; + public static final int USEAGE_DATA = 0b00000000; + public static final int USEAGE_FEEDBACK = 0b00010000; + public static final int USEAGE_EXPLICIT = 0b00100000; + public static final int USEAGE_RESERVED = 0b00110000; - private byte mEndpointAddress; // 2:1 Endpoint Address + private int mEndpointAddress; // 2:1 Endpoint Address // Bits 0..3b Endpoint Number. // Bits 4..6b Reserved. Set to Zero // Bits 7 Direction 0 = Out, 1 = In // (Ignored for Control Endpoints) - private byte mAttributes; // 3:1 Various flags + private int mAttributes; // 3:1 Various flags // Bits 0..1 Transfer Type: // 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt // Bits 2..7 are reserved. If Isochronous endpoint, @@ -69,7 +71,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { // 11: Reserved private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of // sending or receiving - private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in + private int mInterval; // 6:1 Interval for polling endpoint data transfers. Value in // frame counts. // Ignored for Bulk & Control Endpoints. Isochronous must equal // 1 and field may range from 1 to 255 for interrupt endpoints. @@ -81,11 +83,11 @@ public class UsbEndpointDescriptor extends UsbDescriptor { mHierarchyLevel = 4; } - public byte getEndpointAddress() { + public int getEndpointAddress() { return mEndpointAddress; } - public byte getAttributes() { + public int getAttributes() { return mAttributes; } @@ -93,7 +95,7 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mPacketSize; } - public byte getInterval() { + public int getInterval() { return mInterval; } @@ -105,12 +107,16 @@ public class UsbEndpointDescriptor extends UsbDescriptor { return mSyncAddress; } + /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) { + return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval); + } + @Override public int parseRawDescriptors(ByteStream stream) { - mEndpointAddress = stream.getByte(); - mAttributes = stream.getByte(); + mEndpointAddress = stream.getUnsignedByte(); + mAttributes = stream.getUnsignedByte(); mPacketSize = stream.unpackUsbShort(); - mInterval = stream.getByte(); + mInterval = stream.getUnsignedByte(); if (mLength == 9) { mRefresh = stream.getByte(); mSyncAddress = stream.getByte(); @@ -124,13 +130,13 @@ public class UsbEndpointDescriptor extends UsbDescriptor { canvas.openList(); - byte address = getEndpointAddress(); + int address = getEndpointAddress(); canvas.writeListItem("Address: " + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS) + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION) == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]")); - byte attributes = getAttributes(); + int attributes = getAttributes(); canvas.openListItem(); canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " "); switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) { diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java index 4eef6caf5a60..d87b1afba79a 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java @@ -15,9 +15,14 @@ */ package com.android.server.usb.descriptors; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; + import com.android.server.usb.descriptors.report.ReportCanvas; import com.android.server.usb.descriptors.report.UsbStrings; +import java.util.ArrayList; + /** * @hide * A common super-class for all USB Interface Descritor subtypes. @@ -26,14 +31,17 @@ import com.android.server.usb.descriptors.report.UsbStrings; public class UsbInterfaceDescriptor extends UsbDescriptor { private static final String TAG = "UsbInterfaceDescriptor"; - protected byte mInterfaceNumber; // 2:1 Number of Interface + protected int mInterfaceNumber; // 2:1 Number of Interface protected byte mAlternateSetting; // 3:1 Value used to select alternative setting protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface - protected byte mUsbClass; // 5:1 Class Code - protected byte mUsbSubclass; // 6:1 Subclass Code - protected byte mProtocol; // 7:1 Protocol Code + protected int mUsbClass; // 5:1 Class Code + protected int mUsbSubclass; // 6:1 Subclass Code + protected int mProtocol; // 7:1 Protocol Code protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface + private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors = + new ArrayList<UsbEndpointDescriptor>(); + UsbInterfaceDescriptor(int length, byte type) { super(length, type); mHierarchyLevel = 3; @@ -41,18 +49,18 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { @Override public int parseRawDescriptors(ByteStream stream) { - mInterfaceNumber = stream.getByte(); + mInterfaceNumber = stream.getUnsignedByte(); mAlternateSetting = stream.getByte(); mNumEndpoints = stream.getByte(); - mUsbClass = stream.getByte(); - mUsbSubclass = stream.getByte(); - mProtocol = stream.getByte(); + mUsbClass = stream.getUnsignedByte(); + mUsbSubclass = stream.getUnsignedByte(); + mProtocol = stream.getUnsignedByte(); mDescrIndex = stream.getByte(); return mLength; } - public byte getInterfaceNumber() { + public int getInterfaceNumber() { return mInterfaceNumber; } @@ -64,15 +72,15 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { return mNumEndpoints; } - public byte getUsbClass() { + public int getUsbClass() { return mUsbClass; } - public byte getUsbSubclass() { + public int getUsbSubclass() { return mUsbSubclass; } - public byte getProtocol() { + public int getProtocol() { return mProtocol; } @@ -80,13 +88,29 @@ public class UsbInterfaceDescriptor extends UsbDescriptor { return mDescrIndex; } + void addEndpointDescriptor(UsbEndpointDescriptor endpoint) { + mEndpointDescriptors.add(endpoint); + } + + UsbInterface toAndroid(UsbDescriptorParser parser) { + String name = parser.getDescriptorString(mDescrIndex); + UsbInterface ntrface = new UsbInterface( + mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol); + UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()]; + for (int index = 0; index < mEndpointDescriptors.size(); index++) { + endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser); + } + ntrface.setEndpoints(endpoints); + return ntrface; + } + @Override public void report(ReportCanvas canvas) { super.report(canvas); - byte usbClass = getUsbClass(); - byte usbSubclass = getUsbSubclass(); - byte protocol = getProtocol(); + int usbClass = getUsbClass(); + int usbSubclass = getUsbSubclass(); + int protocol = getProtocol(); String className = UsbStrings.getClassName(usbClass); String subclassName = ""; if (usbClass == UsbDescriptor.CLASSID_AUDIO) { diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java index 85a3e6802ff7..d0ca6db87d38 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiHeader extends UsbACInterface { private static final String TAG = "UsbMSMidiHeader"; - public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) { + public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java index 1d5cbf2b5c99..7df7cfc6a5e3 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiInputJack extends UsbACInterface { private static final String TAG = "UsbMSMidiInputJack"; - UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) { + UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java index 9f50240a94ca..1879ac096abd 100644 --- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java +++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java @@ -25,7 +25,7 @@ import com.android.server.usb.descriptors.report.ReportCanvas; public final class UsbMSMidiOutputJack extends UsbACInterface { private static final String TAG = "UsbMSMidiOutputJack"; - public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) { + public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) { super(length, type, subtype, subclass); } diff --git a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java index 99ebccade735..adfc5143bc09 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -32,8 +32,8 @@ public final class HTMLReportCanvas extends ReportCanvas { * from the USB device. * @param stringBuilder Generated output gets written into this object. */ - public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) { - super(connection); + public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) { + super(parser); mStringBuilder = stringBuilder; } diff --git a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java index 9e0adf55d87b..c34dc988555d 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -24,22 +24,19 @@ import android.hardware.usb.UsbDeviceConnection; public abstract class ReportCanvas { private static final String TAG = "ReportCanvas"; - private final UsbDeviceConnection mConnection; + private final UsbDescriptorParser mParser; /** * Constructor. * @param connection The USB connection object used to retrieve strings * from the USB device. */ - public ReportCanvas(UsbDeviceConnection connection) { - mConnection = connection; + public ReportCanvas(UsbDescriptorParser parser) { + mParser = parser; } - /** - * @returns the UsbDeviceConnection member (mConnection). - */ - public UsbDeviceConnection getConnection() { - return mConnection; + public UsbDescriptorParser getParser() { + return mParser; } /** diff --git a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java index a43569d40a67..1e19ea1447ef 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java @@ -15,7 +15,7 @@ */ package com.android.server.usb.descriptors.report; -import android.hardware.usb.UsbDeviceConnection; +import com.android.server.usb.descriptors.UsbDescriptorParser; /** * @hide @@ -34,8 +34,8 @@ public final class TextReportCanvas extends ReportCanvas { * from the USB device. * @param stringBuilder Generated output gets written into this object. */ - public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) { - super(connection); + public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) { + super(parser); mStringBuilder = stringBuilder; } diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java index 64ecebc29db6..fb4576a6ee78 100644 --- a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java +++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java @@ -32,8 +32,8 @@ public final class UsbStrings { private static HashMap<Byte, String> sDescriptorNames; private static HashMap<Byte, String> sACControlInterfaceNames; private static HashMap<Byte, String> sACStreamingInterfaceNames; - private static HashMap<Byte, String> sClassNames; - private static HashMap<Byte, String> sAudioSubclassNames; + private static HashMap<Integer, String> sClassNames; + private static HashMap<Integer, String> sAudioSubclassNames; private static HashMap<Integer, String> sAudioEncodingNames; private static HashMap<Integer, String> sTerminalNames; private static HashMap<Integer, String> sFormatNames; @@ -92,7 +92,7 @@ public final class UsbStrings { } private static void initClassNames() { - sClassNames = new HashMap<Byte, String>(); + sClassNames = new HashMap<Integer, String>(); sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device"); sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio"); sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications"); @@ -118,7 +118,7 @@ public final class UsbStrings { } private static void initAudioSubclassNames() { - sAudioSubclassNames = new HashMap<Byte, String>(); + sAudioSubclassNames = new HashMap<Integer, String>(); sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded"); sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control"); sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming"); @@ -300,7 +300,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB class ID. */ - public static String getClassName(byte classID) { + public static String getClassName(int classID) { String name = sClassNames.get(classID); int iClassID = classID & 0xFF; return name != null @@ -312,7 +312,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB audio subclass ID. */ - public static String getAudioSubclassName(byte subClassID) { + public static String getAudioSubclassName(int subClassID) { String name = sAudioSubclassNames.get(subClassID); int iSubclassID = subClassID & 0xFF; return name != null @@ -335,7 +335,7 @@ public final class UsbStrings { /** * Retrieves the name for the specified USB audio interface subclass ID. */ - public static String getACInterfaceSubclassName(byte subClassID) { + public static String getACInterfaceSubclassName(int subClassID) { return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming"; } } diff --git a/legacy-test/Android.mk b/test-base/Android.mk index 4c150c8f8a23..f73eff7d63aa 100644 --- a/legacy-test/Android.mk +++ b/test-base/Android.mk @@ -63,8 +63,8 @@ LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src LEGACY_TEST_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/api.txt LEGACY_TEST_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/removed.txt -LEGACY_TEST_API_FILE := $(LOCAL_PATH)/api/legacy-test-current.txt -LEGACY_TEST_REMOVED_API_FILE := $(LOCAL_PATH)/api/legacy-test-removed.txt +LEGACY_TEST_API_FILE := $(LOCAL_PATH)/api/android-test-base-current.txt +LEGACY_TEST_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-base-removed.txt LOCAL_DROIDDOC_OPTIONS:= \ -stubpackages android.test:android.test.suitebuilder.annotation:com.android.internal.util:junit.framework \ @@ -119,7 +119,7 @@ $(eval $(call check-api, \ -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \ -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \ -error 25 -error 26 -error 27, \ - cat $(LOCAL_PATH)/api/apicheck_msg_legacy_test.txt, \ + cat $(LOCAL_PATH)/api/apicheck_msg_android_test_base.txt, \ check-legacy-test-api, \ $(call doc-timestamp-for,legacy-test-api-stubs-gen) \ )) diff --git a/legacy-test/api/legacy-test-current.txt b/test-base/api/android-test-base-current.txt index 7ebd6aa8a4a2..7ebd6aa8a4a2 100644 --- a/legacy-test/api/legacy-test-current.txt +++ b/test-base/api/android-test-base-current.txt diff --git a/legacy-test/api/legacy-test-removed.txt b/test-base/api/android-test-base-removed.txt index e69de29bb2d1..e69de29bb2d1 100644 --- a/legacy-test/api/legacy-test-removed.txt +++ b/test-base/api/android-test-base-removed.txt diff --git a/legacy-test/api/apicheck_msg_legacy_test.txt b/test-base/api/apicheck_msg_android_test_base.txt index ad5f2359b8b1..ad5f2359b8b1 100644 --- a/legacy-test/api/apicheck_msg_legacy_test.txt +++ b/test-base/api/apicheck_msg_android_test_base.txt diff --git a/legacy-test/jarjar-rules.txt b/test-base/jarjar-rules.txt index fd8555c8931c..fd8555c8931c 100644 --- a/legacy-test/jarjar-rules.txt +++ b/test-base/jarjar-rules.txt diff --git a/legacy-test/src/android/test/AndroidTestCase.java b/test-base/src/android/test/AndroidTestCase.java index 1e6bd9c14fd9..1e6bd9c14fd9 100644 --- a/legacy-test/src/android/test/AndroidTestCase.java +++ b/test-base/src/android/test/AndroidTestCase.java diff --git a/legacy-test/src/android/test/FlakyTest.java b/test-base/src/android/test/FlakyTest.java index 4e5c4e35a8c6..4e5c4e35a8c6 100644 --- a/legacy-test/src/android/test/FlakyTest.java +++ b/test-base/src/android/test/FlakyTest.java diff --git a/legacy-test/src/android/test/InstrumentationTestCase.java b/test-base/src/android/test/InstrumentationTestCase.java index 6b79314a4385..6b79314a4385 100644 --- a/legacy-test/src/android/test/InstrumentationTestCase.java +++ b/test-base/src/android/test/InstrumentationTestCase.java diff --git a/legacy-test/src/android/test/InstrumentationTestSuite.java b/test-base/src/android/test/InstrumentationTestSuite.java index a53fa267f1e1..a53fa267f1e1 100644 --- a/legacy-test/src/android/test/InstrumentationTestSuite.java +++ b/test-base/src/android/test/InstrumentationTestSuite.java diff --git a/legacy-test/src/android/test/PerformanceTestCase.java b/test-base/src/android/test/PerformanceTestCase.java index 65bd4a48f7f5..65bd4a48f7f5 100644 --- a/legacy-test/src/android/test/PerformanceTestCase.java +++ b/test-base/src/android/test/PerformanceTestCase.java diff --git a/legacy-test/src/android/test/RepetitiveTest.java b/test-base/src/android/test/RepetitiveTest.java index 6a7130e68e61..6a7130e68e61 100644 --- a/legacy-test/src/android/test/RepetitiveTest.java +++ b/test-base/src/android/test/RepetitiveTest.java diff --git a/legacy-test/src/android/test/UiThreadTest.java b/test-base/src/android/test/UiThreadTest.java index cd06ab890074..cd06ab890074 100644 --- a/legacy-test/src/android/test/UiThreadTest.java +++ b/test-base/src/android/test/UiThreadTest.java diff --git a/legacy-test/src/android/test/package.html b/test-base/src/android/test/package.html index 5be51359630e..5be51359630e 100644 --- a/legacy-test/src/android/test/package.html +++ b/test-base/src/android/test/package.html diff --git a/legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java b/test-base/src/android/test/suitebuilder/annotation/LargeTest.java index dc77ee6b2739..dc77ee6b2739 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/LargeTest.java +++ b/test-base/src/android/test/suitebuilder/annotation/LargeTest.java diff --git a/legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java b/test-base/src/android/test/suitebuilder/annotation/MediumTest.java index b941da03ac9a..b941da03ac9a 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/MediumTest.java +++ b/test-base/src/android/test/suitebuilder/annotation/MediumTest.java diff --git a/legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java b/test-base/src/android/test/suitebuilder/annotation/SmallTest.java index d3c74f019b53..d3c74f019b53 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/SmallTest.java +++ b/test-base/src/android/test/suitebuilder/annotation/SmallTest.java diff --git a/legacy-test/src/android/test/suitebuilder/annotation/Smoke.java b/test-base/src/android/test/suitebuilder/annotation/Smoke.java index aac293796be1..aac293796be1 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/Smoke.java +++ b/test-base/src/android/test/suitebuilder/annotation/Smoke.java diff --git a/legacy-test/src/android/test/suitebuilder/annotation/Suppress.java b/test-base/src/android/test/suitebuilder/annotation/Suppress.java index 629a3cf4a2cd..629a3cf4a2cd 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/Suppress.java +++ b/test-base/src/android/test/suitebuilder/annotation/Suppress.java diff --git a/legacy-test/src/android/test/suitebuilder/annotation/package.html b/test-base/src/android/test/suitebuilder/annotation/package.html index ffba2e9bf980..ffba2e9bf980 100644 --- a/legacy-test/src/android/test/suitebuilder/annotation/package.html +++ b/test-base/src/android/test/suitebuilder/annotation/package.html diff --git a/legacy-test/src/com/android/internal/util/Predicate.java b/test-base/src/com/android/internal/util/Predicate.java index e87f489f4670..e87f489f4670 100644 --- a/legacy-test/src/com/android/internal/util/Predicate.java +++ b/test-base/src/com/android/internal/util/Predicate.java diff --git a/legacy-test/src/junit/MODULE_LICENSE_CPL b/test-base/src/junit/MODULE_LICENSE_CPL index e69de29bb2d1..e69de29bb2d1 100644 --- a/legacy-test/src/junit/MODULE_LICENSE_CPL +++ b/test-base/src/junit/MODULE_LICENSE_CPL diff --git a/legacy-test/src/junit/README.android b/test-base/src/junit/README.android index 1384a1fedda2..1384a1fedda2 100644 --- a/legacy-test/src/junit/README.android +++ b/test-base/src/junit/README.android diff --git a/legacy-test/src/junit/cpl-v10.html b/test-base/src/junit/cpl-v10.html index 36aa208d4a29..36aa208d4a29 100644 --- a/legacy-test/src/junit/cpl-v10.html +++ b/test-base/src/junit/cpl-v10.html diff --git a/legacy-test/src/junit/framework/Assert.java b/test-base/src/junit/framework/Assert.java index 3dcc23d71c19..3dcc23d71c19 100644 --- a/legacy-test/src/junit/framework/Assert.java +++ b/test-base/src/junit/framework/Assert.java diff --git a/legacy-test/src/junit/framework/AssertionFailedError.java b/test-base/src/junit/framework/AssertionFailedError.java index 0d7802c431c6..0d7802c431c6 100644 --- a/legacy-test/src/junit/framework/AssertionFailedError.java +++ b/test-base/src/junit/framework/AssertionFailedError.java diff --git a/legacy-test/src/junit/framework/ComparisonCompactor.java b/test-base/src/junit/framework/ComparisonCompactor.java index e540f03b87d3..e540f03b87d3 100644 --- a/legacy-test/src/junit/framework/ComparisonCompactor.java +++ b/test-base/src/junit/framework/ComparisonCompactor.java diff --git a/legacy-test/src/junit/framework/ComparisonFailure.java b/test-base/src/junit/framework/ComparisonFailure.java index 507799328a44..507799328a44 100644 --- a/legacy-test/src/junit/framework/ComparisonFailure.java +++ b/test-base/src/junit/framework/ComparisonFailure.java diff --git a/legacy-test/src/junit/framework/Protectable.java b/test-base/src/junit/framework/Protectable.java index e1432370cfaf..e1432370cfaf 100644 --- a/legacy-test/src/junit/framework/Protectable.java +++ b/test-base/src/junit/framework/Protectable.java diff --git a/legacy-test/src/junit/framework/Test.java b/test-base/src/junit/framework/Test.java index a016ee8308f1..a016ee8308f1 100644 --- a/legacy-test/src/junit/framework/Test.java +++ b/test-base/src/junit/framework/Test.java diff --git a/legacy-test/src/junit/framework/TestCase.java b/test-base/src/junit/framework/TestCase.java index b047ec9e1afc..b047ec9e1afc 100644 --- a/legacy-test/src/junit/framework/TestCase.java +++ b/test-base/src/junit/framework/TestCase.java diff --git a/legacy-test/src/junit/framework/TestFailure.java b/test-base/src/junit/framework/TestFailure.java index 6662b1fab1b2..6662b1fab1b2 100644 --- a/legacy-test/src/junit/framework/TestFailure.java +++ b/test-base/src/junit/framework/TestFailure.java diff --git a/legacy-test/src/junit/framework/TestListener.java b/test-base/src/junit/framework/TestListener.java index 9b6944361b9d..9b6944361b9d 100644 --- a/legacy-test/src/junit/framework/TestListener.java +++ b/test-base/src/junit/framework/TestListener.java diff --git a/legacy-test/src/junit/framework/TestResult.java b/test-base/src/junit/framework/TestResult.java index 3052e94074fd..3052e94074fd 100644 --- a/legacy-test/src/junit/framework/TestResult.java +++ b/test-base/src/junit/framework/TestResult.java diff --git a/legacy-test/src/junit/framework/TestSuite.java b/test-base/src/junit/framework/TestSuite.java index 336efd1800d7..336efd1800d7 100644 --- a/legacy-test/src/junit/framework/TestSuite.java +++ b/test-base/src/junit/framework/TestSuite.java diff --git a/test-mock/Android.mk b/test-mock/Android.mk index 18da8b8bd687..e4af17cbd711 100644 --- a/test-mock/Android.mk +++ b/test-mock/Android.mk @@ -26,7 +26,7 @@ LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_JAVA_LIBRARIES := core-oj core-libart framework legacy-test -LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt +LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../test-base/jarjar-rules.txt LOCAL_MODULE:= repackaged.android.test.mock @@ -130,15 +130,4 @@ update-android-test-mock-api: $(ANDROID_TEST_MOCK_OUTPUT_API_FILE) | $(ACP) @echo Copying removed.txt $(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE) -# Build the android.test.mock.sdk library -# ======================================= -include $(CLEAR_VARS) - -LOCAL_MODULE := android.test.mock.sdk -LOCAL_SDK_VERSION := current - -LOCAL_STATIC_JAVA_LIBRARIES := android.test.mock.stubs - -include $(BUILD_STATIC_JAVA_LIBRARY) - endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true diff --git a/test-runner/Android.mk b/test-runner/Android.mk index d0f5b3248c29..c0fd7f8ec26d 100644 --- a/test-runner/Android.mk +++ b/test-runner/Android.mk @@ -46,7 +46,7 @@ LOCAL_JAVA_LIBRARIES := \ legacy-test \ android.test.mock \ -LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../legacy-test/jarjar-rules.txt +LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../test-base/jarjar-rules.txt LOCAL_MODULE:= repackaged.android.test.runner diff --git a/test-runner/api/android-test-runner-current.txt b/test-runner/api/android-test-runner-current.txt index 905cfe701ab6..1170eb53ab7f 100644 --- a/test-runner/api/android-test-runner-current.txt +++ b/test-runner/api/android-test-runner-current.txt @@ -271,8 +271,6 @@ package android.test.suitebuilder { public deprecated class TestSuiteBuilder { ctor public TestSuiteBuilder(java.lang.Class); ctor public TestSuiteBuilder(java.lang.String, java.lang.ClassLoader); - method public android.test.suitebuilder.TestSuiteBuilder addRequirements(java.util.List<com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>>); - method public final android.test.suitebuilder.TestSuiteBuilder addRequirements(com.android.internal.util.Predicate<android.test.suitebuilder.TestMethod>...); method public final junit.framework.TestSuite build(); method public android.test.suitebuilder.TestSuiteBuilder excludePackages(java.lang.String...); method protected java.lang.String getSuiteName(); diff --git a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java index 6158e0cf14f4..2857696ef2ff 100644 --- a/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java +++ b/test-runner/src/android/test/suitebuilder/TestSuiteBuilder.java @@ -119,6 +119,7 @@ public class TestSuiteBuilder { * * @param predicates Predicates to add to the list of requirements. * @return The builder for method chaining. + * @hide */ public TestSuiteBuilder addRequirements(List<Predicate<TestMethod>> predicates) { this.predicates.addAll(predicates); @@ -156,7 +157,7 @@ public class TestSuiteBuilder { /** * Override the default name for the suite being built. This should generally be called if you - * call {@link #addRequirements(com.android.internal.util.Predicate[])} to make it clear which + * call {@code addRequirements(com.android.internal.util.Predicate[])} to make it clear which * tests will be included. The name you specify is automatically prefixed with the package * containing the tests to be run. If more than one package is specified, the first is used. * @@ -215,6 +216,7 @@ public class TestSuiteBuilder { * * @param predicates Predicates to add to the list of requirements. * @return The builder for method chaining. + * @hide */ public final TestSuiteBuilder addRequirements(Predicate<TestMethod>... predicates) { ArrayList<Predicate<TestMethod>> list = new ArrayList<Predicate<TestMethod>>(); diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java index a2fa95deaa60..c60f07d56d92 100644 --- a/tests/testables/src/android/testing/TestableResources.java +++ b/tests/testables/src/android/testing/TestableResources.java @@ -39,7 +39,8 @@ public class TestableResources { private final Resources mResources; private final SparseArray<Object> mOverrides = new SparseArray<>(); - TestableResources(Resources realResources) { + /** Creates a TestableResources instance that calls through to the given real Resources. */ + public TestableResources(Resources realResources) { mResources = mock(Resources.class, withSettings() .spiedInstance(realResources) .defaultAnswer(this::answer)); diff --git a/wifi/java/android/net/wifi/BatchedScanResult.java b/wifi/java/android/net/wifi/BatchedScanResult.java index 6d9f00f3d5ca..c06543ec260a 100644 --- a/wifi/java/android/net/wifi/BatchedScanResult.java +++ b/wifi/java/android/net/wifi/BatchedScanResult.java @@ -17,6 +17,7 @@ package android.net.wifi; import android.os.Parcelable; +import android.annotation.SystemApi; import android.os.Parcel; import java.util.ArrayList; @@ -29,6 +30,7 @@ import java.util.List; * @removed */ @Deprecated +@SystemApi public class BatchedScanResult implements Parcelable { private static final String TAG = "BatchedScanResult"; diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index a9e1e9d06470..abbb82a9cc69 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -21,6 +21,7 @@ import android.content.pm.ParceledListSlice; import android.net.wifi.hotspot2.OsuProvider; import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.hotspot2.IProvisioningCallback; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; @@ -184,5 +185,7 @@ interface IWifiManager void restoreBackupData(in byte[] data); void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData); + + void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback); } diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 183cf0d10d49..558004ce1753 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -33,6 +33,8 @@ import android.net.NetworkCapabilities; import android.net.NetworkRequest; 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; import android.os.Handler; @@ -3610,4 +3612,45 @@ public class WifiManager { throw e.rethrowFromSystemServer(); } } + + /** + * Start subscription provisioning flow + * @param provider {@link OsuProvider} to provision with + * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow + * @hide + */ + public void startSubscriptionProvisioning(OsuProvider provider, ProvisioningCallback callback, + @Nullable Handler handler) { + Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper(); + try { + mService.startSubscriptionProvisioning(provider, + new ProvisioningCallbackProxy(looper, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub { + private final Handler mHandler; + private final ProvisioningCallback mCallback; + + ProvisioningCallbackProxy(Looper looper, ProvisioningCallback callback) { + mHandler = new Handler(looper); + mCallback = callback; + } + + @Override + public void onProvisioningStatus(int status) { + mHandler.post(() -> { + mCallback.onProvisioningStatus(status); + }); + } + + @Override + public void onProvisioningFailure(int status) { + mHandler.post(() -> { + mCallback.onProvisioningFailure(status); + }); + } + } } diff --git a/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl new file mode 100644 index 000000000000..c2cb16ab847c --- /dev/null +++ b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 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.hotspot2; + +/** + * Interface for Provisioning callback. + * + * @hide + */ +oneway interface IProvisioningCallback +{ + /** + * Service to manager callback providing failure notification + */ + void onProvisioningFailure(int status); + + /** + * Service to manager callback providing Provisioning status + */ + void onProvisioningStatus(int status); +} + diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java new file mode 100644 index 000000000000..8b86cdde4a9e --- /dev/null +++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017 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.hotspot2; + +import android.os.Handler; + +/** + * Base class for provisioning callbacks. Should be extended by applications and set when calling + * {@link WifiManager#startSubscriptionProvisiong(OsuProvider, ProvisioningCallback, Handler)}. + * + * @hide + */ +public abstract class ProvisioningCallback { + + /** + * The reason code for Provisioning Failure due to connection failure to OSU AP. + * @hide + */ + public static final int OSU_FAILURE_AP_CONNECTION = 1; + + /** + * The status code for Provisioning flow to indicate connecting to OSU AP + * @hide + */ + public static final int OSU_STATUS_AP_CONNECTING = 1; + + /** + * The status code for Provisioning flow to indicate connected to OSU AP + * @hide + */ + public static final int OSU_STATUS_AP_CONNECTED = 2; + + /** + * Provisioning status for OSU failure + * @param status indicates error condition + */ + public abstract void onProvisioningFailure(int status); + + /** + * Provisioning status when OSU is in progress + * @param status indicates status of OSU flow + */ + public abstract void onProvisioningStatus(int status); +} + |