Merge "Throw clear error if a row doesn't fit into CursorWindow"
diff --git a/Android.mk b/Android.mk
index 6470e34..c87aec4 100644
--- a/Android.mk
+++ b/Android.mk
@@ -425,7 +425,6 @@
core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl \
core/java/com/android/internal/widget/ILockSettings.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
- core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
keystore/java/android/security/IKeyChainAliasCallback.aidl \
keystore/java/android/security/IKeyChainService.aidl \
location/java/android/location/IBatchedLocationCallback.aidl \
@@ -579,6 +578,8 @@
LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
+LOCAL_AIDL_INCLUDES += core/java/android/os/StatsLogEventWrapper.aidl
+
LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
LOCAL_SRC_FILES += \
lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
diff --git a/api/current.txt b/api/current.txt
index df8b7ef..8b3c740 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6451,6 +6451,8 @@
method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+ method public boolean setTime(android.content.ComponentName, long);
+ method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
diff --git a/api/system-current.txt b/api/system-current.txt
index 007316f..ee23b6d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6686,6 +6686,8 @@
method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+ method public boolean setTime(android.content.ComponentName, long);
+ method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
diff --git a/api/test-current.txt b/api/test-current.txt
index faccb6b..9a241ad 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6519,6 +6519,8 @@
method public boolean setStatusBarDisabled(android.content.ComponentName, boolean);
method public int setStorageEncryption(android.content.ComponentName, boolean);
method public void setSystemUpdatePolicy(android.content.ComponentName, android.app.admin.SystemUpdatePolicy);
+ method public boolean setTime(android.content.ComponentName, long);
+ method public boolean setTimeZone(android.content.ComponentName, java.lang.String);
method public void setTrustAgentConfiguration(android.content.ComponentName, android.content.ComponentName, android.os.PersistableBundle);
method public void setUninstallBlocked(android.content.ComponentName, java.lang.String, boolean);
method public void setUserIcon(android.content.ComponentName, android.graphics.Bitmap);
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 3c2f2d5..8946aed 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -37,6 +37,7 @@
src/matchers/matcher_util.cpp \
src/matchers/SimpleLogMatchingTracker.cpp \
src/metrics/CountAnomalyTracker.cpp \
+ src/metrics/MetricProducer.cpp \
src/metrics/CountMetricProducer.cpp \
src/metrics/DurationMetricProducer.cpp \
src/metrics/MetricsManager.cpp \
@@ -49,7 +50,8 @@
src/stats_util.cpp
statsd_common_c_includes := \
- $(LOCAL_PATH)/src
+ $(LOCAL_PATH)/src \
+ $(LOCAL_PATH)/../../libs/services/include
statsd_common_aidl_includes := \
$(LOCAL_PATH)/../../core/java
@@ -95,7 +97,7 @@
endif
LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
LOCAL_C_INCLUDES += $(statsd_common_c_includes)
LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries)
@@ -117,7 +119,7 @@
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_MODULE_TAGS := tests
-LOCAL_AIDL_INCLUDES := $(statsd_common_c_includes)
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
LOCAL_C_INCLUDES += $(statsd_common_c_includes)
LOCAL_CFLAGS += \
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 87616d3..1faeee0 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -63,9 +63,9 @@
// ======================================================================
StatsService::StatsService(const sp<Looper>& handlerLooper)
- : mStatsPullerManager(),
- mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
+ : mAnomalyMonitor(new AnomalyMonitor(2)) // TODO: Put this comment somewhere better
{
+ mStatsPullerManager = new StatsPullerManager();
mUidMap = new UidMap();
mConfigManager = new ConfigManager();
mProcessor = new StatsLogProcessor(mUidMap);
@@ -193,6 +193,10 @@
if (!args[0].compare(String8("dump-report"))) {
return cmd_dump_report(out, err, args);
}
+
+ if (!args[0].compare(String8("pull-source")) && args.size() > 1) {
+ return cmd_print_pulled_metrics(out, args);
+ }
}
print_cmd_help(out);
@@ -210,6 +214,11 @@
fprintf(out, " Prints the UID, app name, version mapping.\n");
fprintf(out, "\n");
fprintf(out, "\n");
+ fprintf(out, "usage: adb shell cmds stats pull-source [int] \n");
+ fprintf(out, "\n");
+ fprintf(out, " Prints the output of a pulled metrics source (int indicates source)\n");
+ fprintf(out, "\n");
+ fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
fprintf(out, "\n");
@@ -353,6 +362,16 @@
return NO_ERROR;
}
+status_t StatsService::cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args) {
+ int s = atoi(args[1].c_str());
+ auto stats = mStatsPullerManager->Pull(s);
+ for (const auto& it : stats) {
+ fprintf(out, "Pull from %d: %s\n", s, it->ToString().c_str());
+ }
+ fprintf(out, "Pull from %d: Received %zu elements\n", s, stats.size());
+ return NO_ERROR;
+}
+
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
const vector<String16>& app) {
if (DEBUG) ALOGD("StatsService::informAllUidData was called");
@@ -414,10 +433,6 @@
if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
// TODO: determine what services to poll and poll (or ask StatsCompanionService to poll) them.
- String16 output = mStatsPullerManager.pull(StatsPullerManager::KERNEL_WAKELOCKS);
- // TODO: do something useful with the output instead of writing a string to screen.
- ALOGD("%s", String8(output).string());
- ALOGD("%d", int(output.size()));
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 294aec8..449a2b8 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -121,6 +121,11 @@
status_t cmd_print_uid_map(FILE* out);
/**
+ * Print contents of a pulled metrics source.
+ */
+ status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
+
+ /**
* Update a configuration.
*/
void set_config(int uid, const string& name, const StatsdConfig& config);
@@ -132,9 +137,8 @@
/**
* Fetches external metrics.
- * TODO: This should be an sp<>
*/
- StatsPullerManager mStatsPullerManager;
+ sp<StatsPullerManager> mStatsPullerManager;
/**
* Tracks the configurations that have been passed to statsd.
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.cpp b/cmds/statsd/src/external/KernelWakelockPuller.cpp
index b9abee0..ee072f8 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.cpp
+++ b/cmds/statsd/src/external/KernelWakelockPuller.cpp
@@ -37,9 +37,9 @@
// The reading and parsing are implemented in Java. It is not difficult to port over. But for now
// let StatsCompanionService handle that and send the data back.
-String16 KernelWakelockPuller::pull() {
+vector<StatsLogEventWrapper> KernelWakelockPuller::pull() {
sp<IStatsCompanionService> statsCompanion = StatsService::getStatsCompanionService();
- String16 returned_value("");
+ vector<StatsLogEventWrapper> returned_value;
if (statsCompanion != NULL) {
Status status = statsCompanion->pullData(KernelWakelockPuller::PULL_CODE_KERNEL_WAKELOCKS,
&returned_value);
@@ -47,12 +47,10 @@
ALOGW("error pulling kernel wakelock");
}
ALOGD("KernelWakelockPuller::pull succeeded!");
- // TODO: remove this when we integrate into aggregation chain.
- ALOGD("%s", String8(returned_value).string());
return returned_value;
} else {
ALOGW("statsCompanion not found!");
- return String16();
+ return returned_value;
}
}
diff --git a/cmds/statsd/src/external/KernelWakelockPuller.h b/cmds/statsd/src/external/KernelWakelockPuller.h
index 1ec3376..c12806c 100644
--- a/cmds/statsd/src/external/KernelWakelockPuller.h
+++ b/cmds/statsd/src/external/KernelWakelockPuller.h
@@ -29,7 +29,7 @@
// a number of stats need to be pulled from StatsCompanionService
//
const static int PULL_CODE_KERNEL_WAKELOCKS;
- String16 pull() override;
+ vector<StatsLogEventWrapper> pull() override;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index 5e556b8..6655629 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -17,7 +17,12 @@
#ifndef STATSD_STATSPULLER_H
#define STATSD_STATSPULLER_H
+#include <android/os/StatsLogEventWrapper.h>
#include <utils/String16.h>
+#include <vector>
+
+using android::os::StatsLogEventWrapper;
+using std::vector;
namespace android {
namespace os {
@@ -26,8 +31,8 @@
class StatsPuller {
public:
virtual ~StatsPuller(){};
- // use string for now, until we figure out how to integrate into the aggregation path
- virtual String16 pull() = 0;
+
+ virtual vector<StatsLogEventWrapper> pull() = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 6e8d58bc..7f554d3 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -21,6 +21,11 @@
#include "KernelWakelockPuller.h"
#include "StatsService.h"
#include "external/StatsPullerManager.h"
+#include "logd/LogEvent.h"
+#include <cutils/log.h>
+#include <algorithm>
+
+#include <iostream>
using namespace android;
@@ -35,13 +40,27 @@
{static_cast<int>(KERNEL_WAKELOCKS), std::make_unique<KernelWakelockPuller>()});
}
-String16 StatsPullerManager::pull(int pullCode) {
+vector<std::shared_ptr<LogEvent>> StatsPullerManager::Pull(int pullCode) {
if (DEBUG) ALOGD("Initiating pulling %d", pullCode);
+
+ vector<std::shared_ptr<LogEvent>> ret;
if (mStatsPullers.find(pullCode) != mStatsPullers.end()) {
- return (mStatsPullers.find(pullCode)->second)->pull();
+ vector<StatsLogEventWrapper> outputs = (mStatsPullers.find(pullCode)->second)->pull();
+ for (const StatsLogEventWrapper& it : outputs) {
+ log_msg tmp;
+ tmp.entry_v1.len = it.bytes.size();
+ // Manually set the header size to 28 bytes to match the pushed log events.
+ tmp.entry.hdr_size = 28;
+ // And set the received bytes starting after the 28 bytes reserved for header.
+ std::copy(it.bytes.begin(), it.bytes.end(), tmp.buf + 28);
+ std::shared_ptr<LogEvent> evt = std::make_shared<LogEvent>(tmp);
+ ret.push_back(evt);
+ // ret.emplace_back(tmp);
+ }
+ return ret;
} else {
ALOGD("Unknown pull code %d", pullCode);
- return String16();
+ return ret; // Return early since we don't know what to pull.
}
}
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index f143424..e46aec1 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -20,6 +20,8 @@
#include <utils/String16.h>
#include <unordered_map>
#include "external/StatsPuller.h"
+#include "logd/LogEvent.h"
+#include "matchers/matcher_util.h"
namespace android {
namespace os {
@@ -27,7 +29,7 @@
const static int KERNEL_WAKELOCKS = 1;
-class StatsPullerManager {
+class StatsPullerManager : public virtual RefBase {
public:
// Enums of pulled data types (pullCodes)
// These values must be kept in sync with com/android/server/stats/StatsCompanionService.java.
@@ -35,7 +37,8 @@
const static int KERNEL_WAKELOCKS;
StatsPullerManager();
- String16 pull(const int pullCode);
+ // We return a vector of shared_ptr since LogEvent's copy constructor is not available.
+ vector<std::shared_ptr<LogEvent>> Pull(const int pullCode);
private:
std::unordered_map<int, std::unique_ptr<StatsPuller>> mStatsPullers;
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 032b4b8..fb992c1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -23,29 +23,30 @@
namespace statsd {
using std::ostringstream;
+using std::string;
-LogEvent::LogEvent(const log_msg& msg) {
- init(msg);
+// We need to keep a copy of the android_log_event_list owned by this instance so that the char*
+// for strings is not cleared before we can read them.
+LogEvent::LogEvent(log_msg msg) : mList(msg) {
+ init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
}
-LogEvent::LogEvent(int64_t timestampNs, android_log_event_list* reader) {
- init(timestampNs, reader);
+LogEvent::LogEvent(int tag) : mList(tag) {
}
LogEvent::~LogEvent() {
}
+void LogEvent::init() {
+ mList.convert_to_reader();
+ init(mTimestampNs, &mList);
+}
+
/**
* The elements of each log event are stored as a vector of android_log_list_elements.
* The goal is to do as little preprocessing as possible, because we read a tiny fraction
* of the elements that are written to the log.
*/
-void LogEvent::init(const log_msg& msg) {
-
- android_log_event_list list(const_cast<log_msg&>(msg));
- init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &list);
-}
-
void LogEvent::init(int64_t timestampNs, android_log_event_list* reader) {
mTimestampNs = timestampNs;
mTagId = reader->tag();
@@ -79,6 +80,10 @@
} while ((elem.type != EVENT_TYPE_UNKNOWN) && !elem.complete);
}
+android_log_event_list* LogEvent::GetAndroidLogEventList() {
+ return &mList;
+}
+
int64_t LogEvent::GetLong(size_t key, status_t* err) const {
if (key < 1 || (key - 1) >= mElements.size()) {
*err = BAD_INDEX;
@@ -109,7 +114,8 @@
*err = BAD_TYPE;
return NULL;
}
- return elem.data.string;
+ // Need to add the '/0' at the end by specifying the length of the string.
+ return string(elem.data.string, elem.len).c_str();
}
bool LogEvent::GetBool(size_t key, status_t* err) const {
@@ -189,7 +195,8 @@
} else if (elem.type == EVENT_TYPE_FLOAT) {
result << elem.data.float32;
} else if (elem.type == EVENT_TYPE_STRING) {
- result << elem.data.string;
+ // Need to add the '/0' at the end by specifying the length of the string.
+ result << string(elem.data.string, elem.len).c_str();
}
}
result << " }";
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 464afca..4102675 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -22,6 +22,7 @@
#include <log/log_event_list.h>
#include <log/log_read.h>
+#include <memory>
#include <string>
#include <vector>
@@ -40,12 +41,16 @@
/**
* Read a LogEvent from a log_msg.
*/
- explicit LogEvent(const log_msg& msg);
+ explicit LogEvent(log_msg msg);
/**
- * Read a LogEvent from an android_log_context.
+ * Constructs a LogEvent with the specified tag and creates an android_log_event_list in write
+ * mode. Obtain this list with the getter. Make sure to call init() before attempting to read
+ * any of the values. This constructor is useful for unit-testing since we can't pass in an
+ * android_log_event_list since there is no copy constructor or assignment operator available.
*/
- explicit LogEvent(int64_t timestampNs, android_log_event_list* reader);
+ explicit LogEvent(int tag);
+
~LogEvent();
/**
@@ -85,6 +90,19 @@
*/
KeyValuePair GetKeyValueProto(size_t key) const;
+ /**
+ * A pointer to the contained log_event_list.
+ *
+ * @return The android_log_event_list contained within.
+ */
+ android_log_event_list* GetAndroidLogEventList();
+
+ /**
+ * Used with the constructor where tag is passed in. Converts the log_event_list to read mode
+ * and prepares the list for reading.
+ */
+ void init();
+
private:
/**
* Don't copy, it's slower. If we really need this we can add it but let's try to
@@ -103,6 +121,8 @@
void init(int64_t timestampNs, android_log_event_list* reader);
vector<android_log_list_element> mElements;
+ // Need a copy of the android_log_event_list so the strings are not cleared.
+ android_log_event_list mList;
long mTimestampNs;
int mTagId;
};
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 1f07914..28cb503 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -124,52 +124,23 @@
mCondition = conditionMet;
}
-void CountMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+void CountMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event) {
uint64_t eventTimeNs = event.GetTimestampNs();
- // this is old event, maybe statsd restarted?
- if (eventTimeNs < mStartTimeNs) {
- return;
- }
flushCounterIfNeeded(eventTimeNs);
- if (mConditionSliced) {
- map<string, HashableDimensionKey> conditionKeys;
- for (const auto& link : mConditionLinks) {
- VLOG("Condition link key_in_main size %d", link.key_in_main_size());
- HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
- conditionKeys[link.condition()] = conditionKey;
- }
- if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
- VLOG("metric %lld sliced condition not met", mMetric.metric_id());
- return;
- }
- } else {
- if (!mCondition) {
- VLOG("metric %lld condition not met", mMetric.metric_id());
- return;
- }
+ if (condition == false) {
+ return;
}
- HashableDimensionKey hashableKey;
-
- if (mDimension.size() > 0) {
- vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- hashableKey = getHashableKey(key);
- // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
- // expects vector<KeyValuePair>.
- if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
- mDimensionKeyMap[hashableKey] = key;
- }
- } else {
- hashableKey = DEFAULT_DIMENSION_KEY;
- }
-
- auto it = mCurrentSlicedCounter.find(hashableKey);
+ auto it = mCurrentSlicedCounter.find(eventKey);
if (it == mCurrentSlicedCounter.end()) {
// create a counter for the new key
- mCurrentSlicedCounter[hashableKey] = 1;
+ mCurrentSlicedCounter[eventKey] = 1;
} else {
// increment the existing value
@@ -177,8 +148,8 @@
count++;
}
- VLOG("metric %lld %s->%d", mMetric.metric_id(), hashableKey.c_str(),
- mCurrentSlicedCounter[hashableKey]);
+ VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(),
+ mCurrentSlicedCounter[eventKey]);
}
// When a new matched event comes in, we check if event falls into the current
@@ -215,4 +186,4 @@
} // namespace statsd
} // namespace os
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index f0d6025..8bbdb01 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -41,8 +41,6 @@
virtual ~CountMetricProducer();
- void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
-
void onConditionChanged(const bool conditionMet) override;
void finish() override;
@@ -54,6 +52,11 @@
// TODO: Implement this later.
virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+protected:
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey,
+ bool condition, const LogEvent& event) override;
+
private:
const CountMetric mMetric;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index aa597f4..38e55fd 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -137,11 +137,10 @@
return report;
};
-void DurationMetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
- if (event.GetTimestampNs() < mStartTimeNs) {
- return;
- }
-
+void DurationMetricProducer::onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKeys, bool condition,
+ const LogEvent& event) {
flushDurationIfNeeded(event.GetTimestampNs());
if (matcherIndex == mStopAllIndex) {
@@ -149,49 +148,20 @@
return;
}
- HashableDimensionKey hashableKey;
- if (mDimension.size() > 0) {
- // hook up sliced counter with AnomalyMonitor.
- vector<KeyValuePair> key = getDimensionKey(event, mDimension);
- hashableKey = getHashableKey(key);
- // Add the HashableDimensionKey->DimensionKey to the map, because StatsLogReport expects
- // vector<KeyValuePair>.
- if (mDimensionKeyMap.find(hashableKey) == mDimensionKeyMap.end()) {
- mDimensionKeyMap[hashableKey] = key;
- }
- } else {
- hashableKey = DEFAULT_DIMENSION_KEY;
- }
-
- if (mCurrentSlicedDuration.find(hashableKey) == mCurrentSlicedDuration.end() &&
- mConditionSliced) {
+ if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end() && mConditionSliced) {
// add the durationInfo for the current bucket.
- auto& durationInfo = mCurrentSlicedDuration[hashableKey];
- auto& conditionKeys = durationInfo.conditionKeys;
- // get and cache the keys for query condition.
- for (const auto& link : mConditionLinks) {
- HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
- conditionKeys[link.condition()] = conditionKey;
- }
- }
-
- bool conditionMet;
- if (mConditionSliced) {
- const auto& conditionKeys = mCurrentSlicedDuration[hashableKey].conditionKeys;
- conditionMet =
- mWizard->query(mConditionTrackerIndex, conditionKeys) == ConditionState::kTrue;
- } else {
- conditionMet = mCondition;
+ auto& durationInfo = mCurrentSlicedDuration[eventKey];
+ durationInfo.conditionKeys = conditionKeys;
}
if (matcherIndex == mStartIndex) {
- VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
- conditionMet);
- noteStart(hashableKey, conditionMet, event.GetTimestampNs());
+ VLOG("Metric %lld Key: %s Start, Condition %d", mMetric.metric_id(), eventKey.c_str(),
+ condition);
+ noteStart(eventKey, condition, event.GetTimestampNs());
} else if (matcherIndex == mStopIndex) {
- VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), hashableKey.c_str(),
- conditionMet);
- noteStop(hashableKey, event.GetTimestampNs());
+ VLOG("Metric %lld Key: %s Stop, Condition %d", mMetric.metric_id(), eventKey.c_str(),
+ condition);
+ noteStop(eventKey, event.GetTimestampNs());
}
}
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 44c3254..19e2437 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -61,8 +61,6 @@
virtual ~DurationMetricProducer();
- void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) override;
-
void onConditionChanged(const bool conditionMet) override;
void finish() override;
@@ -74,6 +72,11 @@
// TODO: Implement this later.
virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
+protected:
+ void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKeys,
+ bool condition, const LogEvent& event) override;
+
private:
const DurationMetric mMetric;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
new file mode 100644
index 0000000..3c8ce6e
--- /dev/null
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+#include "MetricProducer.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::map;
+
+void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
+ // this is old event, maybe statsd restarted?
+ if (eventTimeNs < mStartTimeNs) {
+ return;
+ }
+
+ HashableDimensionKey eventKey;
+
+ if (mDimension.size() > 0) {
+ vector<KeyValuePair> key = getDimensionKey(event, mDimension);
+ eventKey = getHashableKey(key);
+ // Add the HashableDimensionKey->vector<KeyValuePair> to the map, because StatsLogReport
+ // expects vector<KeyValuePair>.
+ if (mDimensionKeyMap.find(eventKey) == mDimensionKeyMap.end()) {
+ mDimensionKeyMap[eventKey] = key;
+ }
+ } else {
+ eventKey = DEFAULT_DIMENSION_KEY;
+ }
+
+ bool condition;
+
+ map<string, HashableDimensionKey> conditionKeys;
+ if (mConditionSliced) {
+ for (const auto& link : mConditionLinks) {
+ HashableDimensionKey conditionKey = getDimensionKeyForCondition(event, link);
+ conditionKeys[link.condition()] = conditionKey;
+ }
+ if (mWizard->query(mConditionTrackerIndex, conditionKeys) != ConditionState::kTrue) {
+ condition = false;
+ } else {
+ condition = true;
+ }
+ } else {
+ condition = mCondition;
+ }
+
+ onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index afaab64..496b145 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -48,7 +48,7 @@
virtual ~MetricProducer(){};
// Consume the parsed stats log entry that already matched the "what" of the metric.
- virtual void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) = 0;
+ void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event);
virtual void onConditionChanged(const bool condition) = 0;
@@ -86,6 +86,26 @@
std::unordered_map<HashableDimensionKey, std::vector<KeyValuePair>> mDimensionKeyMap;
std::vector<EventConditionLink> mConditionLinks;
+
+ /*
+ * Individual metrics can implement their own business logic here. All pre-processing is done.
+ *
+ * [matcherIndex]: the index of the matcher which matched this event. This is interesting to
+ * DurationMetric, because it has start/stop/stop_all 3 matchers.
+ * [eventKey]: the extracted dimension key for the final output. if the metric doesn't have
+ * dimensions, it will be DEFAULT_DIMENSION_KEY
+ * [conditionKey]: the keys of conditions which should be used to query the condition for this
+ * target event (from EventConditionLink). This is passed to individual metrics
+ * because DurationMetric needs it to be cached.
+ * [condition]: whether condition is met. If condition is sliced, this is the result coming from
+ * query with ConditionWizard; If condition is not sliced, this is the
+ * nonSlicedCondition.
+ * [event]: the log event, just in case the metric needs its data, e.g., EventMetric.
+ */
+ virtual void onMatchedLogEventInternal(
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event) = 0;
};
} // namespace statsd
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index 19403c0..fdfe8ef 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -42,12 +42,10 @@
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
simpleMatcher->set_tag(TAG_ID);
- // Set up the event
- android_log_event_list list(TAG_ID);
+ LogEvent event(TAG_ID);
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -64,13 +62,13 @@
keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
// Set up the event
- android_log_event_list list(TAG_ID);
- list << true;
- list << false;
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << true;
+ *list << false;
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
keyValue1->set_eq_bool(true);
@@ -100,12 +98,12 @@
keyValue->set_eq_string("some value");
// Set up the event
- android_log_event_list list(TAG_ID);
- list << "some value";
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << "some value";
// Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
EXPECT_TRUE(matchesSimple(*simpleMatcher, event));
@@ -121,12 +119,11 @@
keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
// Set up the event
- android_log_event_list list(TAG_ID);
- list << 11;
+ LogEvent event(TAG_ID);
+ auto list = event.GetAndroidLogEventList();
+ *list << 11;
- // Convert to a LogEvent
- list.convert_to_reader();
- LogEvent event(999, &list);
+ event.init();
// Test
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 3c53063..ab8edee 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -6533,6 +6533,52 @@
}
/**
+ * Called by device owner to set the system wall clock time. This only takes effect if called
+ * when {@link android.provider.Settings.Global#AUTO_TIME} is 0, otherwise {@code false} will be
+ * returned.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+ * @param millis time in milliseconds since the Epoch
+ * @return {@code true} if set time succeeded, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public boolean setTime(@NonNull ComponentName admin, long millis) {
+ throwIfParentInstance("setTime");
+ if (mService != null) {
+ try {
+ return mService.setTime(admin, millis);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Called by device owner to set the system's persistent default time zone. This only takes
+ * effect if called when {@link android.provider.Settings.Global#AUTO_TIME_ZONE} is 0, otherwise
+ * {@code false} will be returned.
+ *
+ * @see android.app.AlarmManager#setTimeZone(String)
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with
+ * @param timeZone one of the Olson ids from the list returned by
+ * {@link java.util.TimeZone#getAvailableIDs}
+ * @return {@code true} if set timezone succeeded, {@code false} otherwise.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public boolean setTimeZone(@NonNull ComponentName admin, String timeZone) {
+ throwIfParentInstance("setTimeZone");
+ if (mService != null) {
+ try {
+ return mService.setTimeZone(admin, timeZone);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Called by profile or device owners to update {@link android.provider.Settings.Secure}
* settings. Validation that the value of the setting is in the correct form for the setting
* type should be performed by the caller.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8865a05..e77c186 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -229,6 +229,9 @@
void setGlobalSetting(in ComponentName who, in String setting, in String value);
void setSecureSetting(in ComponentName who, in String setting, in String value);
+ boolean setTime(in ComponentName who, long millis);
+ boolean setTimeZone(in ComponentName who, String timeZone);
+
void setMasterVolumeMuted(in ComponentName admin, boolean on);
boolean isMasterVolumeMuted(in ComponentName admin);
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 969b19e..37bb6b0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -20,17 +20,19 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
-import android.annotation.SystemService;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
+import android.app.IServiceConnection;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
+import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Handler;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -1051,43 +1053,23 @@
* The appWidgetId specified must already be bound to the calling AppWidgetHost via
* {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
*
- * @param packageName The package from which the binding is requested.
* @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
* @param connection The callback interface to be notified when a connection is made or lost.
+ * @param flags Flags used for binding to the service
+ *
+ * @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
* @hide
*/
- public void bindRemoteViewsService(String packageName, int appWidgetId, Intent intent,
- IBinder connection) {
+ public boolean bindRemoteViewsService(Context context, int appWidgetId, Intent intent,
+ IServiceConnection connection, @Context.BindServiceFlags int flags) {
if (mService == null) {
- return;
+ return false;
}
try {
- mService.bindRemoteViewsService(packageName, appWidgetId, intent, connection);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
- * Unbinds the RemoteViewsService for a given appWidgetId and intent.
- *
- * The appWidgetId specified muse already be bound to the calling AppWidgetHost via
- * {@link android.appwidget.AppWidgetManager#bindAppWidgetId AppWidgetManager.bindAppWidgetId()}.
- *
- * @param packageName The package from which the binding is requested.
- * @param appWidgetId The AppWidget instance for which to bind the RemoteViewsService.
- * @param intent The intent of the service which will be providing the data to the
- * RemoteViewsAdapter.
- * @hide
- */
- public void unbindRemoteViewsService(String packageName, int appWidgetId, Intent intent) {
- if (mService == null) {
- return;
- }
- try {
- mService.unbindRemoteViewsService(packageName, appWidgetId, intent);
+ return mService.bindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent,
+ context.getIApplicationThread(), context.getActivityToken(), connection, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index d8f9567..20f6c8e 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,6 +16,8 @@
package android.os;
+import android.os.StatsLogEventWrapper;
+
/**
* Binder interface to communicate with the Java-based statistics service helper.
* {@hide}
@@ -50,5 +52,5 @@
oneway void cancelPollingAlarms();
/** Pull the specified data. Results will be sent to statsd when complete. */
- String pullData(int pullCode);
+ StatsLogEventWrapper[] pullData(int pullCode);
}
diff --git a/core/java/android/service/autofill/SaveInfo.aidl b/core/java/android/os/StatsLogEventWrapper.aidl
similarity index 69%
rename from core/java/android/service/autofill/SaveInfo.aidl
rename to core/java/android/os/StatsLogEventWrapper.aidl
index 8cda608..766343e 100644
--- a/core/java/android/service/autofill/SaveInfo.aidl
+++ b/core/java/android/os/StatsLogEventWrapper.aidl
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2017, The Android Open Source Project
+/*
+ * 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
+ * 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,
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.service.autofill;
+package android.os;
-parcelable SaveInfo;
+/** @hide */
+parcelable StatsLogEventWrapper cpp_header "android/os/StatsLogEventWrapper.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsLogEventWrapper.java b/core/java/android/os/StatsLogEventWrapper.java
new file mode 100644
index 0000000..9491bec
--- /dev/null
+++ b/core/java/android/os/StatsLogEventWrapper.java
@@ -0,0 +1,141 @@
+/*
+ * 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.os;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Wrapper class for sending data from Android OS to StatsD.
+ *
+ * @hide
+ */
+public final class StatsLogEventWrapper implements Parcelable {
+ private ByteArrayOutputStream mStorage = new ByteArrayOutputStream();
+
+ // Below are constants copied from log/log.h
+ private static final int EVENT_TYPE_INT = 0; /* int32_t */
+ private static final int EVENT_TYPE_LONG = 1; /* int64_t */
+ private static final int EVENT_TYPE_STRING = 2;
+ private static final int EVENT_TYPE_LIST = 3;
+ private static final int EVENT_TYPE_FLOAT = 4;
+
+ /**
+ * Creates a log_event that is binary-encoded as implemented in
+ * system/core/liblog/log_event_list.c; this allows us to use the same parsing logic in statsd
+ * for pushed and pulled data. The write* methods must be called in the same order as their
+ * field number. There is no checking that the correct number of write* methods is called.
+ * We also write an END_LIST character before beginning to write to parcel, but this END_LIST
+ * may be unnecessary.
+ *
+ * @param tag The integer representing the tag for this event.
+ * @param fields The number of fields specified in this event.
+ */
+ public StatsLogEventWrapper(int tag, int fields) {
+ // Write four bytes from tag, starting with least-significant bit.
+ write4Bytes(tag);
+ mStorage.write(EVENT_TYPE_LIST); // This is required to start the log entry.
+ mStorage.write(fields); // Indicate number of elements in this list.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public static final Parcelable.Creator<StatsLogEventWrapper> CREATOR = new
+ Parcelable.Creator<StatsLogEventWrapper>() {
+ public StatsLogEventWrapper createFromParcel(Parcel in) {
+ return new StatsLogEventWrapper(in);
+ }
+
+ public StatsLogEventWrapper[] newArray(int size) {
+ return new StatsLogEventWrapper[size];
+ }
+ };
+
+ private void write4Bytes(int val) {
+ mStorage.write(val);
+ mStorage.write(val >>> 8);
+ mStorage.write(val >>> 16);
+ mStorage.write(val >>> 24);
+ }
+
+ private void write8Bytes(long val) {
+ write4Bytes((int) (val & 0xFFFFFFFF)); // keep the lowe 32-bits
+ write4Bytes((int) (val >>> 32)); // Write the high 32-bits.
+ }
+
+ /**
+ * Adds 32-bit integer to output.
+ */
+ public void writeInt(int val) {
+ mStorage.write(EVENT_TYPE_INT);
+ write4Bytes(val);
+ }
+
+ /**
+ * Adds 64-bit long to output.
+ */
+ public void writeLong(long val) {
+ mStorage.write(EVENT_TYPE_LONG);
+ write8Bytes(val);
+ }
+
+ /**
+ * Adds a 4-byte floating point value to output.
+ */
+ public void writeFloat(float val) {
+ int v = Float.floatToIntBits(val);
+ mStorage.write(EVENT_TYPE_FLOAT);
+ write4Bytes(v);
+ }
+
+ /**
+ * Adds a string to the output.
+ */
+ public void writeString(String val) {
+ mStorage.write(EVENT_TYPE_STRING);
+ write4Bytes(val.length());
+ byte[] bytes = val.getBytes(StandardCharsets.UTF_8);
+ mStorage.write(bytes, 0, bytes.length);
+ }
+
+ private StatsLogEventWrapper(Parcel in) {
+ readFromParcel(in);
+ }
+
+ /**
+ * Writes the stored fields to a byte array. Will first write a new-line character to denote
+ * END_LIST before writing contents to byte array.
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ mStorage.write(10); // new-line character is same as END_LIST
+ out.writeByteArray(mStorage.toByteArray());
+ }
+
+ /**
+ * Not implemented.
+ */
+ public void readFromParcel(Parcel in) {
+ // Not needed since this java class is for sending to statsd only.
+ }
+
+ /**
+ * Boilerplate for Parcel.
+ */
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/service/autofill/Dataset.aidl b/core/java/android/service/autofill/Dataset.aidl
deleted file mode 100644
index 2342c5f..0000000
--- a/core/java/android/service/autofill/Dataset.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.autofill;
-
-parcelable Dataset;
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index b2cdef2..331130e 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -150,8 +150,16 @@
public String toString() {
if (!sDebug) return super.toString();
- return new StringBuilder("Dataset " + mId + " [")
- .append("fieldIds=").append(mFieldIds)
+ final StringBuilder builder = new StringBuilder("Dataset[id=");
+ if (mId == null) {
+ builder.append("null");
+ } else {
+ // Cannot disclose id because it could contain PII.
+ builder.append(mId.length()).append("_chars");
+ }
+
+ return builder
+ .append(", fieldIds=").append(mFieldIds)
.append(", fieldValues=").append(mFieldValues)
.append(", fieldPresentations=")
.append(mFieldPresentations == null ? 0 : mFieldPresentations.size())
diff --git a/core/java/android/service/autofill/FillEventHistory.java b/core/java/android/service/autofill/FillEventHistory.java
index d6c0dbf..b1857b3 100644
--- a/core/java/android/service/autofill/FillEventHistory.java
+++ b/core/java/android/service/autofill/FillEventHistory.java
@@ -129,6 +129,11 @@
}
@Override
+ public String toString() {
+ return mEvents == null ? "no events" : mEvents.toString();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index d91cebb..d2033fa 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -71,7 +71,7 @@
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
mSaveInfo = builder.mSaveInfo;
- mClientState = builder.mCLientState;
+ mClientState = builder.mClientState;
mPresentation = builder.mPresentation;
mAuthentication = builder.mAuthentication;
mAuthenticationIds = builder.mAuthenticationIds;
@@ -145,7 +145,7 @@
public static final class Builder {
private ArrayList<Dataset> mDatasets;
private SaveInfo mSaveInfo;
- private Bundle mCLientState;
+ private Bundle mClientState;
private RemoteViews mPresentation;
private IntentSender mAuthentication;
private AutofillId[] mAuthenticationIds;
@@ -288,7 +288,7 @@
*/
public Builder setClientState(@Nullable Bundle clientState) {
throwIfDestroyed();
- mCLientState = clientState;
+ mClientState = clientState;
return this;
}
diff --git a/core/java/android/view/autofill/AutoFillType.aidl b/core/java/android/view/autofill/AutoFillType.aidl
deleted file mode 100644
index 4606b48..0000000
--- a/core/java/android/view/autofill/AutoFillType.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-/*
- * TODO(b/35956626): remove once clients use getAutoFilltype()
- */
-parcelable AutoFillType;
\ No newline at end of file
diff --git a/core/java/android/view/autofill/AutoFillValue.aidl b/core/java/android/view/autofill/AutoFillValue.aidl
deleted file mode 100644
index 05b7562..0000000
--- a/core/java/android/view/autofill/AutoFillValue.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.autofill;
-
-// @deprecated TODO(b/35956626): remove once clients use AutofillValue
-parcelable AutoFillValue;
\ No newline at end of file
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index af09592..384f4f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -476,17 +476,6 @@
stopTextActionModeWithPreservingSelection();
}
- void invalidateMagnifier() {
- final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics();
- invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels);
- }
-
- void invalidateMagnifier(final float l, final float t, final float r, final float b) {
- if (mMagnifier != null) {
- mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b)));
- }
- }
-
private void discardTextDisplayLists() {
if (mTextRenderNodes != null) {
for (int i = 0; i < mTextRenderNodes.length; i++) {
@@ -4550,17 +4539,15 @@
final Layout layout = mTextView.getLayout();
final int lineNumber = layout.getLineForOffset(offset);
// Horizontally snap to character offset.
- final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+ final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
+ + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
// Vertically snap to middle of current line.
final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
- + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
- final int[] coordinatesOnScreen = new int[2];
- mTextView.getLocationOnScreen(coordinatesOnScreen);
- final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true);
- final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false);
+ + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+ + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
suspendBlink();
- mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+ mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
}
protected final void dismissMagnifier() {
diff --git a/core/java/android/widget/RemoteViewsAdapter.java b/core/java/android/widget/RemoteViewsAdapter.java
index 51277e4..e5ae0ca 100644
--- a/core/java/android/widget/RemoteViewsAdapter.java
+++ b/core/java/android/widget/RemoteViewsAdapter.java
@@ -16,11 +16,14 @@
package android.widget;
-import android.Manifest;
+import android.annotation.WorkerThread;
+import android.app.IServiceConnection;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.os.Handler;
import android.os.HandlerThread;
@@ -29,7 +32,6 @@
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
@@ -39,7 +41,6 @@
import android.view.ViewGroup;
import android.widget.RemoteViews.OnClickHandler;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
import java.lang.ref.WeakReference;
@@ -49,52 +50,33 @@
import java.util.concurrent.Executor;
/**
- * An adapter to a RemoteViewsService which fetches and caches RemoteViews
- * to be later inflated as child views.
+ * An adapter to a RemoteViewsService which fetches and caches RemoteViews to be later inflated as
+ * child views.
+ *
+ * The adapter runs in the host process, typically a Launcher app.
+ *
+ * It makes a service connection to the {@link RemoteViewsService} running in the
+ * AppWidgetsProvider's process. This connection is made on a background thread (and proxied via
+ * the platform to get the bind permissions) and all interaction with the service is done on the
+ * background thread.
+ *
+ * On first bind, the adapter will load can cache the RemoteViews locally. Afterwards the
+ * connection is only made when new RemoteViews are required.
+ * @hide
*/
-/** @hide */
public class RemoteViewsAdapter extends BaseAdapter implements Handler.Callback {
- private static final String MULTI_USER_PERM = Manifest.permission.INTERACT_ACROSS_USERS_FULL;
private static final String TAG = "RemoteViewsAdapter";
// The max number of items in the cache
- private static final int sDefaultCacheSize = 40;
+ private static final int DEFAULT_CACHE_SIZE = 40;
// The delay (in millis) to wait until attempting to unbind from a service after a request.
// This ensures that we don't stay continually bound to the service and that it can be destroyed
// if we need the memory elsewhere in the system.
- private static final int sUnbindServiceDelay = 5000;
+ private static final int UNBIND_SERVICE_DELAY = 5000;
// Default height for the default loading view, in case we cannot get inflate the first view
- private static final int sDefaultLoadingViewHeight = 50;
-
- // Type defs for controlling different messages across the main and worker message queues
- private static final int sDefaultMessageType = 0;
- private static final int sUnbindServiceMessageType = 1;
-
- private final Context mContext;
- private final Intent mIntent;
- private final int mAppWidgetId;
- private final Executor mAsyncViewLoadExecutor;
-
- private RemoteViewsAdapterServiceConnection mServiceConnection;
- private WeakReference<RemoteAdapterConnectionCallback> mCallback;
- private OnClickHandler mRemoteViewsOnClickHandler;
- private final FixedSizeRemoteViewsCache mCache;
- private int mVisibleWindowLowerBound;
- private int mVisibleWindowUpperBound;
-
- // A flag to determine whether we should notify data set changed after we connect
- private boolean mNotifyDataSetChangedAfterOnServiceConnected = false;
-
- // The set of requested views that are to be notified when the associated RemoteViews are
- // loaded.
- private RemoteViewsFrameLayoutRefSet mRequestedViews;
-
- private HandlerThread mWorkerThread;
- // items may be interrupted within the normally processed queues
- private Handler mWorkerQueue;
- private Handler mMainQueue;
+ private static final int DEFAULT_LOADING_VIEW_HEIGHT = 50;
// We cache the FixedSizeRemoteViewsCaches across orientation. These are the related data
// structures;
@@ -111,6 +93,26 @@
// duration, the cache is dropped.
private static final int REMOTE_VIEWS_CACHE_DURATION = 5000;
+ private final Context mContext;
+ private final Intent mIntent;
+ private final int mAppWidgetId;
+ private final Executor mAsyncViewLoadExecutor;
+
+ private OnClickHandler mRemoteViewsOnClickHandler;
+ private final FixedSizeRemoteViewsCache mCache;
+ private int mVisibleWindowLowerBound;
+ private int mVisibleWindowUpperBound;
+
+ // The set of requested views that are to be notified when the associated RemoteViews are
+ // loaded.
+ private RemoteViewsFrameLayoutRefSet mRequestedViews;
+
+ private final HandlerThread mWorkerThread;
+ // items may be interrupted within the normally processed queues
+ private final Handler mMainHandler;
+ private final RemoteServiceHandler mServiceHandler;
+ private final RemoteAdapterConnectionCallback mCallback;
+
// Used to indicate to the AdapterView that it can use this Adapter immediately after
// construction (happens when we have a cached FixedSizeRemoteViewsCache).
private boolean mDataReady = false;
@@ -158,154 +160,192 @@
}
}
+ static final int MSG_REQUEST_BIND = 1;
+ static final int MSG_NOTIFY_DATA_SET_CHANGED = 2;
+ static final int MSG_LOAD_NEXT_ITEM = 3;
+ static final int MSG_UNBIND_SERVICE = 4;
+
+ private static final int MSG_MAIN_HANDLER_COMMIT_METADATA = 1;
+ private static final int MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED = 2;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED = 3;
+ private static final int MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED = 4;
+ private static final int MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED = 5;
+
/**
- * The service connection that gets populated when the RemoteViewsService is
- * bound. This must be a static inner class to ensure that no references to the outer
- * RemoteViewsAdapter instance is retained (this would prevent the RemoteViewsAdapter from being
- * garbage collected, and would cause us to leak activities due to the caching mechanism for
- * FrameLayouts in the adapter).
+ * Handler for various interactions with the {@link RemoteViewsService}.
*/
- private static class RemoteViewsAdapterServiceConnection extends
- IRemoteViewsAdapterConnection.Stub {
- private boolean mIsConnected;
- private boolean mIsConnecting;
- private WeakReference<RemoteViewsAdapter> mAdapter;
+ private static class RemoteServiceHandler extends Handler implements ServiceConnection {
+
+ private final WeakReference<RemoteViewsAdapter> mAdapter;
+ private final Context mContext;
+
private IRemoteViewsFactory mRemoteViewsFactory;
- public RemoteViewsAdapterServiceConnection(RemoteViewsAdapter adapter) {
- mAdapter = new WeakReference<RemoteViewsAdapter>(adapter);
+ // The last call to notifyDataSetChanged didn't succeed, try again on next service bind.
+ private boolean mNotifyDataSetChangedPending = false;
+ private boolean mBindRequested = false;
+
+ RemoteServiceHandler(Looper workerLooper, RemoteViewsAdapter adapter, Context context) {
+ super(workerLooper);
+ mAdapter = new WeakReference<>(adapter);
+ mContext = context;
}
- public synchronized void bind(Context context, int appWidgetId, Intent intent) {
- if (!mIsConnecting) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.bindRemoteViewsService(context.getOpPackageName(), appWidgetId,
- intent, asBinder());
- } else {
- Slog.w(TAG, "bind: adapter was null");
- }
- mIsConnecting = true;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "bind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
- }
- }
- }
-
- public synchronized void unbind(Context context, int appWidgetId, Intent intent) {
- try {
- RemoteViewsAdapter adapter;
- final AppWidgetManager mgr = AppWidgetManager.getInstance(context);
- if ((adapter = mAdapter.get()) != null) {
- mgr.unbindRemoteViewsService(context.getOpPackageName(), appWidgetId, intent);
- } else {
- Slog.w(TAG, "unbind: adapter was null");
- }
- mIsConnecting = false;
- } catch (Exception e) {
- Log.e("RVAServiceConnection", "unbind(): " + e.getMessage());
- mIsConnecting = false;
- mIsConnected = false;
- }
- }
-
- public synchronized void onServiceConnected(IBinder service) {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // This is called on the same thread.
mRemoteViewsFactory = IRemoteViewsFactory.Stub.asInterface(service);
+ enqueueDeferredUnbindServiceMessage();
- // Remove any deferred unbind messages
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter == null) {
+ return;
+ }
- // Queue up work that we need to do for the callback to run
- adapter.mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (adapter.mNotifyDataSetChangedAfterOnServiceConnected) {
- // Handle queued notifyDataSetChanged() if necessary
- adapter.onNotifyDataSetChanged();
- } else {
- IRemoteViewsFactory factory =
- adapter.mServiceConnection.getRemoteViewsFactory();
- try {
- if (!factory.isCreated()) {
- // We only call onDataSetChanged() if this is the factory was just
- // create in response to this bind
- factory.onDataSetChanged();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
-
- // Return early to prevent anything further from being notified
- // (effectively nothing has changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error notifying factory of data set changed in " +
- "onServiceConnected(): " + e.getMessage());
- }
-
- // Request meta data so that we have up to date data when calling back to
- // the remote adapter callback
- adapter.updateTemporaryMetaData();
-
- // Notify the host that we've connected
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (adapter.mCache) {
- adapter.mCache.commitTemporaryMetaData();
- }
-
- final RemoteAdapterConnectionCallback callback =
- adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterConnected();
- }
- }
- });
- }
-
- // Enqueue unbind message
- adapter.enqueueDeferredUnbindServiceMessage();
- mIsConnected = true;
- mIsConnecting = false;
+ if (mNotifyDataSetChangedPending) {
+ mNotifyDataSetChangedPending = false;
+ Message msg = Message.obtain(this, MSG_NOTIFY_DATA_SET_CHANGED);
+ handleMessage(msg);
+ msg.recycle();
+ } else {
+ if (!sendNotifyDataSetChange(false)) {
+ return;
}
- });
+
+ // Request meta data so that we have up to date data when calling back to
+ // the remote adapter callback
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED);
+ }
}
- public synchronized void onServiceDisconnected() {
- mIsConnected = false;
- mIsConnecting = false;
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
mRemoteViewsFactory = null;
+ RemoteViewsAdapter adapter = mAdapter.get();
+ if (adapter != null) {
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED);
+ }
+ }
- // Clear the main/worker queues
- final RemoteViewsAdapter adapter = mAdapter.get();
- if (adapter == null) return;
+ @Override
+ public void handleMessage(Message msg) {
+ RemoteViewsAdapter adapter = mAdapter.get();
- adapter.mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- // Dequeue any unbind messages
- adapter.mMainQueue.removeMessages(sUnbindServiceMessageType);
-
- final RemoteAdapterConnectionCallback callback = adapter.mCallback.get();
- if (callback != null) {
- callback.onRemoteAdapterDisconnected();
+ switch (msg.what) {
+ case MSG_REQUEST_BIND: {
+ if (adapter == null || mRemoteViewsFactory != null) {
+ enqueueDeferredUnbindServiceMessage();
}
+ if (mBindRequested) {
+ return;
+ }
+ int flags = Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE;
+ final IServiceConnection sd = mContext.getServiceDispatcher(this, this, flags);
+ Intent intent = (Intent) msg.obj;
+ int appWidgetId = msg.arg1;
+ mBindRequested = AppWidgetManager.getInstance(mContext)
+ .bindRemoteViewsService(mContext, appWidgetId, intent, sd, flags);
+ return;
}
- });
+ case MSG_NOTIFY_DATA_SET_CHANGED: {
+ enqueueDeferredUnbindServiceMessage();
+ if (adapter == null) {
+ return;
+ }
+ if (mRemoteViewsFactory == null) {
+ mNotifyDataSetChangedPending = true;
+ adapter.requestBindService();
+ return;
+ }
+ if (!sendNotifyDataSetChange(true)) {
+ return;
+ }
+
+ // Flush the cache so that we can reload new items from the service
+ synchronized (adapter.mCache) {
+ adapter.mCache.reset();
+ }
+
+ // Re-request the new metadata (only after the notification to the factory)
+ adapter.updateTemporaryMetaData(mRemoteViewsFactory);
+ int newCount;
+ int[] visibleWindow;
+ synchronized (adapter.mCache.getTemporaryMetaData()) {
+ newCount = adapter.mCache.getTemporaryMetaData().count;
+ visibleWindow = adapter.getVisibleWindow(newCount);
+ }
+
+ // Pre-load (our best guess of) the views which are currently visible in the
+ // AdapterView. This mitigates flashing and flickering of loading views when a
+ // widget notifies that its data has changed.
+ for (int position : visibleWindow) {
+ // Because temporary meta data is only ever modified from this thread
+ // (ie. mWorkerThread), it is safe to assume that count is a valid
+ // representation.
+ if (position < newCount) {
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, false);
+ }
+ }
+
+ // Propagate the notification back to the base adapter
+ adapter.mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_COMMIT_METADATA);
+ adapter.mMainHandler.sendEmptyMessage(
+ MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
+ return;
+ }
+
+ case MSG_LOAD_NEXT_ITEM: {
+ if (adapter == null || mRemoteViewsFactory == null) {
+ return;
+ }
+ removeMessages(MSG_UNBIND_SERVICE);
+ // Get the next index to load
+ final int position = adapter.mCache.getNextIndexToLoad();
+ if (position > -1) {
+ // Load the item, and notify any existing RemoteViewsFrameLayouts
+ adapter.updateRemoteViews(mRemoteViewsFactory, position, true);
+
+ // Queue up for the next one to load
+ sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ } else {
+ // No more items to load, so queue unbind
+ enqueueDeferredUnbindServiceMessage();
+ }
+ return;
+ }
+ case MSG_UNBIND_SERVICE: {
+ unbindNow();
+ return;
+ }
+ }
}
- public synchronized IRemoteViewsFactory getRemoteViewsFactory() {
- return mRemoteViewsFactory;
+ protected void unbindNow() {
+ if (mBindRequested) {
+ mBindRequested = false;
+ mContext.unbindService(this);
+ }
+ mRemoteViewsFactory = null;
}
- public synchronized boolean isConnected() {
- return mIsConnected;
+ private boolean sendNotifyDataSetChange(boolean always) {
+ try {
+ if (always || !mRemoteViewsFactory.isCreated()) {
+ mRemoteViewsFactory.onDataSetChanged();
+ }
+ return true;
+ } catch (RemoteException | RuntimeException e) {
+ Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
+ return false;
+ }
+ }
+
+ private void enqueueDeferredUnbindServiceMessage() {
+ removeMessages(MSG_UNBIND_SERVICE);
+ sendEmptyMessageDelayed(MSG_UNBIND_SERVICE, UNBIND_SERVICE_DELAY);
}
}
@@ -507,7 +547,6 @@
*
*/
private static class FixedSizeRemoteViewsCache {
- private static final String TAG = "FixedSizeRemoteViewsCache";
// The meta data related to all the RemoteViews, ie. count, is stable, etc.
// The meta data objects are made final so that they can be locked on independently
@@ -671,7 +710,7 @@
}
}
- int count = 0;
+ int count;
synchronized (mMetaData) {
count = mMetaData.count;
}
@@ -786,9 +825,11 @@
// Initialize the worker thread
mWorkerThread = new HandlerThread("RemoteViewsCache-loader");
mWorkerThread.start();
- mWorkerQueue = new Handler(mWorkerThread.getLooper());
- mMainQueue = new Handler(Looper.myLooper(), this);
+ mMainHandler = new Handler(Looper.myLooper(), this);
+ mServiceHandler = new RemoteServiceHandler(mWorkerThread.getLooper(), this,
+ context.getApplicationContext());
mAsyncViewLoadExecutor = useAsyncLoader ? new HandlerThreadExecutor(mWorkerThread) : null;
+ mCallback = callback;
if (sCacheRemovalThread == null) {
sCacheRemovalThread = new HandlerThread("RemoteViewsAdapter-cachePruner");
@@ -796,10 +837,6 @@
sCacheRemovalQueue = new Handler(sCacheRemovalThread.getLooper());
}
- // Initialize the cache and the service connection on startup
- mCallback = new WeakReference<RemoteAdapterConnectionCallback>(callback);
- mServiceConnection = new RemoteViewsAdapterServiceConnection(this);
-
RemoteViewsCacheKey key = new RemoteViewsCacheKey(new Intent.FilterComparison(mIntent),
mAppWidgetId);
@@ -814,7 +851,7 @@
}
}
} else {
- mCache = new FixedSizeRemoteViewsCache(sDefaultCacheSize);
+ mCache = new FixedSizeRemoteViewsCache(DEFAULT_CACHE_SIZE);
}
if (!mDataReady) {
requestBindService();
@@ -825,9 +862,8 @@
@Override
protected void finalize() throws Throwable {
try {
- if (mWorkerThread != null) {
- mWorkerThread.quit();
- }
+ mServiceHandler.unbindNow();
+ mWorkerThread.quit();
} finally {
super.finalize();
}
@@ -864,16 +900,13 @@
sCachedRemoteViewsCaches.put(key, mCache);
}
- Runnable r = new Runnable() {
- @Override
- public void run() {
- synchronized (sCachedRemoteViewsCaches) {
- if (sCachedRemoteViewsCaches.containsKey(key)) {
- sCachedRemoteViewsCaches.remove(key);
- }
- if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
- sRemoteViewsCacheRemoveRunnables.remove(key);
- }
+ Runnable r = () -> {
+ synchronized (sCachedRemoteViewsCaches) {
+ if (sCachedRemoteViewsCaches.containsKey(key)) {
+ sCachedRemoteViewsCaches.remove(key);
+ }
+ if (sRemoteViewsCacheRemoveRunnables.containsKey(key)) {
+ sRemoteViewsCacheRemoveRunnables.remove(key);
}
}
};
@@ -882,54 +915,8 @@
}
}
- private void loadNextIndexInBackground() {
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- if (mServiceConnection.isConnected()) {
- // Get the next index to load
- int position = -1;
- synchronized (mCache) {
- position = mCache.getNextIndexToLoad();
- }
- if (position > -1) {
- // Load the item, and notify any existing RemoteViewsFrameLayouts
- updateRemoteViews(position, true);
-
- // Queue up for the next one to load
- loadNextIndexInBackground();
- } else {
- // No more items to load, so queue unbind
- enqueueDeferredUnbindServiceMessage();
- }
- }
- }
- });
- }
-
- private void processException(String method, Exception e) {
- Log.e("RemoteViewsAdapter", "Error in " + method + ": " + e.getMessage());
-
- // If we encounter a crash when updating, we should reset the metadata & cache and trigger
- // a notifyDataSetChanged to update the widget accordingly
- final RemoteViewsMetaData metaData = mCache.getMetaData();
- synchronized (metaData) {
- metaData.reset();
- }
- synchronized (mCache) {
- mCache.reset();
- }
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- superNotifyDataSetChanged();
- }
- });
- }
-
- private void updateTemporaryMetaData() {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateTemporaryMetaData(IRemoteViewsFactory factory) {
try {
// get the properties/first view (so that we can use it to
// measure our dummy views)
@@ -953,40 +940,40 @@
tmpMetaData.count = count;
tmpMetaData.loadingTemplate = loadingTemplate;
}
- } catch(RemoteException e) {
- processException("updateMetaData", e);
- } catch(RuntimeException e) {
- processException("updateMetaData", e);
+ } catch (RemoteException | RuntimeException e) {
+ Log.e("RemoteViewsAdapter", "Error in updateMetaData: " + e.getMessage());
+
+ // If we encounter a crash when updating, we should reset the metadata & cache
+ // and trigger a notifyDataSetChanged to update the widget accordingly
+ synchronized (mCache.getMetaData()) {
+ mCache.getMetaData().reset();
+ }
+ synchronized (mCache) {
+ mCache.reset();
+ }
+ mMainHandler.sendEmptyMessage(MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED);
}
}
- private void updateRemoteViews(final int position, boolean notifyWhenLoaded) {
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
-
+ @WorkerThread
+ private void updateRemoteViews(IRemoteViewsFactory factory, int position,
+ boolean notifyWhenLoaded) {
// Load the item information from the remote service
- RemoteViews remoteViews = null;
- long itemId = 0;
+ final RemoteViews remoteViews;
+ final long itemId;
try {
remoteViews = factory.getViewAt(position);
itemId = factory.getItemId(position);
- } catch (RemoteException e) {
+
+ if (remoteViews == null) {
+ throw new RuntimeException("Null remoteViews");
+ }
+ } catch (RemoteException | RuntimeException e) {
Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
// Return early to prevent additional work in re-centering the view cache, and
// swapping from the loading view
return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + e.getMessage());
- return;
- }
-
- if (remoteViews == null) {
- // If a null view was returned, we break early to prevent it from getting
- // into our cache and causing problems later. The effect is that the child at this
- // position will remain as a loading view until it is updated.
- Log.e(TAG, "Error in updateRemoteViews(" + position + "): " + " null RemoteViews " +
- "returned from RemoteViewsFactory.");
- return;
}
if (remoteViews.mApplication != null) {
@@ -1013,21 +1000,15 @@
}
synchronized (mCache) {
if (viewTypeInRange) {
- int[] visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, cacheCount);
+ int[] visibleWindow = getVisibleWindow(cacheCount);
// Cache the RemoteViews we loaded
mCache.insert(position, remoteViews, itemId, visibleWindow);
- // Notify all the views that we have previously returned for this index that
- // there is new data for it.
- final RemoteViews rv = remoteViews;
if (notifyWhenLoaded) {
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- mRequestedViews.notifyOnRemoteViewsLoaded(position, rv);
- }
- });
+ // Notify all the views that we have previously returned for this index that
+ // there is new data for it.
+ Message.obtain(mMainHandler, MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED, position, 0,
+ remoteViews).sendToTarget();
}
} else {
// We need to log an error here, as the the view type count specified by the
@@ -1066,7 +1047,7 @@
}
public int getItemViewType(int position) {
- int typeId = 0;
+ final int typeId;
synchronized (mCache) {
if (mCache.containsMetaDataAt(position)) {
typeId = mCache.getMetaDataAt(position).typeId;
@@ -1097,14 +1078,13 @@
synchronized (mCache) {
RemoteViews rv = mCache.getRemoteViewsAt(position);
boolean isInCache = (rv != null);
- boolean isConnected = mServiceConnection.isConnected();
boolean hasNewItems = false;
if (convertView != null && convertView instanceof RemoteViewsFrameLayout) {
mRequestedViews.removeView((RemoteViewsFrameLayout) convertView);
}
- if (!isInCache && !isConnected) {
+ if (!isInCache) {
// Requesting bind service will trigger a super.notifyDataSetChanged(), which will
// in turn trigger another request to getView()
requestBindService();
@@ -1124,7 +1104,9 @@
if (isInCache) {
// Apply the view synchronously if possible, to avoid flickering
layout.onRemoteViewsLoaded(rv, mRemoteViewsOnClickHandler, false);
- if (hasNewItems) loadNextIndexInBackground();
+ if (hasNewItems) {
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
+ }
} else {
// If the views is not loaded, apply the loading view. If the loading view doesn't
// exist, the layout will create a default view based on the firstView height.
@@ -1134,7 +1116,7 @@
false);
mRequestedViews.add(position, layout);
mCache.queueRequestedPositionToLoad(position);
- loadNextIndexInBackground();
+ mServiceHandler.sendEmptyMessage(MSG_LOAD_NEXT_ITEM);
}
return layout;
}
@@ -1158,69 +1140,12 @@
return getCount() <= 0;
}
- private void onNotifyDataSetChanged() {
- // Complete the actual notifyDataSetChanged() call initiated earlier
- IRemoteViewsFactory factory = mServiceConnection.getRemoteViewsFactory();
- try {
- factory.onDataSetChanged();
- } catch (RemoteException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
-
- // Return early to prevent from further being notified (since nothing has
- // changed)
- return;
- } catch (RuntimeException e) {
- Log.e(TAG, "Error in updateNotifyDataSetChanged(): " + e.getMessage());
- return;
- }
-
- // Flush the cache so that we can reload new items from the service
- synchronized (mCache) {
- mCache.reset();
- }
-
- // Re-request the new metadata (only after the notification to the factory)
- updateTemporaryMetaData();
- int newCount;
- int[] visibleWindow;
- synchronized(mCache.getTemporaryMetaData()) {
- newCount = mCache.getTemporaryMetaData().count;
- visibleWindow = getVisibleWindow(mVisibleWindowLowerBound,
- mVisibleWindowUpperBound, newCount);
- }
-
- // Pre-load (our best guess of) the views which are currently visible in the AdapterView.
- // This mitigates flashing and flickering of loading views when a widget notifies that
- // its data has changed.
- for (int i: visibleWindow) {
- // Because temporary meta data is only ever modified from this thread (ie.
- // mWorkerThread), it is safe to assume that count is a valid representation.
- if (i < newCount) {
- updateRemoteViews(i, false);
- }
- }
-
- // Propagate the notification back to the base adapter
- mMainQueue.post(new Runnable() {
- @Override
- public void run() {
- synchronized (mCache) {
- mCache.commitTemporaryMetaData();
- }
-
- superNotifyDataSetChanged();
- enqueueDeferredUnbindServiceMessage();
- }
- });
-
- // Reset the notify flagflag
- mNotifyDataSetChangedAfterOnServiceConnected = false;
- }
-
/**
* Returns a sorted array of all integers between lower and upper.
*/
- private int[] getVisibleWindow(int lower, int upper, int count) {
+ private int[] getVisibleWindow(int count) {
+ int lower = mVisibleWindowLowerBound;
+ int upper = mVisibleWindowUpperBound;
// In the case that the window is invalid or uninitialized, return an empty window.
if ((lower == 0 && upper == 0) || lower < 0 || upper < 0) {
return new int[0];
@@ -1250,23 +1175,8 @@
}
public void notifyDataSetChanged() {
- // Dequeue any unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
-
- // If we are not connected, queue up the notifyDataSetChanged to be handled when we do
- // connect
- if (!mServiceConnection.isConnected()) {
- mNotifyDataSetChangedAfterOnServiceConnected = true;
- requestBindService();
- return;
- }
-
- mWorkerQueue.post(new Runnable() {
- @Override
- public void run() {
- onNotifyDataSetChanged();
- }
- });
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ mServiceHandler.sendEmptyMessage(MSG_NOTIFY_DATA_SET_CHANGED);
}
void superNotifyDataSetChanged() {
@@ -1275,35 +1185,38 @@
@Override
public boolean handleMessage(Message msg) {
- boolean result = false;
switch (msg.what) {
- case sUnbindServiceMessageType:
- if (mServiceConnection.isConnected()) {
- mServiceConnection.unbind(mContext, mAppWidgetId, mIntent);
+ case MSG_MAIN_HANDLER_COMMIT_METADATA: {
+ mCache.commitTemporaryMetaData();
+ return true;
}
- result = true;
- break;
- default:
- break;
+ case MSG_MAIN_HANDLER_SUPER_NOTIFY_DATA_SET_CHANGED: {
+ superNotifyDataSetChanged();
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_CONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterConnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_ADAPTER_DISCONNECTED: {
+ if (mCallback != null) {
+ mCallback.onRemoteAdapterDisconnected();
+ }
+ return true;
+ }
+ case MSG_MAIN_HANDLER_REMOTE_VIEWS_LOADED: {
+ mRequestedViews.notifyOnRemoteViewsLoaded(msg.arg1, (RemoteViews) msg.obj);
+ return true;
+ }
}
- return result;
+ return false;
}
- private void enqueueDeferredUnbindServiceMessage() {
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- mMainQueue.sendEmptyMessageDelayed(sUnbindServiceMessageType, sUnbindServiceDelay);
- }
-
- private boolean requestBindService() {
- // Try binding the service (which will start it if it's not already running)
- if (!mServiceConnection.isConnected()) {
- mServiceConnection.bind(mContext, mAppWidgetId, mIntent);
- }
-
- // Remove any existing deferred-unbind messages
- mMainQueue.removeMessages(sUnbindServiceMessageType);
- return mServiceConnection.isConnected();
+ private void requestBindService() {
+ mServiceHandler.removeMessages(MSG_UNBIND_SERVICE);
+ Message.obtain(mServiceHandler, MSG_REQUEST_BIND, mAppWidgetId, 0, mIntent).sendToTarget();
}
private static class HandlerThreadExecutor implements Executor {
@@ -1331,7 +1244,7 @@
remoteViews = views;
float density = context.getResources().getDisplayMetrics().density;
- defaultHeight = Math.round(sDefaultLoadingViewHeight * density);
+ defaultHeight = Math.round(DEFAULT_LOADING_VIEW_HEIGHT * density);
}
public void loadFirstViewHeight(
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ce80552..d9bc51f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9219,36 +9219,6 @@
}
}
- @Override
- public void invalidate() {
- super.invalidate();
-
- if (mEditor != null) {
- mEditor.invalidateMagnifier();
- }
- }
-
- @Override
- public void invalidate(int l, int t, int r, int b) {
- super.invalidate(l, t, r, b);
-
- if (mEditor != null) {
- mEditor.invalidateMagnifier(
- convertViewToScreenCoord(l, true /* isHorizontal */),
- convertViewToScreenCoord(t, false /* isHorizontal */),
- convertViewToScreenCoord(r, true /* isHorizontal */),
- convertViewToScreenCoord(b, false /* isHorizontal */));
- }
- }
-
- float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) {
- final int[] coordinatesOnScreen = new int[2];
- getLocationOnScreen(coordinatesOnScreen);
- return isHorizontal
- ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0]
- : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1];
- }
-
/**
* @return whether or not the cursor is visible (assuming this TextView is editable)
*
diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
index caf35b3..a4da6b9c 100644
--- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
+++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl
@@ -26,6 +26,8 @@
import android.os.Bundle;
import android.os.IBinder;
import android.widget.RemoteViews;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
/** {@hide} */
interface IAppWidgetService {
@@ -62,9 +64,9 @@
void setBindAppWidgetPermission(in String packageName, int userId, in boolean permission);
boolean bindAppWidgetId(in String callingPackage, int appWidgetId,
int providerProfileId, in ComponentName providerComponent, in Bundle options);
- void bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
- in IBinder connection);
- void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent);
+ boolean bindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent,
+ IApplicationThread caller, IBinder token, IServiceConnection connection, int flags);
+
int[] getAppWidgetIds(in ComponentName providerComponent);
boolean isBoundWidgetPackage(String packageName, int userId);
boolean requestPinAppWidget(String packageName, in ComponentName providerComponent,
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
index 46098c5..70b6f96 100644
--- a/core/java/com/android/internal/util/WakeupMessage.java
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -47,17 +47,19 @@
protected final int mCmd, mArg1, mArg2;
@VisibleForTesting
protected final Object mObj;
+ private final Runnable mRunnable;
private boolean mScheduled;
public WakeupMessage(Context context, Handler handler,
String cmdName, int cmd, int arg1, int arg2, Object obj) {
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ mAlarmManager = getAlarmManager(context);
mHandler = handler;
mCmdName = cmdName;
mCmd = cmd;
mArg1 = arg1;
mArg2 = arg2;
mObj = obj;
+ mRunnable = null;
}
public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
@@ -73,6 +75,21 @@
this(context, handler, cmdName, cmd, 0, 0, null);
}
+ public WakeupMessage(Context context, Handler handler, String cmdName, Runnable runnable) {
+ mAlarmManager = getAlarmManager(context);
+ mHandler = handler;
+ mCmdName = cmdName;
+ mCmd = 0;
+ mArg1 = 0;
+ mArg2 = 0;
+ mObj = null;
+ mRunnable = runnable;
+ }
+
+ private static AlarmManager getAlarmManager(Context context) {
+ return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ }
+
/**
* Schedule the message to be delivered at the time in milliseconds of the
* {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()} clock and wakeup
@@ -107,7 +124,12 @@
mScheduled = false;
}
if (stillScheduled) {
- Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ Message msg;
+ if (mRunnable == null) {
+ msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj);
+ } else {
+ msg = Message.obtain(mHandler, mRunnable);
+ }
mHandler.dispatchMessage(msg);
msg.recycle();
}
diff --git a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl b/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
deleted file mode 100644
index 7294124..0000000
--- a/core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget;
-
-import android.os.IBinder;
-
-/** {@hide} */
-oneway interface IRemoteViewsAdapterConnection {
- void onServiceConnected(IBinder service);
- void onServiceDisconnected();
-}
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index 9bc0778..6d54d7b 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -22,9 +22,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.graphics.PointF;
import android.graphics.Rect;
-import android.graphics.RectF;
import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
@@ -38,13 +36,15 @@
import com.android.internal.R;
import com.android.internal.util.Preconditions;
+import java.util.Timer;
+import java.util.TimerTask;
+
/**
* Android magnifier widget. Can be used by any view which is attached to window.
*/
public final class Magnifier {
private static final String LOG_TAG = "magnifier";
- // Use this to specify that a previous configuration value does not exist.
- private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+ private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
// The view for which this magnifier is attached.
private final View mView;
// The window containing the magnifier.
@@ -62,15 +62,10 @@
// The callback of the pixel copy request will be invoked on this Handler when
// the copy is finished.
private final Handler mPixelCopyHandler = Handler.getMain();
-
- private RectF mTmpRectF;
-
- // Variables holding previous states, used for detecting redundant calls and invalidation.
- private Point mPrevStartCoordsOnScreen = new Point(
- INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
- private PointF mPrevCenterCoordsOnScreen = new PointF(
- INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
- private float mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+ // Current magnification scale.
+ private float mScale;
+ // Timer used to schedule the copy task.
+ private Timer mTimer;
/**
* Initializes a magnifier.
@@ -82,6 +77,7 @@
mView = Preconditions.checkNotNull(view);
final Context context = mView.getContext();
final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+ content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
@@ -101,15 +97,15 @@
/**
* Shows the magnifier on the screen.
*
- * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source. The
- * lower end is clamped to 0
- * @param centerYOnScreen vertical coordinate of the center point of the magnifier source. The
- * lower end is clamped to 0
+ * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
+ * to the view. The lower end is clamped to 0
+ * @param yPosInView vertical coordinate of the center point of the magnifier source
+ * relative to the view. The lower end is clamped to 0
* @param scale the scale at which the magnifier zooms on the source content. The
* lower end is clamped to 1 and the higher end to 4
*/
- public void show(@FloatRange(from=0) float centerXOnScreen,
- @FloatRange(from=0) float centerYOnScreen,
+ public void show(@FloatRange(from=0) float xPosInView,
+ @FloatRange(from=0) float yPosInView,
@FloatRange(from=1, to=4) float scale) {
if (scale > 4) {
scale = 4;
@@ -119,27 +115,29 @@
scale = 1;
}
- if (centerXOnScreen < 0) {
- centerXOnScreen = 0;
+ if (xPosInView < 0) {
+ xPosInView = 0;
}
- if (centerYOnScreen < 0) {
- centerYOnScreen = 0;
+ if (yPosInView < 0) {
+ yPosInView = 0;
}
- showInternal(centerXOnScreen, centerYOnScreen, scale, false);
- }
-
- private void showInternal(@FloatRange(from=0) float centerXOnScreen,
- @FloatRange(from=0) float centerYOnScreen,
- @FloatRange(from=1, to=4) float scale,
- boolean forceShow) {
- if (mPrevScale != scale) {
+ if (mScale != scale) {
resizeBitmap(scale);
- mPrevScale = scale;
}
- configureCoordinates(centerXOnScreen, centerYOnScreen);
- maybePerformPixelCopy(scale, forceShow);
+ mScale = scale;
+ configureCoordinates(xPosInView, yPosInView);
+
+ if (mTimer == null) {
+ mTimer = new Timer();
+ mTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ performPixelCopy();
+ }
+ }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
+ }
if (mWindow.isShowing()) {
mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
@@ -148,9 +146,6 @@
mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
mWindowCoords.x, mWindowCoords.y);
}
-
- mPrevCenterCoordsOnScreen.x = centerXOnScreen;
- mPrevCenterCoordsOnScreen.y = centerYOnScreen;
}
/**
@@ -159,36 +154,10 @@
public void dismiss() {
mWindow.dismiss();
- mPrevStartCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
- mPrevStartCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
- mPrevCenterCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
- mPrevCenterCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
- mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
- }
-
- /**
- * Forces the magnifier to update content by taking and showing a new snapshot using the
- * previous coordinates. It does this only if the magnifier is showing and the dirty rectangle
- * intersects the rectangle which holds the content to be magnified.
- *
- * @param dirtyRectOnScreen the rectangle representing the screen bounds of the dirty region
- */
- public void invalidate(RectF dirtyRectOnScreen) {
- if (mWindow.isShowing() && mPrevCenterCoordsOnScreen.x != INEXISTENT_PREVIOUS_CONFIG_VALUE
- && mPrevCenterCoordsOnScreen.y != INEXISTENT_PREVIOUS_CONFIG_VALUE
- && mPrevScale != INEXISTENT_PREVIOUS_CONFIG_VALUE) {
- // Update the current showing RectF.
- mTmpRectF = new RectF(mPrevStartCoordsOnScreen.x,
- mPrevStartCoordsOnScreen.y,
- mPrevStartCoordsOnScreen.x + mBitmap.getWidth(),
- mPrevStartCoordsOnScreen.y + mBitmap.getHeight());
-
- // Update only if we are currently showing content that has been declared as invalid.
- if (RectF.intersects(dirtyRectOnScreen, mTmpRectF)) {
- // Update the contents shown in the magnifier.
- showInternal(mPrevCenterCoordsOnScreen.x, mPrevCenterCoordsOnScreen.y, mPrevScale,
- true /* forceShow */);
- }
+ if (mTimer != null) {
+ mTimer.cancel();
+ mTimer.purge();
+ mTimer = null;
}
}
@@ -213,7 +182,12 @@
getImageView().setImageBitmap(mBitmap);
}
- private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+ private void configureCoordinates(float xPosInView, float yPosInView) {
+ final int[] coordinatesOnScreen = new int[2];
+ mView.getLocationOnScreen(coordinatesOnScreen);
+ final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
+ final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
+
mCenterZoomCoords.x = (int) posXOnScreen;
mCenterZoomCoords.y = (int) posYOnScreen;
@@ -223,7 +197,7 @@
mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
}
- private void maybePerformPixelCopy(final float scale, final boolean forceShow) {
+ private void performPixelCopy() {
final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
@@ -234,13 +208,6 @@
rawStartX = mView.getWidth() - mBitmap.getWidth();
}
- if (!forceShow && rawStartX == mPrevStartCoordsOnScreen.x
- && startY == mPrevStartCoordsOnScreen.y
- && scale == mPrevScale) {
- // Skip, we are already showing the desired content.
- return;
- }
-
final int startX = rawStartX;
final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
@@ -251,11 +218,7 @@
new Rect(startX, startY, startX + mBitmap.getWidth(),
startY + mBitmap.getHeight()),
mBitmap,
- result -> {
- getImageView().invalidate();
- mPrevStartCoordsOnScreen.x = startX;
- mPrevStartCoordsOnScreen.y = startY;
- },
+ result -> getImageView().invalidate(),
mPixelCopyHandler);
} else {
Log.d(LOG_TAG, "Could not perform PixelCopy request");
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index c6801bf..9e6985c 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -56,9 +56,9 @@
if (langs != nullptr) {
ScopedUtfChars str(env, langs);
builder = new NativeFamilyBuilder(
- minikin::FontStyle::registerLanguageList(str.c_str()), variant);
+ minikin::FontStyle::registerLocaleList(str.c_str()), variant);
} else {
- builder = new NativeFamilyBuilder(minikin::FontStyle::registerLanguageList(""), variant);
+ builder = new NativeFamilyBuilder(minikin::FontStyle::registerLocaleList(""), variant);
}
return reinterpret_cast<jlong>(builder);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 93bd16b2..fd62a19f 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -544,7 +544,7 @@
static jint setTextLocales(JNIEnv* env, jobject clazz, jlong objHandle, jstring locales) {
Paint* obj = reinterpret_cast<Paint*>(objHandle);
ScopedUtfChars localesChars(env, locales);
- jint minikinLangListId = minikin::FontStyle::registerLanguageList(localesChars.c_str());
+ jint minikinLangListId = minikin::FontStyle::registerLocaleList(localesChars.c_str());
obj->setMinikinLangListId(minikinLangListId);
return minikinLangListId;
}
diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml
index 181e5e5..d6cd8b4 100644
--- a/core/res/res/layout/magnifier.xml
+++ b/core/res/res/layout/magnifier.xml
@@ -18,10 +18,17 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:background="?android:attr/floatingToolbarPopupBackgroundDrawable">
- <ImageView
- android:id="@+id/magnifier_image"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="wrap_content">
+ <LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/magnifier_inner"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
+ android:elevation="@android:dimen/magnifier_elevation">
+ <ImageView
+ android:id="@+id/magnifier_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
</LinearLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index dec5fd9..571aad9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3128,4 +3128,7 @@
<!-- Class names of device specific services inheriting com.android.server.SystemService. The
classes are instantiated in the order of the array. -->
<string-array translatable="false" name="config_deviceSpecificSystemServices"></string-array>
+
+ <!-- Component name of media projection permission dialog -->
+ <string name="config_mediaProjectionPermissionDialogComponent" translateable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 14069e7..08e2233 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -521,7 +521,7 @@
<dimen name="content_rect_bottom_clip_allowance">20dp</dimen>
<!-- Magnifier dimensions -->
- <dimen name="magnifier_width">200dp</dimen>
+ <dimen name="magnifier_width">164dp</dimen>
<dimen name="magnifier_height">48dp</dimen>
<dimen name="magnifier_elevation">2dp</dimen>
<dimen name="magnifier_offset">42dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7bbd17e..1627a0e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2479,6 +2479,7 @@
<!-- Magnifier -->
<java-symbol type="id" name="magnifier_image" />
+ <java-symbol type="id" name="magnifier_inner" />
<java-symbol type="layout" name="magnifier" />
<java-symbol type="dimen" name="magnifier_width" />
<java-symbol type="dimen" name="magnifier_height" />
@@ -3123,4 +3124,7 @@
<java-symbol type="string" name="shortcut_restore_not_supported" />
<java-symbol type="string" name="shortcut_restore_signature_mismatch" />
<java-symbol type="string" name="shortcut_restore_unknown_issue" />
+
+ <!-- From media projection -->
+ <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
</resources>
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
new file mode 100644
index 0000000..06b860a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsAdapterTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+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.app.IServiceConnection;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.DataSetObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+import com.android.internal.widget.IRemoteViewsFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+/**
+ * Tests for RemoteViewsAdapter.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RemoteViewsAdapterTest {
+
+ @Mock AppWidgetManager mAppWidgetManager;
+ @Mock IServiceConnection mIServiceConnection;
+ @Mock RemoteViewsAdapter.RemoteAdapterConnectionCallback mCallback;
+
+ private Handler mMainHandler;
+ private TestContext mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ when(mAppWidgetManager
+ .bindRemoteViewsService(any(), anyInt(), any(), any(), anyInt())).thenReturn(true);
+ mContext = new TestContext();
+ mMainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Test
+ public void onRemoteAdapterConnected_after_metadata_loaded() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+ assertFalse(adapter.isDataReady());
+
+ assertNotNull(mContext.conn.get());
+
+ ViewsFactory factory = new ViewsFactory(1);
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mMainHandler);
+ verify(mCallback, never()).onRemoteAdapterConnected();
+
+ factory.loadingView.set(createViews("loading"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(1)).onRemoteAdapterConnected();
+
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void viewReplaced_after_mainView_loaded() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // Returned view contains the loading text
+ View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+ ArrayList<View> search = new ArrayList<>();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Send the final remoteViews
+ factory.views[0].set(createViews("updated"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // Existing view got updated with new text
+ search.clear();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertTrue(search.isEmpty());
+ view.findViewsWithText(search, "updated", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void notifyDataSetChanged_deferred() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Reset the loading view so that next refresh is blocked
+ factory.loadingView = new LockedValue<>();
+ factory.mCount = 3;
+ DataSetObserver observer = mock(DataSetObserver.class);
+ getOnUiThread(() -> {
+ adapter.registerDataSetObserver(observer);
+ adapter.notifyDataSetChanged();
+ return null;
+ });
+
+ waitOnHandler(mMainHandler);
+ // Still giving the old values
+ verify(observer, never()).onChanged();
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ factory.loadingView.set(createViews("refreshed"));
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+
+ // When the service returns new data, UI is updated.
+ verify(observer, times(1)).onChanged();
+ assertEquals((Integer) 3, getOnUiThread(adapter::getCount));
+
+ // Service is unbound
+ assertTrue(isUnboundOrScheduled());
+ }
+
+ @Test
+ public void serviceDisconnected_before_getView() throws Throwable {
+ RemoteViewsAdapter adapter = getOnUiThread(
+ () -> new RemoteViewsAdapter(mContext, new DistinctIntent(), mCallback, false));
+
+ ViewsFactory factory = new ViewsFactory(1);
+ factory.loadingView.set(createViews("loading"));
+ mContext.sendConnect(factory);
+
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(1)).onRemoteAdapterConnected();
+ assertEquals((Integer) 1, getOnUiThread(adapter::getCount));
+
+ // Unbind the service
+ ServiceConnection conn = mContext.conn.get();
+ getOnHandler(mContext.handler.get(), () -> {
+ conn.onServiceDisconnected(null);
+ return null;
+ });
+
+ // Returned view contains the loading text
+ View view = getOnUiThread(() -> adapter.getView(0, null, new FrameLayout(mContext)));
+ ArrayList<View> search = new ArrayList<>();
+ view.findViewsWithText(search, "loading", View.FIND_VIEWS_WITH_TEXT);
+ assertEquals(1, search.size());
+
+ // Unbind is not scheduled
+ assertFalse(isUnboundOrScheduled());
+
+ mContext.sendConnect(factory);
+ waitOnHandler(mContext.handler.get());
+ waitOnHandler(mMainHandler);
+ verify(mCallback, times(2)).onRemoteAdapterConnected();
+ }
+
+ private RemoteViews createViews(String text) {
+ RemoteViews views = new RemoteViews(mContext.getPackageName(), R.layout.remote_views_text);
+ views.setTextViewText(R.id.text, text);
+ return views;
+ }
+
+ private <T> T getOnUiThread(Supplier<T> supplier) throws Throwable {
+ return getOnHandler(mMainHandler, supplier);
+ }
+
+ private boolean isUnboundOrScheduled() throws Throwable {
+ Handler handler = mContext.handler.get();
+ return getOnHandler(handler, () -> mContext.boundCount == 0
+ || handler.hasMessages(RemoteViewsAdapter.MSG_UNBIND_SERVICE));
+ }
+
+ private static <T> T getOnHandler(Handler handler, Supplier<T> supplier) throws Throwable {
+ LockedValue<T> result = new LockedValue<>();
+ handler.post(() -> result.set(supplier.get()));
+ return result.get();
+ }
+
+ private class TestContext extends ContextWrapper {
+
+ public final LockedValue<ServiceConnection> conn = new LockedValue<>();
+ public final LockedValue<Handler> handler = new LockedValue<>();
+ public int boundCount;
+
+ TestContext() {
+ super(InstrumentationRegistry.getContext());
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ boundCount--;
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ if (Context.APPWIDGET_SERVICE.equals(name)) {
+ return mAppWidgetManager;
+ }
+ return super.getSystemService(name);
+ }
+
+ @Override
+ public IServiceConnection getServiceDispatcher(
+ ServiceConnection conn, Handler handler, int flags) {
+ this.conn.set(conn);
+ this.handler.set(handler);
+ boundCount++;
+ return mIServiceConnection;
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return this;
+ }
+
+ public void sendConnect(ViewsFactory factory) throws Exception {
+ ServiceConnection connection = conn.get();
+ handler.get().post(() -> connection.onServiceConnected(null, factory.asBinder()));
+ }
+ }
+
+ private static void waitOnHandler(Handler handler) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ handler.post(() -> latch.countDown());
+ latch.await(20, TimeUnit.SECONDS);
+ }
+
+ private static class ViewsFactory extends IRemoteViewsFactory.Stub {
+
+ public LockedValue<RemoteViews> loadingView = new LockedValue<>();
+ public LockedValue<RemoteViews>[] views;
+
+ private int mCount;
+
+ ViewsFactory(int count) {
+ mCount = count;
+ views = new LockedValue[count];
+ for (int i = 0; i < count; i++) {
+ views[i] = new LockedValue<>();
+ }
+ }
+
+ @Override
+ public void onDataSetChanged() {}
+
+ @Override
+ public void onDataSetChangedAsync() { }
+
+ @Override
+ public void onDestroy(Intent intent) { }
+
+ @Override
+ public int getCount() throws RemoteException {
+ return mCount;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) throws RemoteException {
+ try {
+ return views[position].get();
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public RemoteViews getLoadingView() throws RemoteException {
+ try {
+ return loadingView.get();
+ } catch (Exception e) {
+ throw new RemoteException(e.getMessage());
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return false;
+ }
+
+ @Override
+ public boolean isCreated() {
+ return false;
+ }
+ }
+
+ private static class DistinctIntent extends Intent {
+
+ @Override
+ public boolean filterEquals(Intent other) {
+ return false;
+ }
+ }
+
+ private static class LockedValue<T> {
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private T mValue;
+
+ public void set(T value) {
+ mValue = value;
+ mLatch.countDown();
+ }
+
+ public T get() throws Exception {
+ mLatch.await(10, TimeUnit.SECONDS);
+ return mValue;
+ }
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
index 7935880..734ebef 100644
--- a/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/WakeupMessageTest.java
@@ -47,6 +47,7 @@
private static final int TEST_ARG2 = 182;
private static final Object TEST_OBJ = "hello";
+ @Mock Context mContext;
@Mock AlarmManager mAlarmManager;
WakeupMessage mMessage;
// Make a spy so that we can verify calls to it
@@ -86,13 +87,12 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- Context context = mock(Context.class);
- when(context.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
+ when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
// capture the listener for each AlarmManager.setExact call
doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), any(String.class),
mListenerCaptor.capture(), any(Handler.class));
- mMessage = new WakeupMessage(context, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
+ mMessage = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, TEST_CMD, TEST_ARG1,
TEST_ARG2, TEST_OBJ);
}
@@ -168,4 +168,19 @@
verifyMessageDispatchedOnce();
}
+ /**
+ * Verify that a Runnable is scheduled and dispatched.
+ */
+ @Test
+ public void scheduleRunnable() {
+ final long when = 1011;
+ final Runnable runnable = mock(Runnable.class);
+ WakeupMessage dut = new WakeupMessage(mContext, mHandler, TEST_CMD_NAME, runnable);
+ dut.schedule(when);
+ verify(mAlarmManager).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), eq(when),
+ eq(TEST_CMD_NAME), any(AlarmManager.OnAlarmListener.class), eq(mHandler));
+ mListenerCaptor.getValue().onAlarm();
+ verify(runnable, times(1)).run();
+ }
+
}
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
index cbfd4b3..d72059a 100644
--- a/libs/services/Android.mk
+++ b/libs/services/Android.mk
@@ -21,7 +21,8 @@
LOCAL_MODULE := libservices
LOCAL_SRC_FILES := \
../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
- src/os/DropBoxManager.cpp
+ src/os/DropBoxManager.cpp \
+ src/os/StatsLogEventWrapper.cpp
LOCAL_AIDL_INCLUDES := \
$(LOCAL_PATH)/../../core/java
diff --git a/libs/services/include/android/os/StatsLogEventWrapper.h b/libs/services/include/android/os/StatsLogEventWrapper.h
new file mode 100644
index 0000000..255619c
--- /dev/null
+++ b/libs/services/include/android/os/StatsLogEventWrapper.h
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+#ifndef STATS_LOG_EVENT_WRAPPER_H
+#define STATS_LOG_EVENT_WRAPPER_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Only used to send data from Android OS to statsd.
+class StatsLogEventWrapper : public android::Parcelable {
+ public:
+ StatsLogEventWrapper();
+
+ StatsLogEventWrapper(StatsLogEventWrapper&& in) = default;
+
+ android::status_t writeToParcel(android::Parcel* out) const;
+
+ android::status_t readFromParcel(const android::Parcel* in);
+
+ // These are public for ease of conversion.
+ std::vector<uint8_t> bytes;
+};
+} // Namespace os
+} // Namespace android
+
+
+#endif // STATS_LOG_EVENT_WRAPPER_H
+
diff --git a/libs/services/src/os/StatsLogEventWrapper.cpp b/libs/services/src/os/StatsLogEventWrapper.cpp
new file mode 100644
index 0000000..8b3aa9a
--- /dev/null
+++ b/libs/services/src/os/StatsLogEventWrapper.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+#include <android/os/StatsLogEventWrapper.h>
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+#include <vector>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsLogEventWrapper::StatsLogEventWrapper(){};
+
+status_t StatsLogEventWrapper::writeToParcel(Parcel* out) const {
+ out->writeByteVector(bytes);
+ return ::android::NO_ERROR;
+};
+
+status_t StatsLogEventWrapper::readFromParcel(const Parcel* in) {
+ in->readByteVector(&bytes);
+ return ::android::NO_ERROR;
+};
+
+} // Namespace os
+} // Namespace android
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9f2c08e..aa0d0cc 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -20,8 +20,10 @@
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.app.Activity;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.media.projection.IMediaProjection;
import android.os.Handler;
import android.os.IBinder;
@@ -71,8 +73,11 @@
*/
public Intent createScreenCaptureIntent() {
Intent i = new Intent();
- i.setClassName("com.android.systemui",
- "com.android.systemui.media.MediaProjectionPermissionActivity");
+ final ComponentName mediaProjectionPermissionDialogComponent =
+ ComponentName.unflattenFromString(mContext.getResources().getString(
+ com.android.internal.R.string
+ .config_mediaProjectionPermissionDialogComponent));
+ i.setComponent(mediaProjectionPermissionDialogComponent);
return i;
}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 51afada..76e7782 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -21,9 +21,12 @@
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManagerInternal;
@@ -99,7 +102,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.internal.widget.IRemoteViewsAdapterConnection;
import com.android.internal.widget.IRemoteViewsFactory;
import com.android.server.LocalServices;
import com.android.server.WidgetBackupProvider;
@@ -191,10 +193,6 @@
}
};
- // Manages active connections to RemoteViewsServices.
- private final HashMap<Pair<Integer, FilterComparison>, ServiceConnection>
- mBoundRemoteViewsServices = new HashMap<>();
-
// Manages persistent references to RemoteViewsServices from different App Widgets.
private final HashMap<Pair<Integer, FilterComparison>, HashSet<Integer>>
mRemoteViewsServicesAppWidgets = new HashMap<>();
@@ -1209,17 +1207,14 @@
}
@Override
- public void bindRemoteViewsService(String callingPackage, int appWidgetId,
- Intent intent, IBinder callbacks) {
+ public boolean bindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent,
+ IApplicationThread caller, IBinder activtiyToken, IServiceConnection connection,
+ int flags) {
final int userId = UserHandle.getCallingUserId();
-
if (DEBUG) {
Slog.i(TAG, "bindRemoteViewsService() " + userId);
}
- // Make sure the package runs under the caller uid.
- mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
synchronized (mLock) {
ensureGroupStateLoadedLocked(userId);
@@ -1254,76 +1249,35 @@
mSecurityPolicy.enforceServiceExistsAndRequiresBindRemoteViewsPermission(
componentName, widget.provider.getUserId());
- // Good to go - the service pakcage is correct, it exists for the correct
+ // Good to go - the service package is correct, it exists for the correct
// user, and requires the bind permission.
- // If there is already a connection made for this service intent, then
- // disconnect from that first. (This does not allow multiple connections
- // to the same service under the same key).
- ServiceConnectionProxy connection = null;
- FilterComparison fc = new FilterComparison(intent);
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId, fc);
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ // Ask ActivityManager to bind it. Notice that we are binding the service with the
+ // caller app instead of DevicePolicyManagerService.
+ if(ActivityManager.getService().bindService(
+ caller, activtiyToken, intent,
+ intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+ connection, flags, mContext.getOpPackageName(),
+ widget.provider.getUserId()) != 0) {
- if (mBoundRemoteViewsServices.containsKey(key)) {
- connection = (ServiceConnectionProxy) mBoundRemoteViewsServices.get(key);
- connection.disconnect();
- unbindService(connection);
- mBoundRemoteViewsServices.remove(key);
- }
-
- // Bind to the RemoteViewsService (which will trigger a callback to the
- // RemoteViewsAdapter.onServiceConnected())
- connection = new ServiceConnectionProxy(callbacks);
- bindService(intent, connection, widget.provider.info.getProfile());
- mBoundRemoteViewsServices.put(key, connection);
-
- // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
- // can determine when we can call back to the RemoteViewsService later to
- // destroy associated factories.
- Pair<Integer, FilterComparison> serviceId = Pair.create(widget.provider.id.uid, fc);
- incrementAppWidgetServiceRefCount(appWidgetId, serviceId);
- }
- }
-
- @Override
- public void unbindRemoteViewsService(String callingPackage, int appWidgetId, Intent intent) {
- final int userId = UserHandle.getCallingUserId();
-
- if (DEBUG) {
- Slog.i(TAG, "unbindRemoteViewsService() " + userId);
- }
-
- // Make sure the package runs under the caller uid.
- mSecurityPolicy.enforceCallFromPackage(callingPackage);
-
- synchronized (mLock) {
- ensureGroupStateLoadedLocked(userId);
-
- // Unbind from the RemoteViewsService (which will trigger a callback to the bound
- // RemoteViewsAdapter)
- Pair<Integer, FilterComparison> key = Pair.create(appWidgetId,
- new FilterComparison(intent));
- if (mBoundRemoteViewsServices.containsKey(key)) {
- // We don't need to use the appWidgetId until after we are sure there is something
- // to unbind. Note that this may mask certain issues with apps calling unbind()
- // more than necessary.
-
- // NOTE: The lookup is enforcing security across users by making
- // sure the caller can only access widgets it hosts or provides.
- Widget widget = lookupWidgetLocked(appWidgetId,
- Binder.getCallingUid(), callingPackage);
-
- if (widget == null) {
- throw new IllegalArgumentException("Bad widget id " + appWidgetId);
+ // Add it to the mapping of RemoteViewsService to appWidgetIds so that we
+ // can determine when we can call back to the RemoteViewsService later to
+ // destroy associated factories.
+ incrementAppWidgetServiceRefCount(appWidgetId,
+ Pair.create(widget.provider.id.uid, new FilterComparison(intent)));
+ return true;
}
-
- ServiceConnectionProxy connection = (ServiceConnectionProxy)
- mBoundRemoteViewsServices.get(key);
- connection.disconnect();
- mContext.unbindService(connection);
- mBoundRemoteViewsServices.remove(key);
+ } catch (RemoteException ex) {
+ // Same process, should not happen.
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
}
}
+
+ // Failed to bind.
+ return false;
}
@Override
@@ -1754,7 +1708,9 @@
private void deleteAppWidgetLocked(Widget widget) {
// We first unbind all services that are bound to this id
- unbindAppWidgetRemoteViewsServicesLocked(widget);
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(widget);
Host host = widget.host;
host.widgets.remove(widget);
@@ -1796,28 +1752,6 @@
}
}
- // Unbinds from a RemoteViewsService when we delete an app widget
- private void unbindAppWidgetRemoteViewsServicesLocked(Widget widget) {
- int appWidgetId = widget.appWidgetId;
- // Unbind all connections to Services bound to this AppWidgetId
- Iterator<Pair<Integer, Intent.FilterComparison>> it = mBoundRemoteViewsServices.keySet()
- .iterator();
- while (it.hasNext()) {
- final Pair<Integer, Intent.FilterComparison> key = it.next();
- if (key.first == appWidgetId) {
- final ServiceConnectionProxy conn = (ServiceConnectionProxy)
- mBoundRemoteViewsServices.get(key);
- conn.disconnect();
- mContext.unbindService(conn);
- it.remove();
- }
- }
-
- // Check if we need to destroy any services (if no other app widgets are
- // referencing the same service)
- decrementAppWidgetServiceRefCount(widget);
- }
-
// Destroys the cached factory on the RemoteViewsService's side related to the specified intent
private void destroyRemoteViewsService(final Intent intent, Widget widget) {
final ServiceConnection conn = new ServiceConnection() {
@@ -1853,7 +1787,7 @@
// Adds to the ref-count for a given RemoteViewsService intent
private void incrementAppWidgetServiceRefCount(int appWidgetId,
Pair<Integer, FilterComparison> serviceId) {
- HashSet<Integer> appWidgetIds = null;
+ final HashSet<Integer> appWidgetIds;
if (mRemoteViewsServicesAppWidgets.containsKey(serviceId)) {
appWidgetIds = mRemoteViewsServicesAppWidgets.get(serviceId);
} else {
@@ -4055,40 +3989,6 @@
}
}
- /**
- * Acts as a proxy between the ServiceConnection and the RemoteViewsAdapterConnection. This
- * needs to be a static inner class since a reference to the ServiceConnection is held globally
- * and may lead us to leak AppWidgetService instances (if there were more than one).
- */
- private static final class ServiceConnectionProxy implements ServiceConnection {
- private final IRemoteViewsAdapterConnection mConnectionCb;
-
- ServiceConnectionProxy(IBinder connectionCb) {
- mConnectionCb = IRemoteViewsAdapterConnection.Stub
- .asInterface(connectionCb);
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- try {
- mConnectionCb.onServiceConnected(service);
- } catch (RemoteException re) {
- Slog.e(TAG, "Error passing service interface", re);
- }
- }
-
- public void onServiceDisconnected(ComponentName name) {
- disconnect();
- }
-
- public void disconnect() {
- try {
- mConnectionCb.onServiceDisconnected();
- } catch (RemoteException re) {
- Slog.e(TAG, "Error clearing service interface", re);
- }
- }
- }
-
private class LoadedWidgetState {
final Widget widget;
final int hostTag;
@@ -4642,7 +4542,9 @@
// reconstructed due to the restore
host.widgets.remove(widget);
provider.widgets.remove(widget);
- unbindAppWidgetRemoteViewsServicesLocked(widget);
+ // Check if we need to destroy any services (if no other app widgets are
+ // referencing the same service)
+ decrementAppWidgetServiceRefCount(widget);
removeWidgetLocked(widget);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 59fc34d..b720f74 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -846,12 +846,9 @@
* when necessary.
*/
public void logContextCommittedLocked() {
- if (mResponses == null) {
- if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): skipped (no responses)");
- return;
- }
+ final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
+ if (lastResponse == null) return;
- final FillResponse lastResponse = mResponses.valueAt(mResponses.size() -1);
final int flags = lastResponse.getFlags();
if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
@@ -1599,11 +1596,10 @@
* Checks whether a view should be ignored.
*/
private boolean isIgnoredLocked(AutofillId id) {
- if (mResponses == null || mResponses.size() == 0) {
- return false;
- }
// Always check the latest response only
- final FillResponse response = mResponses.valueAt(mResponses.size() - 1);
+ final FillResponse response = getLastResponseLocked(null);
+ if (response == null) return false;
+
return ArrayUtils.contains(response.getIgnoredIds(), id);
}
@@ -1680,13 +1676,10 @@
}
private void updateTrackedIdsLocked() {
- if (mResponses == null || mResponses.size() == 0) {
- return;
- }
-
// Only track the views of the last response as only those are reported back to the
// service, see #showSaveLocked
- final FillResponse response = mResponses.valueAt(getLastResponseIndexLocked());
+ final FillResponse response = getLastResponseLocked(null);
+ if (response == null) return;
ArraySet<AutofillId> trackedViews = null;
boolean saveOnAllViewsInvisible = false;
diff --git a/services/core/java/com/android/server/am/LockTaskNotify.java b/services/core/java/com/android/server/am/LockTaskNotify.java
index 5d6e9b5..1dcb0ad 100644
--- a/services/core/java/com/android/server/am/LockTaskNotify.java
+++ b/services/core/java/com/android/server/am/LockTaskNotify.java
@@ -17,8 +17,6 @@
package com.android.server.am;
import android.content.Context;
-import android.os.Handler;
-import android.os.Message;
import android.os.SystemClock;
import android.util.Slog;
import android.view.WindowManager;
@@ -27,21 +25,19 @@
import com.android.internal.R;
/**
- * Helper to manage showing/hiding a image to notify them that they are entering
- * or exiting screen pinning mode.
+ * Helper to manage showing/hiding a image to notify them that they are entering or exiting screen
+ * pinning mode. All exposed methods should be called from a handler thread.
*/
public class LockTaskNotify {
private static final String TAG = "LockTaskNotify";
private static final long SHOW_TOAST_MINIMUM_INTERVAL = 1000;
private final Context mContext;
- private final H mHandler;
private Toast mLastToast;
private long mLastShowToastTime;
public LockTaskNotify(Context context) {
mContext = context;
- mHandler = new H();
}
/** Show "Screen pinned" toast. */
@@ -56,10 +52,6 @@
/** Show a toast that describes the gesture the user should use to escape pinned mode. */
void showEscapeToast() {
- mHandler.obtainMessage(H.SHOW_ESCAPE_TOAST).sendToTarget();
- }
-
- private void handleShowEscapeToast() {
long showToastTime = SystemClock.elapsedRealtime();
if ((showToastTime - mLastShowToastTime) < SHOW_TOAST_MINIMUM_INTERVAL) {
Slog.i(TAG, "Ignore toast since it is requested in very short interval.");
@@ -79,17 +71,4 @@
toast.show();
return toast;
}
-
- private final class H extends Handler {
- private static final int SHOW_ESCAPE_TOAST = 3;
-
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case SHOW_ESCAPE_TOAST:
- handleShowEscapeToast();
- break;
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 515fa39..755c5f0 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -34,8 +34,6 @@
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioAttributes;
-import android.nfc.INfcAdapter;
-import android.nfc.NfcAdapter;
import android.os.FileUtils;
import android.os.Handler;
import android.os.PowerManager;
@@ -124,7 +122,6 @@
private static String METRIC_RADIOS = "shutdown_radios";
private static String METRIC_BT = "shutdown_bt";
private static String METRIC_RADIO = "shutdown_radio";
- private static String METRIC_NFC = "shutdown_nfc";
private static String METRIC_SM = "shutdown_storage_manager";
private final Object mActionDoneSync = new Object();
@@ -629,29 +626,14 @@
Thread t = new Thread() {
public void run() {
TimingsTraceLog shutdownTimingsTraceLog = newTimingsLog();
- boolean nfcOff;
boolean bluetoothReadyForShutdown;
boolean radioOff;
- final INfcAdapter nfc =
- INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
final ITelephony phone =
ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
final IBluetoothManager bluetooth =
IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
- try {
- nfcOff = nfc == null ||
- nfc.getState() == NfcAdapter.STATE_OFF;
- if (!nfcOff) {
- Log.w(TAG, "Turning off NFC...");
- metricStarted(METRIC_NFC);
- nfc.disable(false); // Don't persist new state
- }
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during NFC shutdown", ex);
- nfcOff = true;
- }
try {
bluetoothReadyForShutdown = bluetooth == null ||
@@ -678,7 +660,7 @@
radioOff = true;
}
- Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
+ Log.i(TAG, "Waiting for Bluetooth and Radio...");
long delay = endTime - SystemClock.elapsedRealtime();
while (delay > 0) {
@@ -722,23 +704,9 @@
.logDuration("ShutdownRadio", TRON_METRICS.get(METRIC_RADIO));
}
}
- if (!nfcOff) {
- try {
- nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
- } catch (RemoteException ex) {
- Log.e(TAG, "RemoteException during NFC shutdown", ex);
- nfcOff = true;
- }
- if (nfcOff) {
- Log.i(TAG, "NFC turned off.");
- metricEnded(METRIC_NFC);
- shutdownTimingsTraceLog
- .logDuration("ShutdownNfc", TRON_METRICS.get(METRIC_NFC));
- }
- }
- if (radioOff && bluetoothReadyForShutdown && nfcOff) {
- Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
+ if (radioOff && bluetoothReadyForShutdown) {
+ Log.i(TAG, "Radio and Bluetooth shutdown complete.");
done[0] = true;
break;
}
@@ -755,7 +723,7 @@
} catch (InterruptedException ex) {
}
if (!done[0]) {
- Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
+ Log.w(TAG, "Timed out waiting for Radio and Bluetooth shutdown.");
}
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index ca3dd05..22d2bcf 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -33,12 +33,14 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StatsLogEventWrapper;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Slog;
import java.util.ArrayList;
import java.util.List;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
@@ -49,6 +51,7 @@
/**
* Helper service for statsd (the native stats management service in cmds/statsd/).
* Used for registering and receiving alarms on behalf of statsd.
+ *
* @hide
*/
public class StatsCompanionService extends IStatsCompanionService.Stub {
@@ -90,7 +93,7 @@
// Needed since the new user basically has a version of every app.
informAllUidsLocked(context);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+ Slog.e(TAG, "Failed to inform statsd latest update of all apps", e);
forgetEverything();
}
}
@@ -99,9 +102,9 @@
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
}
- private final static int[] toIntArray(List<Integer> list){
+ private final static int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
- for(int i = 0;i < ret.length;i++) {
+ for (int i = 0; i < ret.length; i++) {
ret[i] = list.get(i);
}
return ret;
@@ -113,7 +116,7 @@
PackageManager pm = context.getPackageManager();
final List<UserInfo> users = um.getUsers(true);
if (DEBUG) {
- Slog.w(TAG, "Iterating over "+users.size() + " profiles.");
+ Slog.w(TAG, "Iterating over " + users.size() + " profiles.");
}
List<Integer> uids = new ArrayList();
@@ -122,23 +125,23 @@
// Add in all the apps for every user/profile.
for (UserInfo profile : users) {
- List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
- for (int j = 0; j < pi.size(); j++) {
- if (pi.get(j).applicationInfo != null) {
- uids.add(pi.get(j).applicationInfo.uid);
- versions.add(pi.get(j).versionCode);
- apps.add(pi.get(j).packageName);
- }
- }
+ List<PackageInfo> pi = pm.getInstalledPackagesAsUser(0, profile.id);
+ for (int j = 0; j < pi.size(); j++) {
+ if (pi.get(j).applicationInfo != null) {
+ uids.add(pi.get(j).applicationInfo.uid);
+ versions.add(pi.get(j).versionCode);
+ apps.add(pi.get(j).packageName);
+ }
+ }
}
sStatsd.informAllUidData(toIntArray(uids), toIntArray(versions), apps.toArray(new
- String[apps.size()]));
+ String[apps.size()]));
if (DEBUG) {
- Slog.w(TAG, "Sent data for "+uids.size() +" apps");
+ Slog.w(TAG, "Sent data for " + uids.size() + " apps");
}
}
- public final static class AppUpdateReceiver extends BroadcastReceiver {
+ public final static class AppUpdateReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Slog.i(TAG, "StatsCompanionService noticed an app was updated.");
@@ -147,7 +150,7 @@
* waste, we ignore the REMOVE and ADD broadcasts that contain the replacing flag.
*/
if (!intent.getAction().equals(Intent.ACTION_PACKAGE_REPLACED) &&
- intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
return; // Keep only replacing or normal add and remove.
}
synchronized (sStatsdLock) {
@@ -180,9 +183,9 @@
}
}
}
- };
+ }
- public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
+ public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -200,7 +203,7 @@
}
// AlarmManager releases its own wakelock here.
}
- };
+ }
public final static class PollingAlarmReceiver extends BroadcastReceiver {
@Override
@@ -220,7 +223,7 @@
}
// AlarmManager releases its own wakelock here.
}
- };
+ }
@Override // Binder call
public void setAnomalyAlarm(long timestampMs) {
@@ -286,33 +289,34 @@
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
@Override // Binder call
- public String pullData(int pullCode) {
+ public StatsLogEventWrapper[] pullData(int pullCode) {
enforceCallingPermission();
- if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+ if (DEBUG) {
+ Slog.d(TAG, "Pulling " + pullCode);
+ }
- StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+ List<StatsLogEventWrapper> ret = new ArrayList<>();
switch (pullCode) {
- case PULL_CODE_KERNEL_WAKELOCKS:
+ case PULL_CODE_KERNEL_WAKELOCKS: {
final KernelWakelockStats wakelockStats =
mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
-
for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
String name = ent.getKey();
KernelWakelockStats.Entry kws = ent.getValue();
- s.append("Wakelock ")
- .append(name)
- .append(", time=")
- .append(kws.mTotalTime)
- .append(", count=")
- .append(kws.mCount)
- .append('\n');
+ StatsLogEventWrapper e = new StatsLogEventWrapper(101, 4);
+ e.writeInt(kws.mCount);
+ e.writeInt(kws.mVersion);
+ e.writeLong(kws.mTotalTime);
+ e.writeString(name);
+ ret.add(e);
}
break;
+ }
default:
Slog.w(TAG, "No such pollable data as " + pullCode);
return null;
}
- return s.toString();
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
}
@Override // Binder call
@@ -331,7 +335,9 @@
// Lifecycle and related code
- /** Fetches the statsd IBinder service */
+ /**
+ * Fetches the statsd IBinder service
+ */
private static IStatsManager fetchStatsdService() {
return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
}
@@ -363,13 +369,17 @@
}
}
- /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+ /**
+ * Now that the android system is ready, StatsCompanion is ready too, so inform statsd.
+ */
private void systemReady() {
if (DEBUG) Slog.d(TAG, "Learned that systemReady");
sayHiToStatsd();
}
- /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+ /**
+ * Tells statsd that statscompanion is ready. If the binder call returns, link to statsd.
+ */
private void sayHiToStatsd() {
synchronized (sStatsdLock) {
if (sStatsd != null) {
@@ -398,14 +408,14 @@
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mAppUpdateReceiver, UserHandle.ALL, filter, null,
- null);
+ null);
// Setup receiver for user initialize (which happens once for a new user) and
// if a user is removed.
filter = new IntentFilter(Intent.ACTION_USER_INITIALIZE);
filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
- filter, null, null);
+ filter, null, null);
// Pull the latest state of UID->app name, version mapping when statsd starts.
informAllUidsLocked(mContext);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c59f44e..80f6a4b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -175,6 +175,7 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
@@ -1682,6 +1683,10 @@
return getCallingUid() == Process.myUid();
}
+ void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+ Binder.withCleanCallingIdentity(action);
+ }
+
final int userHandleGetCallingUserId() {
return UserHandle.getUserId(binderGetCallingUid());
}
@@ -9023,6 +9028,31 @@
}
@Override
+ public boolean setTime(ComponentName who, long millis) {
+ Preconditions.checkNotNull(who, "ComponentName is null in setTime");
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ // Don't allow set time when auto time is on.
+ if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
+ return false;
+ }
+ mInjector.binderWithCleanCallingIdentity(() -> mInjector.getAlarmManager().setTime(millis));
+ return true;
+ }
+
+ @Override
+ public boolean setTimeZone(ComponentName who, String timeZone) {
+ Preconditions.checkNotNull(who, "ComponentName is null in setTimeZone");
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ // Don't allow set timezone when auto timezone is on.
+ if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
+ return false;
+ }
+ mInjector.binderWithCleanCallingIdentity(() ->
+ mInjector.getAlarmManager().setTimeZone(timeZone));
+ return true;
+ }
+
+ @Override
public void setSecureSetting(ComponentName who, String setting, String value) {
Preconditions.checkNotNull(who, "ComponentName is null");
int callingUserId = mInjector.userHandleGetCallingUserId();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 5471715..ae4b569 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -34,11 +35,13 @@
import android.os.UserManager;
import android.os.UserManagerInternal;
import android.security.KeyChain;
+import android.support.annotation.NonNull;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Pair;
import android.view.IWindowManager;
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.widget.LockPatternUtils;
import java.io.File;
@@ -194,6 +197,9 @@
}
@Override
+ AlarmManager getAlarmManager() {return services.alarmManager;}
+
+ @Override
LockPatternUtils newLockPatternUtils() {
return services.lockPatternUtils;
}
@@ -234,6 +240,11 @@
}
@Override
+ void binderWithCleanCallingIdentity(@NonNull ThrowingRunnable action) {
+ context.binder.withCleanCallingIdentity(action);
+ }
+
+ @Override
int binderGetCallingUid() {
return context.binder.getCallingUid();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a8bf8f1..e1e9cf5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3090,6 +3090,47 @@
assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
}
+ public void testSetTime() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ dpm.setTime(admin1, 0);
+ verify(getServices().alarmManager).setTime(0);
+ }
+
+ public void testSetTimeFailWithPO() throws Exception {
+ setupProfileOwner();
+ assertExpectException(SecurityException.class, null, () -> dpm.setTime(admin1, 0));
+ }
+
+ public void testSetTimeWithAutoTimeOn() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME, 0))
+ .thenReturn(1);
+ assertFalse(dpm.setTime(admin1, 0));
+ }
+
+ public void testSetTimeZone() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ dpm.setTimeZone(admin1, "Asia/Shanghai");
+ verify(getServices().alarmManager).setTimeZone("Asia/Shanghai");
+ }
+
+ public void testSetTimeZoneFailWithPO() throws Exception {
+ setupProfileOwner();
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setTimeZone(admin1, "Asia/Shanghai"));
+ }
+
+ public void testSetTimeZoneWithAutoTimeZoneOn() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(getServices().settings.settingsGlobalGetInt(Settings.Global.AUTO_TIME_ZONE, 0))
+ .thenReturn(1);
+ assertFalse(dpm.setTimeZone(admin1, "Asia/Shanghai"));
+ }
+
public void testGetLastBugReportRequestTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 9702118..7e11e87 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -30,8 +30,12 @@
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManagerInternal;
+import android.support.annotation.NonNull;
import android.test.mock.MockContext;
import android.util.ArrayMap;
+import android.util.ExceptionUtils;
+
+import com.android.internal.util.FunctionalUtils;
import org.junit.Assert;
@@ -95,6 +99,21 @@
callingPid = (int) token;
}
+ public void withCleanCallingIdentity(@NonNull FunctionalUtils.ThrowingRunnable action) {
+ long callingIdentity = clearCallingIdentity();
+ Throwable throwableToPropagate = null;
+ try {
+ action.run();
+ } catch (Throwable throwable) {
+ throwableToPropagate = throwable;
+ } finally {
+ restoreCallingIdentity(callingIdentity);
+ if (throwableToPropagate != null) {
+ throw ExceptionUtils.propagate(throwableToPropagate);
+ }
+ }
+ }
+
public int getCallingUid() {
return callingUid;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 56a3fb0..b8e8946 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -89,6 +89,9 @@
public void setUp() throws Exception {
if (!sOneTimeSetupDone) {
sOneTimeSetupDone = true;
+
+ // Allows to mock package local classes and methods
+ System.setProperty("dexmaker.share_classloader", "true");
MockitoAnnotations.initMocks(this);
}
diff --git a/tools/locked_region_code_injection/Android.mk b/tools/locked_region_code_injection/Android.mk
index bb5f4d6..3f65151 100644
--- a/tools/locked_region_code_injection/Android.mk
+++ b/tools/locked_region_code_injection/Android.mk
@@ -6,10 +6,10 @@
LOCAL_MODULE := lockedregioncodeinjection
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- asm-6.0_BETA \
- asm-commons-6.0_BETA \
- asm-tree-6.0_BETA \
- asm-analysis-6.0_BETA \
+ asm-6.0 \
+ asm-commons-6.0 \
+ asm-tree-6.0 \
+ asm-analysis-6.0 \
guava-21.0 \
include $(BUILD_HOST_JAVA_LIBRARY)