Merge "Add method to show/hide preference in base controller"
diff --git a/api/current.txt b/api/current.txt
index 0eb4715..12a3f07 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -31015,6 +31015,7 @@
}
public final class Debug {
+ method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
method public static deprecated void changeDebugPort(int);
method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -44558,6 +44559,7 @@
field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
field public static final int STATE_OFF = 1; // 0x1
field public static final int STATE_ON = 2; // 0x2
+ field public static final int STATE_ON_SUSPEND = 6; // 0x6
field public static final int STATE_UNKNOWN = 0; // 0x0
field public static final int STATE_VR = 5; // 0x5
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 1747f23..0b34782 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -33756,6 +33756,7 @@
}
public final class Debug {
+ method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
method public static deprecated void changeDebugPort(int);
method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -48293,6 +48294,7 @@
field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
field public static final int STATE_OFF = 1; // 0x1
field public static final int STATE_ON = 2; // 0x2
+ field public static final int STATE_ON_SUSPEND = 6; // 0x6
field public static final int STATE_UNKNOWN = 0; // 0x0
field public static final int STATE_VR = 5; // 0x5
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 5664391..4eba70c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -31218,6 +31218,7 @@
}
public final class Debug {
+ method public static void attachJvmtiAgent(java.lang.String, java.lang.String) throws java.io.IOException;
method public static deprecated void changeDebugPort(int);
method public static void dumpHprofData(java.lang.String) throws java.io.IOException;
method public static boolean dumpService(java.lang.String, java.io.FileDescriptor, java.lang.String[]);
@@ -45170,6 +45171,7 @@
field public static final int STATE_DOZE_SUSPEND = 4; // 0x4
field public static final int STATE_OFF = 1; // 0x1
field public static final int STATE_ON = 2; // 0x2
+ field public static final int STATE_ON_SUSPEND = 6; // 0x6
field public static final int STATE_UNKNOWN = 0; // 0x0
field public static final int STATE_VR = 5; // 0x5
}
diff --git a/cmds/appwidget/appwidget b/cmds/appwidget/appwidget
index 6105009..26ab173 100755
--- a/cmds/appwidget/appwidget
+++ b/cmds/appwidget/appwidget
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "appwidget" on the device, which has a very rudimentary shell.
base=/system
export CLASSPATH=$base/framework/appwidget.jar
diff --git a/cmds/bmgr/bmgr b/cmds/bmgr/bmgr
index 6b4bbe2d..60b5833 100755
--- a/cmds/bmgr/bmgr
+++ b/cmds/bmgr/bmgr
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "bmgr" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/bu/bu b/cmds/bu/bu
index e8dbc31..e50b53d 100755
--- a/cmds/bu/bu
+++ b/cmds/bu/bu
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "bu" on the device
#
base=/system
diff --git a/cmds/content/content b/cmds/content/content
index a8e056d..f1bfe17 100755
--- a/cmds/content/content
+++ b/cmds/content/content
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "content" on the device, which has a very rudimentary shell.
base=/system
export CLASSPATH=$base/framework/content.jar
diff --git a/cmds/dpm/dpm b/cmds/dpm/dpm
index c2e5cbb..e0efdc1 100755
--- a/cmds/dpm/dpm
+++ b/cmds/dpm/dpm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "dpm" on the device
#
base=/system
diff --git a/cmds/ime/ime b/cmds/ime/ime
index 96c56d3..1a1fdd9 100755
--- a/cmds/ime/ime
+++ b/cmds/ime/ime
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "pm" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/input/input b/cmds/input/input
index 7f1a18e..54ab947 100755
--- a/cmds/input/input
+++ b/cmds/input/input
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "input" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/locksettings/locksettings b/cmds/locksettings/locksettings
index c963b23..0ef4fa9 100755
--- a/cmds/locksettings/locksettings
+++ b/cmds/locksettings/locksettings
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "locksettings" on the device
#
base=/system
diff --git a/cmds/media/media b/cmds/media/media
index 1194442..5c0eb2f 100755
--- a/cmds/media/media
+++ b/cmds/media/media
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "media_cmd" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 8183838..53f85b2 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "pm" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/requestsync/requestsync b/cmds/requestsync/requestsync
index 9315675..2d5d0e4 100755
--- a/cmds/requestsync/requestsync
+++ b/cmds/requestsync/requestsync
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "requestsync" on the device
#
base=/system
diff --git a/cmds/sm/sm b/cmds/sm/sm
index 8fba007..4bc859e0 100755
--- a/cmds/sm/sm
+++ b/cmds/sm/sm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "sm" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index a9a4118..699de94 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -20,6 +20,9 @@
import static android.os.storage.StorageManager.PROP_HAS_ADOPTABLE;
import static android.os.storage.StorageManager.PROP_VIRTUAL_DISK;
+import android.os.IBinder;
+import android.os.IVoldTaskListener;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -29,6 +32,8 @@
import android.os.storage.VolumeInfo;
import android.util.Log;
+import java.util.concurrent.CompletableFuture;
+
public final class Sm {
private static final String TAG = "Sm";
@@ -221,9 +226,23 @@
mSm.format(volId);
}
- public void runBenchmark() throws RemoteException {
+ public void runBenchmark() throws Exception {
final String volId = nextArg();
- mSm.benchmark(volId);
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runForget() throws RemoteException {
@@ -235,8 +254,22 @@
}
}
- public void runFstrim() throws RemoteException {
- mSm.fstrim(0);
+ public void runFstrim() throws Exception {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ mSm.fstrim(0, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ // Touch to unparcel
+ extras.size();
+ result.complete(extras);
+ }
+ });
+ System.out.println(result.get());
}
public void runSetVirtualDisk() throws RemoteException {
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 86f353b..87d318b 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -157,8 +157,10 @@
tests/LogReader_test.cpp \
tests/MetricsManager_test.cpp \
tests/UidMap_test.cpp \
- tests/OringDurationTracker_test.cpp \
- tests/MaxDurationTracker_test.cpp
+ tests/metrics/OringDurationTracker_test.cpp \
+ tests/metrics/MaxDurationTracker_test.cpp \
+ tests/metrics/CountMetricProducer_test.cpp \
+ tests/metrics/EventMetricProducer_test.cpp
LOCAL_STATIC_LIBRARIES := \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index c0cedb1..8910523 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -89,7 +89,7 @@
*dest = m;
}
auto temp = mUidMap->getOutput(key);
- report.set_allocated_uid_map(&temp);
+ report.mutable_uid_map()->Swap(&temp);
return report;
}
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index 38953f1..3608ee4 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -83,28 +83,31 @@
return false;
}
+ uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+
data->clear();
Return<void> ret = gPowerHalV1_0->getPlatformLowPowerStats(
- [&data](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
+ [&data, timestamp](hidl_vec<PowerStatePlatformSleepState> states, Status status) {
if (status != Status::SUCCESS) return;
for (size_t i = 0; i < states.size(); i++) {
const PowerStatePlatformSleepState& state = states[i];
- auto statePtr = make_shared<LogEvent>(power_state_platform_sleep_state_tag);
+ auto statePtr =
+ make_shared<LogEvent>(power_state_platform_sleep_state_tag, timestamp);
auto elemList = statePtr->GetAndroidLogEventList();
*elemList << state.name;
*elemList << state.residencyInMsecSinceBoot;
*elemList << state.totalTransitions;
*elemList << state.supportedOnlyInSuspend;
+ statePtr->init();
data->push_back(statePtr);
-
VLOG("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
(long long)state.residencyInMsecSinceBoot,
(long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0);
for (auto voter : state.voters) {
- auto voterPtr = make_shared<LogEvent>(power_state_voter_tag);
+ auto voterPtr = make_shared<LogEvent>(power_state_voter_tag, timestamp);
auto elemList = voterPtr->GetAndroidLogEventList();
*elemList << state.name;
*elemList << voter.name;
@@ -128,7 +131,7 @@
android::hardware::power::V1_1::IPower::castFrom(gPowerHalV1_0);
if (gPowerHal_1_1 != nullptr) {
ret = gPowerHal_1_1->getSubsystemLowPowerStats(
- [&data](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
+ [&data, timestamp](hidl_vec<PowerStateSubsystem> subsystems, Status status) {
if (status != Status::SUCCESS) return;
@@ -137,8 +140,8 @@
const PowerStateSubsystem& subsystem = subsystems[i];
for (size_t j = 0; j < subsystem.states.size(); j++) {
const PowerStateSubsystemSleepState& state = subsystem.states[j];
- auto subsystemStatePtr =
- make_shared<LogEvent>(power_state_subsystem_state_tag);
+ auto subsystemStatePtr = make_shared<LogEvent>(
+ power_state_subsystem_state_tag, timestamp);
auto elemList = subsystemStatePtr->GetAndroidLogEventList();
*elemList << subsystem.name;
*elemList << state.name;
@@ -146,6 +149,7 @@
*elemList << state.totalTransitions;
*elemList << state.lastEntryTimestampMs;
*elemList << state.supportedOnlyInSuspend;
+ subsystemStatePtr->init();
data->push_back(subsystemStatePtr);
VLOG("subsystemstate: %s, %s, %lld, %lld, %lld",
subsystem.name.c_str(), state.name.c_str(),
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 003b5c4..43543cc 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -30,11 +30,11 @@
#include <iostream>
+using std::make_shared;
using std::map;
+using std::shared_ptr;
using std::string;
using std::vector;
-using std::make_shared;
-using std::shared_ptr;
namespace android {
namespace os {
@@ -42,40 +42,35 @@
StatsPullerManager::StatsPullerManager()
: mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
- shared_ptr<StatsPuller> statsCompanionServicePuller = make_shared<StatsCompanionServicePuller>();
- shared_ptr <StatsPuller>
- resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
+ shared_ptr<StatsPuller> statsCompanionServicePuller =
+ make_shared<StatsCompanionServicePuller>();
+ shared_ptr<StatsPuller> resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
- mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED,
- statsCompanionServicePuller});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED,
- statsCompanionServicePuller});
- mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED,
- statsCompanionServicePuller});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG,
- statsCompanionServicePuller});
- mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG,
- statsCompanionServicePuller});
- mPullers.insert({android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED,
- resourcePowerManagerPuller});
- mPullers.insert({android::util::POWER_STATE_VOTER_PULLED,
- resourcePowerManagerPuller});
- mPullers.insert({android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED,
- resourcePowerManagerPuller});
+ mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED, statsCompanionServicePuller});
+ mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED, statsCompanionServicePuller});
+ mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED, statsCompanionServicePuller});
+ mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
+ mPullers.insert(
+ {android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
+ mPullers.insert(
+ {android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
+ mPullers.insert({android::util::POWER_STATE_VOTER_PULLED, resourcePowerManagerPuller});
+ mPullers.insert(
+ {android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
mStatsCompanionService = StatsService::getStatsCompanionService();
}
- bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
- if (DEBUG) ALOGD("Initiating pulling %d", tagId);
+bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
+ if (DEBUG) ALOGD("Initiating pulling %d", tagId);
- if (mPullers.find(tagId) != mPullers.end()) {
- return mPullers.find(tagId)->second->Pull(tagId, data);
- } else {
- ALOGD("Unknown tagId %d", tagId);
- return false; // Return early since we don't know what to pull.
- }
- }
+ if (mPullers.find(tagId) != mPullers.end()) {
+ return mPullers.find(tagId)->second->Pull(tagId, data);
+ } else {
+ ALOGD("Unknown tagId %d", tagId);
+ return false; // Return early since we don't know what to pull.
+ }
+}
StatsPullerManager& StatsPullerManager::GetInstance() {
static StatsPullerManager instance;
@@ -91,7 +86,8 @@
return time(nullptr) * 1000;
}
-void StatsPullerManager::RegisterReceiver(int tagId, sp<PullDataReceiver> receiver, long intervalMs) {
+void StatsPullerManager::RegisterReceiver(int tagId, sp<PullDataReceiver> receiver,
+ long intervalMs) {
AutoMutex _l(mReceiversLock);
vector<ReceiverInfo>& receivers = mReceivers[tagId];
for (auto it = receivers.begin(); it != receivers.end(); it++) {
@@ -143,8 +139,8 @@
vector<pair<int, vector<ReceiverInfo*>>>();
for (auto& pair : mReceivers) {
vector<ReceiverInfo*> receivers = vector<ReceiverInfo*>();
- if (pair.second.size() != 0){
- for(auto& receiverInfo : pair.second) {
+ if (pair.second.size() != 0) {
+ for (auto& receiverInfo : pair.second) {
if (receiverInfo.timeInfo.first + receiverInfo.timeInfo.second > currentTimeMs) {
receivers.push_back(&receiverInfo);
}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 8220fcb..913b906 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -35,7 +35,7 @@
init(msg.entry_v1.sec * NS_PER_SEC + msg.entry_v1.nsec, &mList);
}
-LogEvent::LogEvent(int tag) : mList(tag) {
+LogEvent::LogEvent(int tag, uint64_t timestampNs) : mList(tag), mTimestampNs(timestampNs) {
}
LogEvent::~LogEvent() {
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index df75d9f..2984940 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -50,7 +50,7 @@
* 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(int tag);
+ explicit LogEvent(int tag, uint64_t timestampNs);
~LogEvent();
@@ -123,7 +123,9 @@
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;
+
+ uint64_t mTimestampNs;
+
int mTagId;
};
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 71cb771..100a7a4 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -58,10 +58,9 @@
// TODO: add back AnomalyTracker.
CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
- const sp<ConditionWizard>& wizard)
- // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
- : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
- mMetric(metric) {
+ const sp<ConditionWizard>& wizard,
+ const uint64_t startTimeNs)
+ : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
// TODO: evaluate initial conditions. and set mConditionMet.
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
mBucketSizeNs = metric.bucket().bucket_size_millis() * 1000 * 1000;
@@ -114,15 +113,17 @@
}
StatsLogReport CountMetricProducer::onDumpReport() {
- long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+ long long endTime = time(nullptr) * NS_PER_SEC;
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
flushCounterIfNeeded(endTime);
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
- for (const auto& counter : mPastBucketProtos) {
+ for (const auto& counter : mPastBuckets) {
const HashableDimensionKey& hashableKey = counter.first;
+ VLOG(" dimension key %s", hashableKey.c_str());
auto it = mDimensionKeyMap.find(hashableKey);
if (it == mDimensionKeyMap.end()) {
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
@@ -147,20 +148,17 @@
}
// Then fill bucket_info (CountBucketInfo).
- for (const auto& proto : counter.second) {
- size_t bufferSize = proto->size();
- char* buffer(new char[bufferSize]);
- size_t pos = 0;
- auto it = proto->data();
- while (it.readBuffer() != NULL) {
- size_t toRead = it.currentToRead();
- std::memcpy(&buffer[pos], it.readBuffer(), toRead);
- pos += toRead;
- it.rp()->move(toRead);
- }
- mProto->write(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION, buffer, bufferSize);
+ for (const auto& bucket : counter.second) {
+ long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+ mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
+ (long long)bucket.mBucketStartNs);
+ mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
+ (long long)bucket.mBucketEndNs);
+ mProto->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)bucket.mCount);
+ mProto->end(bucketInfoToken);
+ VLOG("\t bucket [%lld - %lld] count: %lld", (long long)bucket.mBucketStartNs,
+ (long long)bucket.mBucketEndNs, (long long)bucket.mCount);
}
-
mProto->end(wrapperToken);
}
@@ -169,8 +167,8 @@
(long long)mCurrentBucketStartTimeNs);
size_t bufferSize = mProto->size();
- VLOG("metric %lld dump report now...", mMetric.metric_id());
std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+
size_t pos = 0;
auto it = mProto->data();
while (it.readBuffer() != NULL) {
@@ -181,7 +179,7 @@
}
startNewProtoOutputStream(endTime);
- mPastBucketProtos.clear();
+ mPastBuckets.clear();
mByteSize = 0;
// TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
@@ -239,20 +237,16 @@
// adjust the bucket start time
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ CountBucket info;
+ info.mBucketStartNs = mCurrentBucketStartTimeNs;
+ info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
for (const auto& counter : mCurrentSlicedCounter) {
- unique_ptr<ProtoOutputStream> proto = make_unique<ProtoOutputStream>();
- proto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
- (long long)mCurrentBucketStartTimeNs);
- proto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
- (long long)mCurrentBucketStartTimeNs + mBucketSizeNs);
- proto->write(FIELD_TYPE_INT64 | FIELD_ID_COUNT, (long long)counter.second);
-
- auto& bucketList = mPastBucketProtos[counter.first];
- bucketList.push_back(std::move(proto));
- mByteSize += proto->size();
-
+ info.mCount = counter.second;
+ auto& bucketList = mPastBuckets[counter.first];
+ bucketList.push_back(info);
VLOG("metric %lld, dump key value: %s -> %d", mMetric.metric_id(), counter.first.c_str(),
counter.second);
+ mByteSize += sizeof(info);
}
// TODO: Re-add anomaly detection (similar to):
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 473a4ba..3bfc724 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -20,6 +20,7 @@
#include <unordered_map>
#include <android/util/ProtoOutputStream.h>
+#include <gtest/gtest_prod.h>
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
#include "CountAnomalyTracker.h"
@@ -34,11 +35,17 @@
namespace os {
namespace statsd {
+struct CountBucket {
+ int64_t mBucketStartNs;
+ int64_t mBucketEndNs;
+ int64_t mCount;
+};
+
class CountMetricProducer : public MetricProducer {
public:
// TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
CountMetricProducer(const CountMetric& countMetric, const int conditionIndex,
- const sp<ConditionWizard>& wizard);
+ const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
virtual ~CountMetricProducer();
@@ -66,8 +73,7 @@
private:
const CountMetric mMetric;
- std::unordered_map<HashableDimensionKey,
- std::vector<unique_ptr<android::util::ProtoOutputStream>>> mPastBucketProtos;
+ std::unordered_map<HashableDimensionKey, std::vector<CountBucket>> mPastBuckets;
size_t mByteSize;
@@ -83,6 +89,10 @@
long long mProtoToken;
void startNewProtoOutputStream(long long timestamp);
+
+ FRIEND_TEST(CountMetricProducerTest, TestNonDimensionalEvents);
+ FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
+ FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 340f503..09132bf 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -35,9 +35,9 @@
const int conditionIndex, const size_t startIndex,
const size_t stopIndex, const size_t stopAllIndex,
const sp<ConditionWizard>& wizard,
- const vector<KeyMatcher>& internalDimension)
- // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
- : MetricProducer(time(nullptr) * NANO_SECONDS_IN_A_SECOND, conditionIndex, wizard),
+ const vector<KeyMatcher>& internalDimension,
+ const uint64_t startTimeNs)
+ : MetricProducer(startTimeNs, conditionIndex, wizard),
mMetric(metric),
mStartIndex(startIndex),
mStopIndex(stopIndex),
@@ -131,7 +131,7 @@
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
- flushIfNeeded(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ flushIfNeeded(time(nullptr) * NS_PER_SEC);
report.set_end_report_nanos(mCurrentBucketStartTimeNs);
StatsLogReport_DurationMetricDataWrapper* wrapper = report.mutable_duration_metrics();
@@ -195,10 +195,10 @@
}
size_t DurationMetricProducer::byteSize() {
-// TODO: return actual proto size when ProtoOutputStream is ready for use for
-// DurationMetricsProducer.
-// return mProto->size();
- return 0;
+ // TODO: return actual proto size when ProtoOutputStream is ready for use for
+ // DurationMetricsProducer.
+ // return mProto->size();
+ return 0;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index febf25d..12ff58e 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -40,7 +40,7 @@
DurationMetricProducer(const DurationMetric& durationMetric, const int conditionIndex,
const size_t startIndex, const size_t stopIndex,
const size_t stopAllIndex, const sp<ConditionWizard>& wizard,
- const vector<KeyMatcher>& internalDimension);
+ const vector<KeyMatcher>& internalDimension, const uint64_t startTimeNs);
virtual ~DurationMetricProducer();
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index cbae1d3..677ae38 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -46,10 +46,9 @@
const int FIELD_ID_STATS_EVENTS = 2;
EventMetricProducer::EventMetricProducer(const EventMetric& metric, const int conditionIndex,
- const sp<ConditionWizard>& wizard)
- // TODO: Pass in the start time from MetricsManager, instead of calling time() here.
- : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
- mMetric(metric) {
+ const sp<ConditionWizard>& wizard,
+ const uint64_t startTimeNs)
+ : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric) {
if (metric.links().size() > 0) {
mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
metric.links().end());
@@ -82,7 +81,7 @@
}
StatsLogReport EventMetricProducer::onDumpReport() {
- long long endTime = time(nullptr) * NANO_SECONDS_IN_A_SECOND;
+ long long endTime = time(nullptr) * NS_PER_SEC;
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
@@ -114,7 +113,6 @@
const size_t matcherIndex, const HashableDimensionKey& eventKey,
const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
const LogEvent& event, bool scheduledPull) {
-
if (!condition) {
return;
}
@@ -128,7 +126,7 @@
}
size_t EventMetricProducer::byteSize() {
- return mProto->size();
+ return mProto->size();
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 7dd0e38..0fc2b5b 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -20,6 +20,7 @@
#include <unordered_map>
#include <android/util/ProtoOutputStream.h>
+
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
#include "MetricProducer.h"
@@ -35,13 +36,14 @@
public:
// TODO: Pass in the start time from MetricsManager, it should be consistent for all metrics.
EventMetricProducer(const EventMetric& eventMetric, const int conditionIndex,
- const sp<ConditionWizard>& wizard);
+ const sp<ConditionWizard>& wizard, const uint64_t startTimeNs);
virtual ~EventMetricProducer();
void onMatchedLogEventInternal(const size_t matcherIndex, const HashableDimensionKey& eventKey,
const std::map<std::string, HashableDimensionKey>& conditionKey,
- bool condition, const LogEvent& event, bool scheduledPull) override;
+ bool condition, const LogEvent& event,
+ bool scheduledPull) override;
void onConditionChanged(const bool conditionMet, const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index bd638b4..285c8f4 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -35,7 +35,7 @@
GaugeMetricProducer::GaugeMetricProducer(const GaugeMetric& metric, const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId)
- : MetricProducer((time(nullptr) * NANO_SECONDS_IN_A_SECOND), conditionIndex, wizard),
+ : MetricProducer((time(nullptr) * NS_PER_SEC), conditionIndex, wizard),
mMetric(metric),
mPullTagId(pullTagId) {
if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
@@ -94,7 +94,7 @@
// Dump current bucket if it's stale.
// If current bucket is still on-going, don't force dump current bucket.
// In finish(), We can force dump current bucket.
- flushGaugeIfNeededLocked(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
+ flushGaugeIfNeededLocked(time(nullptr) * NS_PER_SEC);
report.set_end_report_nanos(mCurrentBucketStartTimeNs);
StatsLogReport_GaugeMetricDataWrapper* wrapper = report.mutable_gauge_metrics();
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 3b117ec..6ba726f4 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -40,6 +40,7 @@
: mStartTimeNs(startTimeNs),
mCurrentBucketStartTimeNs(startTimeNs),
mCondition(conditionIndex >= 0 ? false : true),
+ mConditionSliced(false),
mWizard(wizard),
mConditionTrackerIndex(conditionIndex) {
// reuse the same map for non-sliced metrics too. this way, we avoid too many if-else.
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 9b708dd6..80b325f 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,17 +15,17 @@
*/
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-
#include "MetricsManager.h"
-#include <log/logprint.h>
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
+
#include "CountMetricProducer.h"
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/SimpleLogMatchingTracker.h"
#include "metrics_manager_util.h"
#include "stats_util.h"
+#include <log/logprint.h>
using std::make_unique;
using std::set;
using std::string;
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 44cd637..63e2c33 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -96,4 +96,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index ca33371..07a078f 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -23,12 +23,12 @@
#include <limits.h>
#include <stdlib.h>
-using std::map;
-using std::unordered_map;
using std::list;
using std::make_shared;
+using std::map;
using std::shared_ptr;
using std::unique_ptr;
+using std::unordered_map;
namespace android {
namespace os {
@@ -36,51 +36,50 @@
// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
- const sp<ConditionWizard>& wizard, const int pullTagId)
- : MetricProducer((time(nullptr) / 600 * 600 * NANO_SECONDS_IN_A_SECOND), conditionIndex,
- wizard),
- mMetric(metric),
- mPullTagId(pullTagId) {
- // TODO: valuemetric for pushed events may need unlimited bucket length
- mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const uint64_t startTimeNs)
+ : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric), mPullTagId(pullTagId) {
+ // TODO: valuemetric for pushed events may need unlimited bucket length
+ mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
- mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
+ mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
- if (metric.links().size() > 0) {
- mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
- metric.links().end());
- mConditionSliced = true;
- }
+ if (metric.links().size() > 0) {
+ mConditionLinks.insert(mConditionLinks.begin(), metric.links().begin(),
+ metric.links().end());
+ mConditionSliced = true;
+ }
- if (!metric.has_condition() && mPullTagId != -1) {
- mStatsPullerManager.RegisterReceiver(mPullTagId, this, metric.bucket().bucket_size_millis());
- }
+ if (!metric.has_condition() && mPullTagId != -1) {
+ mStatsPullerManager.RegisterReceiver(mPullTagId, this,
+ metric.bucket().bucket_size_millis());
+ }
- VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
- (long long)mBucketSizeNs, (long long)mStartTimeNs);
+ VLOG("value metric %lld created. bucket size %lld start_time: %lld", metric.metric_id(),
+ (long long)mBucketSizeNs, (long long)mStartTimeNs);
}
ValueMetricProducer::~ValueMetricProducer() {
- VLOG("~ValueMetricProducer() called");
+ VLOG("~ValueMetricProducer() called");
}
void ValueMetricProducer::finish() {
- // TODO: write the StatsLogReport to dropbox using
- // DropboxWriter.
+ // TODO: write the StatsLogReport to dropbox using
+ // DropboxWriter.
}
static void addSlicedCounterToReport(StatsLogReport_ValueMetricDataWrapper& wrapper,
const vector<KeyValuePair>& key,
const vector<ValueBucketInfo>& buckets) {
- ValueMetricData* data = wrapper.add_data();
- for (const auto& kv : key) {
- data->add_dimension()->CopyFrom(kv);
- }
- for (const auto& bucket : buckets) {
- data->add_bucket_info()->CopyFrom(bucket);
- VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
- bucket.end_bucket_nanos(), bucket.value());
- }
+ ValueMetricData* data = wrapper.add_data();
+ for (const auto& kv : key) {
+ data->add_dimension()->CopyFrom(kv);
+ }
+ for (const auto& bucket : buckets) {
+ data->add_bucket_info()->CopyFrom(bucket);
+ VLOG("\t bucket [%lld - %lld] value: %lld", bucket.start_bucket_nanos(),
+ bucket.end_bucket_nanos(), bucket.value());
+ }
}
void ValueMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
@@ -88,33 +87,28 @@
}
StatsLogReport ValueMetricProducer::onDumpReport() {
- VLOG("metric %lld dump report now...", mMetric.metric_id());
+ VLOG("metric %lld dump report now...", mMetric.metric_id());
- StatsLogReport report;
- report.set_metric_id(mMetric.metric_id());
- report.set_start_report_nanos(mStartTimeNs);
+ StatsLogReport report;
+ report.set_metric_id(mMetric.metric_id());
+ report.set_start_report_nanos(mStartTimeNs);
+ report.set_end_report_nanos(mCurrentBucketStartTimeNs);
- // Dump current bucket if it's stale.
- // If current bucket is still on-going, don't force dump current bucket.
- // In finish(), We can force dump current bucket.
- // flush_if_needed(time(nullptr) * NANO_SECONDS_IN_A_SECOND);
- report.set_end_report_nanos(mCurrentBucketStartTimeNs);
+ StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
- StatsLogReport_ValueMetricDataWrapper* wrapper = report.mutable_value_metrics();
+ for (const auto& pair : mPastBuckets) {
+ const HashableDimensionKey& hashableKey = pair.first;
+ auto it = mDimensionKeyMap.find(hashableKey);
+ if (it == mDimensionKeyMap.end()) {
+ ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
+ continue;
+ }
- for (const auto& pair : mPastBuckets) {
- const HashableDimensionKey& hashableKey = pair.first;
- auto it = mDimensionKeyMap.find(hashableKey);
- if (it == mDimensionKeyMap.end()) {
- ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
- continue;
+ VLOG(" dimension key %s", hashableKey.c_str());
+ addSlicedCounterToReport(*wrapper, it->second, pair.second);
}
-
- VLOG(" dimension key %s", hashableKey.c_str());
- addSlicedCounterToReport(*wrapper, it->second, pair.second);
- }
- return report;
- // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
+ return report;
+ // TODO: Clear mPastBuckets, mDimensionKeyMap once the report is dumped.
}
void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
@@ -158,50 +152,50 @@
}
void ValueMetricProducer::onMatchedLogEventInternal(
- const size_t matcherIndex, const HashableDimensionKey& eventKey,
- const map<string, HashableDimensionKey>& conditionKey, bool condition,
- const LogEvent& event, bool scheduledPull) {
- uint64_t eventTimeNs = event.GetTimestampNs();
- if (eventTimeNs < mCurrentBucketStartTimeNs) {
- VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
- (long long)mCurrentBucketStartTimeNs);
- return;
- }
-
- Interval& interval = mCurrentSlicedBucket[eventKey];
-
- long value = get_value(event);
-
- if (scheduledPull) {
- if (interval.raw.size() > 0) {
- interval.raw.back().second = value;
- } else {
- interval.raw.push_back(std::make_pair(value, value));
+ const size_t matcherIndex, const HashableDimensionKey& eventKey,
+ const map<string, HashableDimensionKey>& conditionKey, bool condition,
+ const LogEvent& event, bool scheduledPull) {
+ uint64_t eventTimeNs = event.GetTimestampNs();
+ if (eventTimeNs < mCurrentBucketStartTimeNs) {
+ VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+ (long long)mCurrentBucketStartTimeNs);
+ return;
}
- mNextSlicedBucket[eventKey].raw[0].first = value;
- } else {
- if (mCondition == ConditionState::kTrue) {
- interval.raw.push_back(std::make_pair(value, 0));
+
+ Interval& interval = mCurrentSlicedBucket[eventKey];
+
+ long value = get_value(event);
+
+ if (scheduledPull) {
+ if (interval.raw.size() > 0) {
+ interval.raw.back().second = value;
+ } else {
+ interval.raw.push_back(std::make_pair(value, value));
+ }
+ mNextSlicedBucket[eventKey].raw[0].first = value;
} else {
- if (interval.raw.size() != 0) {
- interval.raw.back().second = value;
- }
+ if (mCondition == ConditionState::kTrue) {
+ interval.raw.push_back(std::make_pair(value, 0));
+ } else {
+ if (interval.raw.size() != 0) {
+ interval.raw.back().second = value;
+ }
+ }
}
- }
- if (mPullTagId == -1) {
- flush_if_needed(eventTimeNs);
- }
+ if (mPullTagId == -1) {
+ flush_if_needed(eventTimeNs);
+ }
}
long ValueMetricProducer::get_value(const LogEvent& event) {
- status_t err = NO_ERROR;
- long val = event.GetLong(mMetric.value_field(), &err);
- if (err == NO_ERROR) {
- return val;
- } else {
- VLOG("Can't find value in message.");
- return 0;
- }
+ status_t err = NO_ERROR;
+ long val = event.GetLong(mMetric.value_field(), &err);
+ if (err == NO_ERROR) {
+ return val;
+ } else {
+ VLOG("Can't find value in message.");
+ return 0;
+ }
}
void ValueMetricProducer::flush_if_needed(const uint64_t eventTimeNs) {
@@ -218,22 +212,22 @@
info.set_end_bucket_nanos(mCurrentBucketStartTimeNs + mBucketSizeNs);
for (const auto& slice : mCurrentSlicedBucket) {
- long value = 0;
- for (const auto& pair : slice.second.raw) {
- value += pair.second - pair.first;
- }
- info.set_value(value);
- VLOG(" %s, %ld", slice.first.c_str(), value);
- // it will auto create new vector of ValuebucketInfo if the key is not found.
- auto& bucketList = mPastBuckets[slice.first];
- bucketList.push_back(info);
+ long value = 0;
+ for (const auto& pair : slice.second.raw) {
+ value += pair.second - pair.first;
+ }
+ info.set_value(value);
+ VLOG(" %s, %ld", slice.first.c_str(), value);
+ // it will auto create new vector of ValuebucketInfo if the key is not found.
+ auto& bucketList = mPastBuckets[slice.first];
+ bucketList.push_back(info);
}
// Reset counters
mCurrentSlicedBucket.swap(mNextSlicedBucket);
mNextSlicedBucket.clear();
int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
- if (numBucketsForward >1) {
+ if (numBucketsForward > 1) {
VLOG("Skipping forward %lld buckets", (long long)numBucketsForward);
}
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
@@ -243,4 +237,4 @@
} // namespace statsd
} // namespace os
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 8653981..548cd44 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -33,7 +33,8 @@
class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
- const sp<ConditionWizard>& wizard, const int pullTagId);
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const uint64_t startTimeNs);
virtual ~ValueMetricProducer();
@@ -47,7 +48,9 @@
void onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& data) override;
// TODO: Implement this later.
- size_t byteSize() override{return 0;};
+ size_t byteSize() override {
+ return 0;
+ };
// TODO: Implement this later.
virtual void notifyAppUpgrade(const string& apk, const int uid, const int version) override{};
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index a0fdcdc..ca9cdfb 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -192,10 +192,11 @@
unordered_map<int, std::vector<int>>& conditionToMetricMap,
unordered_map<int, std::vector<int>>& trackerToMetricMap) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
- const int allMetricsCount =
- config.count_metric_size() + config.duration_metric_size() + config.event_metric_size() + config.value_metric_size();
+ const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
+ config.event_metric_size() + config.value_metric_size();
allMetricProducers.reserve(allMetricsCount);
StatsPullerManager& statsPullerManager = StatsPullerManager::GetInstance();
+ uint64_t startTimeNs = time(nullptr) * NS_PER_SEC;
// Build MetricProducers for each metric defined in config.
// build CountMetricProducer
@@ -221,7 +222,8 @@
conditionToMetricMap);
}
- sp<MetricProducer> countProducer = new CountMetricProducer(metric, conditionIndex, wizard);
+ sp<MetricProducer> countProducer =
+ new CountMetricProducer(metric, conditionIndex, wizard, startTimeNs);
allMetricProducers.push_back(countProducer);
}
@@ -282,7 +284,7 @@
sp<MetricProducer> durationMetric = new DurationMetricProducer(
metric, conditionIndex, trackerIndices[0], trackerIndices[1], trackerIndices[2],
- wizard, internalDimension);
+ wizard, internalDimension, startTimeNs);
allMetricProducers.push_back(durationMetric);
}
@@ -308,7 +310,9 @@
conditionToMetricMap);
}
- sp<MetricProducer> eventMetric = new EventMetricProducer(metric, conditionIndex, wizard);
+ sp<MetricProducer> eventMetric =
+ new EventMetricProducer(metric, conditionIndex, wizard, startTimeNs);
+
allMetricProducers.push_back(eventMetric);
}
@@ -348,7 +352,7 @@
}
sp<MetricProducer> valueProducer =
- new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId);
+ new ValueMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs);
allMetricProducers.push_back(valueProducer);
}
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index d3d7e37..a9507bf 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -28,7 +28,6 @@
#define DEFAULT_DIMENSION_KEY ""
#define MATCHER_NOT_FOUND -2
-#define NANO_SECONDS_IN_A_SECOND (1000 * 1000 * 1000)
typedef std::string HashableDimensionKey;
diff --git a/cmds/statsd/tests/LogEntryMatcher_test.cpp b/cmds/statsd/tests/LogEntryMatcher_test.cpp
index fdfe8ef..d0898b0 100644
--- a/cmds/statsd/tests/LogEntryMatcher_test.cpp
+++ b/cmds/statsd/tests/LogEntryMatcher_test.cpp
@@ -42,7 +42,7 @@
auto simpleMatcher = matcher.mutable_simple_log_entry_matcher();
simpleMatcher->set_tag(TAG_ID);
- LogEvent event(TAG_ID);
+ LogEvent event(TAG_ID, 0);
// Convert to a LogEvent
event.init();
@@ -62,7 +62,7 @@
keyValue2->mutable_key_matcher()->set_key(FIELD_ID_2);
// Set up the event
- LogEvent event(TAG_ID);
+ LogEvent event(TAG_ID, 0);
auto list = event.GetAndroidLogEventList();
*list << true;
*list << false;
@@ -98,7 +98,7 @@
keyValue->set_eq_string("some value");
// Set up the event
- LogEvent event(TAG_ID);
+ LogEvent event(TAG_ID, 0);
auto list = event.GetAndroidLogEventList();
*list << "some value";
@@ -119,7 +119,7 @@
keyValue->mutable_key_matcher()->set_key(FIELD_ID_1);
// Set up the event
- LogEvent event(TAG_ID);
+ LogEvent event(TAG_ID, 0);
auto list = event.GetAndroidLogEventList();
*list << 11;
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
new file mode 100644
index 0000000..5a4ee73
--- /dev/null
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -0,0 +1,183 @@
+// 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 "metrics_test_helper.h"
+#include "src/metrics/CountMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(CountMetricProducerTest, TestNonDimensionalEvents) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+ int tagId = 1;
+
+ CountMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+
+ LogEvent event1(tagId, bucketStartTimeNs + 1);
+ LogEvent event2(tagId, bucketStartTimeNs + 2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ CountMetricProducer countProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+ bucketStartTimeNs);
+
+ // 2 events in bucket 1.
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+ countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+ EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+ countProducer.mPastBuckets.end());
+ const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ const auto& bucketInfo = buckets[0];
+ EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+ EXPECT_EQ(2LL, bucketInfo.mCount);
+
+ // 1 matched event happens in bucket 2.
+ LogEvent event3(tagId, bucketStartTimeNs + bucketSizeNs + 2);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3, false);
+ countProducer.flushCounterIfNeeded(bucketStartTimeNs + 2 * bucketSizeNs + 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+ EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+ countProducer.mPastBuckets.end());
+ EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY].size());
+ const auto& bucketInfo2 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY][1];
+ EXPECT_EQ(bucket2StartTimeNs, bucketInfo2.mBucketStartNs);
+ EXPECT_EQ(bucket2StartTimeNs + bucketSizeNs, bucketInfo2.mBucketEndNs);
+ EXPECT_EQ(1LL, bucketInfo2.mCount);
+
+ // nothing happens in bucket 3. we should not record anything for bucket 3.
+ countProducer.flushCounterIfNeeded(bucketStartTimeNs + 3 * bucketSizeNs + 1);
+ EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+ EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+ countProducer.mPastBuckets.end());
+ const auto& buckets3 = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+ EXPECT_EQ(2UL, buckets3.size());
+}
+
+TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ CountMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_condition("SCREEN_ON");
+
+ LogEvent event1(1, bucketStartTimeNs + 1);
+ LogEvent event2(1, bucketStartTimeNs + 10);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ CountMetricProducer countProducer(metric, 1, wizard, bucketStartTimeNs);
+
+ countProducer.onConditionChanged(true, bucketStartTimeNs);
+ countProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+ countProducer.onConditionChanged(false /*new condition*/, bucketStartTimeNs + 2);
+ countProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+ EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
+
+ countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+ EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+ EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+ countProducer.mPastBuckets.end());
+ const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ const auto& bucketInfo = buckets[0];
+ EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+ EXPECT_EQ(1LL, bucketInfo.mCount);
+}
+
+TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ CountMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
+ EventConditionLink* link = metric.add_links();
+ link->set_condition("APP_IN_BACKGROUND_PER_UID");
+ link->add_key_in_main()->set_key(1);
+ link->add_key_in_condition()->set_key(2);
+
+ LogEvent event1(1, bucketStartTimeNs + 1);
+ auto list = event1.GetAndroidLogEventList();
+ *list << "111"; // uid
+ event1.init();
+ ConditionKey key1;
+ key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+
+ LogEvent event2(1, bucketStartTimeNs + 10);
+ auto list2 = event2.GetAndroidLogEventList();
+ *list2 << "222"; // uid
+ event2.init();
+ ConditionKey key2;
+ key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+
+ EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+
+ CountMetricProducer countProducer(metric, 1 /*condition tracker index*/, wizard,
+ bucketStartTimeNs);
+
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1, false);
+ countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2, false);
+
+ countProducer.flushCounterIfNeeded(bucketStartTimeNs + bucketSizeNs + 1);
+
+ EXPECT_EQ(1UL, countProducer.mPastBuckets.size());
+ EXPECT_TRUE(countProducer.mPastBuckets.find(DEFAULT_DIMENSION_KEY) !=
+ countProducer.mPastBuckets.end());
+ const auto& buckets = countProducer.mPastBuckets[DEFAULT_DIMENSION_KEY];
+ EXPECT_EQ(1UL, buckets.size());
+ const auto& bucketInfo = buckets[0];
+ EXPECT_EQ(bucketStartTimeNs, bucketInfo.mBucketStartNs);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, bucketInfo.mBucketEndNs);
+ EXPECT_EQ(1LL, bucketInfo.mCount);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
new file mode 100644
index 0000000..76dbc73
--- /dev/null
+++ b/cmds/statsd/tests/metrics/EventMetricProducer_test.cpp
@@ -0,0 +1,130 @@
+// 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 "metrics_test_helper.h"
+#include "src/metrics/EventMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(EventMetricProducerTest, TestNoCondition) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ EventMetric metric;
+ metric.set_metric_id(1);
+
+ LogEvent event1(1 /*tag id*/, bucketStartTimeNs + 1);
+ LogEvent event2(1 /*tag id*/, bucketStartTimeNs + 2);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ EventMetricProducer eventProducer(metric, -1 /*-1 meaning no condition*/, wizard,
+ bucketStartTimeNs);
+
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+ // TODO: get the report and check the content after the ProtoOutputStream change is done.
+ // eventProducer.onDumpReport();
+}
+
+TEST(EventMetricProducerTest, TestEventsWithNonSlicedCondition) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t eventStartTimeNs = bucketStartTimeNs + 1;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ EventMetric metric;
+ metric.set_metric_id(1);
+ metric.set_condition("SCREEN_ON");
+
+ LogEvent event1(1, bucketStartTimeNs + 1);
+ LogEvent event2(1, bucketStartTimeNs + 10);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+
+ eventProducer.onConditionChanged(true /*condition*/, bucketStartTimeNs);
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+
+ eventProducer.onConditionChanged(false /*condition*/, bucketStartTimeNs + 2);
+
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+ // TODO: get the report and check the content after the ProtoOutputStream change is done.
+ // eventProducer.onDumpReport();
+}
+
+TEST(EventMetricProducerTest, TestEventsWithSlicedCondition) {
+ uint64_t bucketStartTimeNs = 10000000000;
+ uint64_t bucketSizeNs = 30 * 1000 * 1000 * 1000LL;
+
+ EventMetric metric;
+ metric.set_metric_id(1);
+ metric.set_condition("APP_IN_BACKGROUND_PER_UID_AND_SCREEN_ON");
+ EventConditionLink* link = metric.add_links();
+ link->set_condition("APP_IN_BACKGROUND_PER_UID");
+ link->add_key_in_main()->set_key(1);
+ link->add_key_in_condition()->set_key(2);
+
+ LogEvent event1(1, bucketStartTimeNs + 1);
+ auto list = event1.GetAndroidLogEventList();
+ *list << "111"; // uid
+ event1.init();
+ ConditionKey key1;
+ key1["APP_IN_BACKGROUND_PER_UID"] = "2:111|";
+
+ LogEvent event2(1, bucketStartTimeNs + 10);
+ auto list2 = event2.GetAndroidLogEventList();
+ *list2 << "222"; // uid
+ event2.init();
+ ConditionKey key2;
+ key2["APP_IN_BACKGROUND_PER_UID"] = "2:222|";
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ EXPECT_CALL(*wizard, query(_, key1)).WillOnce(Return(ConditionState::kFalse));
+
+ EXPECT_CALL(*wizard, query(_, key2)).WillOnce(Return(ConditionState::kTrue));
+
+ EventMetricProducer eventProducer(metric, 1, wizard, bucketStartTimeNs);
+
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event1, false /*pulled*/);
+ eventProducer.onMatchedLogEvent(1 /*matcher index*/, event2, false /*pulled*/);
+
+ // TODO: get the report and check the content after the ProtoOutputStream change is done.
+ // eventProducer.onDumpReport();
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
similarity index 92%
rename from cmds/statsd/tests/MaxDurationTracker_test.cpp
rename to cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index ae8bf42..f2abe7b 100644
--- a/cmds/statsd/tests/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -12,12 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
+#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
#include "src/metrics/duration_helper/MaxDurationTracker.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
#include <stdio.h>
#include <set>
#include <unordered_map>
@@ -32,13 +32,9 @@
#ifdef __ANDROID__
-class MockConditionWizard : public ConditionWizard {
-public:
- MOCK_METHOD2(
- query,
- ConditionState(const int conditionIndex,
- const std::map<std::string, HashableDimensionKey>& conditionParameters));
-};
+namespace android {
+namespace os {
+namespace statsd {
TEST(MaxDurationTrackerTest, TestSimpleMaxDuration) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -110,6 +106,9 @@
EXPECT_EQ(5, buckets[0].duration_nanos());
}
+} // namespace statsd
+} // namespace os
+} // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
similarity index 90%
rename from cmds/statsd/tests/OringDurationTracker_test.cpp
rename to cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 0b79819..338d55d 100644
--- a/cmds/statsd/tests/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -12,18 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
+#include "metrics_test_helper.h"
#include "src/condition/ConditionWizard.h"
#include "src/metrics/duration_helper/OringDurationTracker.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
#include <stdio.h>
#include <set>
#include <unordered_map>
#include <vector>
-using namespace android::os::statsd;
using namespace testing;
using android::sp;
using std::set;
@@ -31,14 +30,9 @@
using std::vector;
#ifdef __ANDROID__
-
-class MockConditionWizard : public ConditionWizard {
-public:
- MOCK_METHOD2(
- query,
- ConditionState(const int conditionIndex,
- const std::map<std::string, HashableDimensionKey>& conditionParameters));
-};
+namespace android {
+namespace os {
+namespace statsd {
TEST(OringDurationTrackerTest, TestDurationOverlap) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
@@ -93,7 +87,9 @@
EXPECT_EQ(1u, buckets.size());
EXPECT_EQ(5, buckets[0].duration_nanos());
}
-
+} // namespace statsd
+} // namespace os
+} // namespace android
#else
GTEST_LOG_(INFO) << "This test does nothing.\n";
#endif
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
new file mode 100644
index 0000000..5fd7d62
--- /dev/null
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -0,0 +1,35 @@
+// 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.
+#pragma once
+
+#include "src/condition/ConditionWizard.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class MockConditionWizard : public ConditionWizard {
+public:
+ MOCK_METHOD2(
+ query,
+ ConditionState(const int conditionIndex,
+ const std::map<std::string, HashableDimensionKey>& conditionParameters));
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/svc/svc b/cmds/svc/svc
index 27111cd..07b50fe 100755
--- a/cmds/svc/svc
+++ b/cmds/svc/svc
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "am" on the device, which has a very rudimentary
# shell.
#
diff --git a/cmds/telecom/telecom b/cmds/telecom/telecom
index 9efdcfd..a19036b 100755
--- a/cmds/telecom/telecom
+++ b/cmds/telecom/telecom
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "telecom" on the device
#
base=/system
diff --git a/cmds/uiautomator/cmds/uiautomator/uiautomator b/cmds/uiautomator/cmds/uiautomator/uiautomator
index 86a1dba..889c2b5 100755
--- a/cmds/uiautomator/cmds/uiautomator/uiautomator
+++ b/cmds/uiautomator/cmds/uiautomator/uiautomator
@@ -1,3 +1,4 @@
+#!/system/bin/sh
#
# Copyright (C) 2012 The Android Open Source Project
#
diff --git a/cmds/vr/vr b/cmds/vr/vr
index a279007..dbde02a 100755
--- a/cmds/vr/vr
+++ b/cmds/vr/vr
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "vr" on the device
#
base=/system
diff --git a/cmds/wm/wm b/cmds/wm/wm
index f7a5bc7..16d6bd6 100755
--- a/cmds/wm/wm
+++ b/cmds/wm/wm
@@ -1,3 +1,4 @@
+#!/system/bin/sh
# Script to start "wm" on the device, which has a very rudimentary
# shell.
#
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8226e0f..d5d95fb 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,6 +22,7 @@
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView);
+ bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@
}
}
- private void bindLargeIcon(RemoteViews contentView) {
+ private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+ boolean alwaysShowReply) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
- if (mN.mLargeIcon != null) {
+ boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+ if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
- // Bind the reply action
- Action action = findReplyAction();
- contentView.setViewVisibility(R.id.reply_icon_action, action != null
- ? View.VISIBLE
- : View.GONE);
+ }
+ // Bind the reply action
+ Action action = findReplyAction();
- if (action != null) {
- int contrastColor = resolveContrastColor();
+ boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+ int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+ if (actionVisible) {
+ // We're only showing the icon as big if we're hiding the large icon
+ int contrastColor = resolveContrastColor();
+ int iconColor;
+ if (showLargeIcon) {
contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
contrastColor, PorterDuff.Mode.SRC_ATOP);
- int iconColor = NotificationColorUtil.isColorLight(contrastColor)
- ? Color.BLACK : Color.WHITE;
- contentView.setDrawableTint(R.id.reply_icon_action,
- false /* targetBackground */,
- iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
- contentView.setOnClickPendingIntent(R.id.reply_icon_action,
- action.actionIntent);
contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
- contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+ iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ } else {
+ contentView.setImageViewResource(R.id.right_icon,
+ R.drawable.ic_reply_notification_large);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ iconColor = contrastColor;
}
+ contentView.setDrawableTint(replyId,
+ false /* targetBackground */,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP);
+ contentView.setOnClickPendingIntent(replyId,
+ action.actionIntent);
+ contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+ } else {
+ contentView.setRemoteInputs(R.id.right_icon, null);
}
- contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+ ? View.VISIBLE
+ : View.GONE);
+ contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
? View.VISIBLE
: View.GONE);
}
@@ -6055,18 +6071,12 @@
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mMessages.clear();
- mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (messages != null && messages instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(messages);
- }
+ mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (histMessages != null && histMessages instanceof Parcelable[]) {
- mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
- }
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
/**
@@ -6074,38 +6084,34 @@
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
- if (!increasedHeight) {
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplate(mBuilder.getBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
- } else {
- mBuilder.mOriginalActions = mBuilder.mActions;
- mBuilder.mActions = new ArrayList<>();
- RemoteViews remoteViews = makeBigContentView();
- mBuilder.mActions = mBuilder.mOriginalActions;
- mBuilder.mOriginalActions = null;
- return remoteViews;
- }
+ mBuilder.mOriginalActions = mBuilder.mActions;
+ mBuilder.mActions = new ArrayList<>();
+ RemoteViews remoteViews = makeBigContentView();
+ mBuilder.mActions = mBuilder.mOriginalActions;
+ mBuilder.mOriginalActions = null;
+ return remoteViews;
}
private Message findLatestIncomingMessage() {
- for (int i = mMessages.size() - 1; i >= 0; i--) {
- Message m = mMessages.get(i);
+ return findLatestIncomingMessage(mMessages);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static Message findLatestIncomingMessage(
+ List<Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ Message m = messages.get(i);
// Incoming messages have a non-empty sender.
if (!TextUtils.isEmpty(m.mSender)) {
return m;
}
}
- if (!mMessages.isEmpty()) {
+ if (!messages.isEmpty()) {
// No incoming messages, fall back to outgoing message
- return mMessages.get(mMessages.size() - 1);
+ return messages.get(messages.size() - 1);
}
return null;
}
@@ -6115,118 +6121,82 @@
*/
@Override
public RemoteViews makeBigContentView() {
- CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
- boolean hasTitle = !TextUtils.isEmpty(title);
-
- if (mMessages.size() == 1) {
- // Special case for a single message: Use the big text style
- // so the collapsed and expanded versions match nicely.
- CharSequence bigTitle;
- CharSequence text;
- if (hasTitle) {
- bigTitle = title;
- text = makeMessageLine(mMessages.get(0), mBuilder);
- } else {
- bigTitle = mMessages.get(0).mSender;
- text = mMessages.get(0).mText;
- }
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigTextLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
- BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
- return contentView;
+ boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+ if (isOneToOne) {
+ // Let's add the conversationTitle in case we didn't have one before and all
+ // messages are from the same sender
+ conversationTitle = createConversationTitleFromMessages();
+ } else if (hasOnlyWhiteSpaceSenders()) {
+ isOneToOne = true;
}
-
+ boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
- int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
- R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
- // Make sure all rows are gone in case we reuse a view.
- for (int rowId : rowIds) {
- contentView.setViewVisibility(rowId, View.GONE);
- }
-
- int i=0;
- contentView.setViewLayoutMarginBottomDimen(R.id.line1,
- hasTitle ? R.dimen.notification_messaging_spacing : 0);
- contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
- !mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
- int contractedChildId = View.NO_ID;
- Message contractedMessage = findLatestIncomingMessage();
- int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
- - (rowIds.length - mMessages.size()));
- while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
- Message m = mHistoricMessages.get(firstHistoricMessage + i);
- int rowId = rowIds[i];
-
- contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
- }
-
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
- int rowId = rowIds[i];
-
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, mBuilder.processTextSpans(
- makeMessageLine(m, mBuilder)));
- mBuilder.setTextViewColorSecondary(contentView, rowId);
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
- }
- // Clear the remaining views for reapply. Ensures that historic message views can
- // reliably be identified as being GONE and having non-null text.
- while (i < rowIds.length) {
- int rowId = rowIds[i];
- contentView.setTextViewText(rowId, null);
- i++;
- }
-
- // Record this here to allow transformation between the contracted and expanded views.
- contentView.setInt(R.id.notification_messaging, "setContractedChildId",
- contractedChildId);
+ mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+ .hideLargeIcon(isOneToOne).alwaysShowReply(true));
+ addExtras(mBuilder.mN.extras);
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.resolveContrastColor());
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+ isOneToOne);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
return contentView;
}
- private CharSequence makeMessageLine(Message m, Builder builder) {
- BidiFormatter bidi = BidiFormatter.getInstance();
- SpannableStringBuilder sb = new SpannableStringBuilder();
- boolean colorize = builder.isColorized();
- TextAppearanceSpan colorSpan;
- CharSequence messageName;
- if (TextUtils.isEmpty(m.mSender)) {
- CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
- sb.append(bidi.unicodeWrap(replyName),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : mBuilder.resolveContrastColor()),
- 0 /* flags */);
- } else {
- sb.append(bidi.unicodeWrap(m.mSender),
- makeFontColorSpan(colorize
- ? builder.getPrimaryTextColor()
- : Color.BLACK),
- 0 /* flags */);
+ private boolean hasOnlyWhiteSpaceSenders() {
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (!isWhiteSpace(sender)) {
+ return false;
+ }
}
- CharSequence text = m.mText == null ? "" : m.mText;
- sb.append(" ").append(bidi.unicodeWrap(text));
- return sb;
+ return true;
+ }
+
+ private boolean isWhiteSpace(CharSequence sender) {
+ if (TextUtils.isEmpty(sender)) {
+ return true;
+ }
+ if (sender.toString().matches("^\\s*$")) {
+ return true;
+ }
+ // Let's check if we only have 0 whitespace chars. Some apps did this as a workaround
+ // For the presentation that we had.
+ for (int i = 0; i < sender.length(); i++) {
+ char c = sender.charAt(i);
+ if (c != '\u200B') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private CharSequence createConversationTitleFromMessages() {
+ ArraySet<CharSequence> names = new ArraySet<>();
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (sender != null) {
+ names.add(sender);
+ }
+ }
+ SpannableStringBuilder title = new SpannableStringBuilder();
+ int size = names.size();
+ for (int i = 0; i < size; i++) {
+ CharSequence name = names.valueAt(i);
+ if (!TextUtils.isEmpty(title)) {
+ title.append(", ");
+ }
+ title.append(BidiFormatter.getInstance().unicodeWrap(name));
+ }
+ return title;
}
/**
@@ -6234,19 +6204,9 @@
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
- if (increasedHeight) {
- return makeBigContentView();
- }
- Message m = findLatestIncomingMessage();
- CharSequence title = mConversationTitle != null
- ? mConversationTitle
- : (m == null) ? null : m.mSender;
- CharSequence text = (m == null)
- ? null
- : mConversationTitle != null ? makeMessageLine(m, mBuilder) : m.mText;
-
- return mBuilder.applyStandardTemplateWithActions(mBuilder.getBigBaseLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(text));
+ RemoteViews remoteViews = makeBigContentView();
+ remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
+ return remoteViews;
}
private static TextAppearanceSpan makeFontColorSpan(int color) {
@@ -6394,7 +6354,15 @@
return bundles;
}
- static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ /**
+ * @return A list of messages read from the bundles.
+ *
+ * @hide
+ */
+ public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ if (bundles == null) {
+ return new ArrayList<>();
+ }
List<Message> messages = new ArrayList<>(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8455,8 @@
boolean ambient = false;
CharSequence title;
CharSequence text;
+ boolean hideLargeIcon;
+ public boolean alwaysShowReply;
final StandardTemplateParams reset() {
hasProgress = true;
@@ -8511,6 +8481,16 @@
return this;
}
+ final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+ this.alwaysShowReply = alwaysShowReply;
+ return this;
+ }
+
+ final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+ this.hideLargeIcon = hideLargeIcon;
+ return this;
+ }
+
final StandardTemplateParams ambient(boolean ambient) {
Preconditions.checkState(title == null && text == null, "must set ambient before text");
this.ambient = ambient;
@@ -8527,7 +8507,6 @@
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text, ambient);
-
return this;
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index d7ecc81..8071e8b 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -619,6 +619,35 @@
*/
public static final int NETID_UNSET = 0;
+ /**
+ * Private DNS Mode values.
+ *
+ * The "private_dns_mode" global setting stores a String value which is
+ * expected to be one of the following.
+ */
+
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OFF = "off";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_OPPORTUNISTIC = "opportunistic";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = "hostname";
+ /**
+ * The default Private DNS mode.
+ *
+ * This may change from release to release or may become dependent upon
+ * the capabilities of the underlying platform.
+ *
+ * @hide
+ */
+ public static final String PRIVATE_DNS_DEFAULT_MODE = PRIVATE_DNS_MODE_OPPORTUNISTIC;
+
private final IConnectivityManager mService;
/**
* A kludge to facilitate static access where a Context pointer isn't available, like in the
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2e62eb6..2acf36f 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -16,11 +16,14 @@
package android.os;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
import android.content.Context;
import android.util.Log;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.TypedProperties;
import dalvik.system.VMDebug;
@@ -2347,4 +2350,24 @@
public static String getCaller() {
return getCaller(Thread.currentThread().getStackTrace(), 0);
}
+
+ /**
+ * Attach a library as a jvmti agent to the current runtime.
+ *
+ * @param library library containing the agent
+ * @param options options passed to the agent
+ *
+ * @throws IOException If the agent could not be attached
+ */
+ public static void attachJvmtiAgent(@NonNull String library, @Nullable String options)
+ throws IOException {
+ Preconditions.checkNotNull(library);
+ Preconditions.checkArgument(!library.contains("="));
+
+ if (options == null) {
+ VMDebug.attachAgent(library);
+ } else {
+ VMDebug.attachAgent(library + "=" + options);
+ }
+ }
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c2cf3967..10adb5a 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -2020,8 +2020,6 @@
@Deprecated
static native void closeFileDescriptor(FileDescriptor desc) throws IOException;
- static native void clearFileDescriptor(FileDescriptor desc);
-
/**
* Read a byte value from the parcel at the current dataPosition().
*/
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 7f588ad..7556f09 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -683,7 +683,7 @@
throw new IllegalStateException("Already closed");
}
final int fd = getFd();
- Parcel.clearFileDescriptor(mFd);
+ mFd.setInt$(-1);
writeCommStatusAndClose(Status.DETACHED, null);
mClosed = true;
mGuard.close();
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 53d87ff..d5820b6 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -29,6 +29,8 @@
import android.content.pm.ApplicationInfo;
import android.net.TrafficStats;
import android.net.Uri;
+import android.os.StrictMode.ThreadPolicy;
+import android.os.StrictMode.VmPolicy;
import android.os.strictmode.CleartextNetworkViolation;
import android.os.strictmode.ContentUriWithoutPermissionViolation;
import android.os.strictmode.CustomViolation;
@@ -54,6 +56,7 @@
import android.view.IWindowManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.os.RuntimeInit;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.HexDump;
@@ -1533,7 +1536,6 @@
if (violationMaskSubset != 0) {
violationMaskSubset |= info.getViolationBit();
- final int savedPolicyMask = getThreadPolicyMask();
final boolean justDropBox = (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DROPBOX;
if (justDropBox) {
@@ -1544,29 +1546,8 @@
// isn't always super fast, despite the implementation
// in the ActivityManager trying to be mostly async.
dropboxViolationAsync(violationMaskSubset, info);
- return;
- }
-
- // Normal synchronous call to the ActivityManager.
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
+ } else {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
}
@@ -1598,28 +1579,39 @@
if (LOG_V) Log.d(TAG, "Dropboxing async; in-flight=" + outstanding);
- new Thread("callActivityManagerForStrictModeDropbox") {
- public void run() {
- Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
- try {
- IActivityManager am = ActivityManager.getService();
- if (am == null) {
- Log.d(TAG, "No activity manager; failed to Dropbox violation.");
- } else {
- am.handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- }
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException handling StrictMode violation", e);
- }
- }
- int outstanding = sDropboxCallsInFlight.decrementAndGet();
- if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstanding);
+ BackgroundThread.getHandler().post(() -> {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ int outstandingInner = sDropboxCallsInFlight.decrementAndGet();
+ if (LOG_V) Log.d(TAG, "Dropbox complete; in-flight=" + outstandingInner);
+ });
+ }
+
+ private static void handleApplicationStrictModeViolation(int violationMaskSubset,
+ ViolationInfo info) {
+ final int oldMask = getThreadPolicyMask();
+ try {
+ // First, remove any policy before we call into the Activity Manager,
+ // otherwise we'll infinite recurse as we try to log policy violations
+ // to disk, thus violating policy, thus requiring logging, etc...
+ // We restore the current policy below, in the finally block.
+ setThreadPolicyMask(0);
+
+ IActivityManager am = ActivityManager.getService();
+ if (am == null) {
+ Log.w(TAG, "No activity manager; failed to Dropbox violation.");
+ } else {
+ am.handleApplicationStrictModeViolation(
+ RuntimeInit.getApplicationObject(), violationMaskSubset, info);
}
- }.start();
+ } catch (RemoteException e) {
+ if (e instanceof DeadObjectException) {
+ // System process is dead; ignore
+ } else {
+ Log.e(TAG, "RemoteException handling StrictMode violation", e);
+ }
+ } finally {
+ setThreadPolicyMask(oldMask);
+ }
}
private static class AndroidCloseGuardReporter implements CloseGuard.Reporter {
@@ -1908,31 +1900,7 @@
}
if (penaltyDropbox && lastViolationTime == 0) {
- // The violationMask, passed to ActivityManager, is a
- // subset of the original StrictMode policy bitmask, with
- // only the bit violated and penalty bits to be executed
- // by the ActivityManagerService remaining set.
- final int savedPolicyMask = getThreadPolicyMask();
- try {
- // First, remove any policy before we call into the Activity Manager,
- // otherwise we'll infinite recurse as we try to log policy violations
- // to disk, thus violating policy, thus requiring logging, etc...
- // We restore the current policy below, in the finally block.
- setThreadPolicyMask(0);
-
- ActivityManager.getService()
- .handleApplicationStrictModeViolation(
- RuntimeInit.getApplicationObject(), violationMaskSubset, info);
- } catch (RemoteException e) {
- if (e instanceof DeadObjectException) {
- // System process is dead; ignore
- } else {
- Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
- }
- } finally {
- // Restore the policy.
- setThreadPolicyMask(savedPolicyMask);
- }
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
}
if (penaltyDeath) {
diff --git a/core/java/android/os/storage/IStorageManager.aidl b/core/java/android/os/storage/IStorageManager.aidl
index e865ed1..0b76eec 100644
--- a/core/java/android/os/storage/IStorageManager.aidl
+++ b/core/java/android/os/storage/IStorageManager.aidl
@@ -17,6 +17,7 @@
package android.os.storage;
import android.content.pm.IPackageMoveObserver;
+import android.os.IVoldTaskListener;
import android.os.ParcelFileDescriptor;
import android.os.storage.DiskInfo;
import android.os.storage.IStorageEventListener;
@@ -165,7 +166,7 @@
void forgetAllVolumes() = 56;
String getPrimaryStorageUuid() = 57;
void setPrimaryStorageUuid(in String volumeUuid, IPackageMoveObserver callback) = 58;
- long benchmark(in String volId) = 59;
+ void benchmark(in String volId, IVoldTaskListener listener) = 59;
void setDebugFlags(int flags, int mask) = 60;
void createUserKey(int userId, int serialNumber, boolean ephemeral) = 61;
void destroyUserKey(int userId) = 62;
@@ -177,7 +178,7 @@
boolean isConvertibleToFBE() = 68;
void addUserKeyAuth(int userId, int serialNumber, in byte[] token, in byte[] secret) = 70;
void fixateNewestUserKeyAuth(int userId) = 71;
- void fstrim(int flags) = 72;
+ void fstrim(int flags, IVoldTaskListener listener) = 72;
AppFuseMount mountProxyFileDescriptorBridge() = 73;
ParcelFileDescriptor openProxyFileDescriptor(int mountPointId, int fileId, int mode) = 74;
long getCacheQuotaBytes(String volumeUuid, int uid) = 75;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 6594cd0..0b007dd 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -42,10 +42,12 @@
import android.os.FileUtils;
import android.os.Handler;
import android.os.IVold;
+import android.os.IVoldTaskListener;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
+import android.os.PersistableBundle;
import android.os.ProxyFileDescriptorCallback;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -87,7 +89,9 @@
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -884,9 +888,32 @@
}
/** {@hide} */
+ @Deprecated
public long benchmark(String volId) {
+ final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
+ benchmark(volId, new IVoldTaskListener.Stub() {
+ @Override
+ public void onStatus(int status, PersistableBundle extras) {
+ // Ignored
+ }
+
+ @Override
+ public void onFinished(int status, PersistableBundle extras) {
+ result.complete(extras);
+ }
+ });
try {
- return mStorageManager.benchmark(volId);
+ // Convert ms to ns
+ return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE) * 1000000;
+ } catch (Exception e) {
+ return Long.MAX_VALUE;
+ }
+ }
+
+ /** {@hide} */
+ public void benchmark(String volId, IVoldTaskListener listener) {
+ try {
+ mStorageManager.benchmark(volId, listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 433878e..2501f22 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9297,11 +9297,20 @@
public static final String DEFAULT_DNS_SERVER = "default_dns_server";
/**
- * Whether to disable DNS over TLS (boolean)
+ * The requested Private DNS mode (string), and an accompanying specifier (string).
+ *
+ * Currently, the specifier holds the chosen provider name when the mode requests
+ * a specific provider. It may be used to store the provider name even when the
+ * mode changes so that temporarily disabling and re-enabling the specific
+ * provider mode does not necessitate retyping the provider hostname.
*
* @hide
*/
- public static final String DNS_TLS_DISABLED = "dns_tls_disabled";
+ public static final String PRIVATE_DNS_MODE = "private_dns_mode";
+ /**
+ * @hide
+ */
+ public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
/** {@hide} */
public static final String
@@ -10387,7 +10396,9 @@
DOCK_AUDIO_MEDIA_ENABLED,
ENCODED_SURROUND_OUTPUT,
LOW_POWER_MODE_TRIGGER_LEVEL,
- BLUETOOTH_ON
+ BLUETOOTH_ON,
+ PRIVATE_DNS_MODE,
+ PRIVATE_DNS_SPECIFIER
};
/** @hide */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 6a15ade..2a245d0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -680,8 +680,8 @@
*
* @return The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @see #setDozeScreenState
* @hide For use by system UI components only.
@@ -700,12 +700,18 @@
* perform transitions between states while dozing to conserve power and
* achieve various effects.
* </p><p>
- * It is recommended that the state be set to {@link Display#STATE_DOZE_SUSPEND}
- * once the dream has completely finished drawing and before it releases its wakelock
- * to allow the display hardware to be fully suspended. While suspended, the
- * display will preserve its on-screen contents or hand off control to dedicated
- * doze hardware if the devices supports it. If the doze suspend state is
- * used, the dream must make sure to set the mode back
+ * Some devices will have dedicated hardware ("Sidekick") to animate
+ * the display content while the CPU sleeps. If the dream and the hardware support
+ * this, {@link Display#STATE_ON_SUSPEND} or {@link Display#STATE_DOZE_SUSPEND}
+ * will switch control to the Sidekick.
+ * </p><p>
+ * If not using Sidekick, it is recommended that the state be set to
+ * {@link Display#STATE_DOZE_SUSPEND} once the dream has completely
+ * finished drawing and before it releases its wakelock
+ * to allow the display hardware to be fully suspended. While suspended,
+ * the display will preserve its on-screen contents.
+ * </p><p>
+ * If the doze suspend state is used, the dream must make sure to set the mode back
* to {@link Display#STATE_DOZE} or {@link Display#STATE_ON} before drawing again
* since the display updates may be ignored and not seen by the user otherwise.
* </p><p>
@@ -716,8 +722,8 @@
*
* @param state The screen state to use while dozing, such as {@link Display#STATE_ON},
* {@link Display#STATE_DOZE}, {@link Display#STATE_DOZE_SUSPEND},
- * or {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN} for the default
- * behavior.
+ * {@link Display#STATE_ON_SUSPEND}, {@link Display#STATE_OFF}, or {@link Display#STATE_UNKNOWN}
+ * for the default behavior.
*
* @hide For use by system UI components only.
*/
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index e7c3f92..6a44cdb 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -294,11 +294,10 @@
/**
* Display state: The display is dozing in a suspended low power state; it is still
- * on but is optimized for showing static system-provided content while the device
- * is non-interactive. This mode may be used to conserve even more power by allowing
- * the hardware to stop applying frame buffer updates from the graphics subsystem or
- * to take over the display and manage it autonomously to implement low power always-on
- * display functionality.
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
*
* @see #getState
* @see android.os.PowerManager#isInteractive
@@ -313,6 +312,18 @@
*/
public static final int STATE_VR = 5;
+ /**
+ * Display state: The display is in a suspended full power state; it is still
+ * on but the CPU is not updating it. This may be used in one of two ways: to show
+ * static system-provided content while the device is non-interactive, or to allow
+ * a "Sidekick" compute resource to update the display. For this reason, the
+ * CPU must not control the display in this mode.
+ *
+ * @see #getState
+ * @see android.os.PowerManager#isInteractive
+ */
+ public static final int STATE_ON_SUSPEND = 6;
+
/* The color mode constants defined below must be kept in sync with the ones in
* system/core/include/system/graphics-base.h */
@@ -994,7 +1005,7 @@
* Gets the state of the display, such as whether it is on or off.
*
* @return The state of the display: one of {@link #STATE_OFF}, {@link #STATE_ON},
- * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, or
+ * {@link #STATE_DOZE}, {@link #STATE_DOZE_SUSPEND}, {@link #STATE_ON_SUSPEND}, or
* {@link #STATE_UNKNOWN}.
*/
public int getState() {
@@ -1113,6 +1124,8 @@
return "DOZE_SUSPEND";
case STATE_VR:
return "VR";
+ case STATE_ON_SUSPEND:
+ return "ON_SUSPEND";
default:
return Integer.toString(state);
}
@@ -1120,11 +1133,11 @@
/**
* Returns true if display updates may be suspended while in the specified
- * display power state.
+ * display power state. In SUSPEND states, updates are absolutely forbidden.
* @hide
*/
public static boolean isSuspendedState(int state) {
- return state == STATE_OFF || state == STATE_DOZE_SUSPEND;
+ return state == STATE_OFF || state == STATE_DOZE_SUSPEND || state == STATE_ON_SUSPEND;
}
/**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cd84147..5641009 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -295,6 +295,12 @@
public static final int POWER_MODE_DOZE_SUSPEND = 3;
/**
+ * Display power mode on: used while putting the screen into a suspended
+ * full power mode. Use only with {@link SurfaceControl#setDisplayPowerMode}.
+ */
+ public static final int POWER_MODE_ON_SUSPEND = 4;
+
+ /**
* A value for windowType used to indicate that the window should be omitted from screenshots
* and display mirroring. A temporary workaround until we express such things with
* the hierarchy.
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 0535ebe..2ec64a5 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9626,7 +9626,8 @@
}
public boolean isScreenOn(int state) {
- return state == Display.STATE_ON || state == Display.STATE_VR;
+ return state == Display.STATE_ON || state == Display.STATE_VR
+ || state == Display.STATE_ON_SUSPEND;
}
public boolean isScreenOff(int state) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 7870333..09f7282 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@
}
return false;
}
-
- public int getLayoutHeight() {
- return getLayout().getHeight();
- }
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 0000000..792f921
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+ private static Pools.SimplePool<MessagingGroup> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private MessagingLinearLayout mMessageContainer;
+ private ImageFloatingTextView mSenderName;
+ private ImageView mAvatarView;
+ private String mAvatarSymbol = "";
+ private int mLayoutColor;
+ private CharSequence mAvatarName = "";
+ private Icon mAvatarIcon;
+ private ColorFilter mMessageBackgroundFilter;
+ private int mTextColor;
+ private List<MessagingMessage> mMessages;
+ private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
+ private boolean mFirstLayout;
+ private boolean mIsHidingAnimated;
+
+ public MessagingGroup(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessageContainer = findViewById(R.id.group_message_container);
+ mSenderName = findViewById(R.id.message_name);
+ mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ mAvatarView = findViewById(R.id.message_icon);
+ }
+
+ public void setSender(CharSequence sender) {
+ if (sender == null) {
+ mAvatarView.setVisibility(GONE);
+ mSenderName.setVisibility(GONE);
+ setGravity(Gravity.END);
+ mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+ PorterDuff.Mode.SRC_ATOP);
+ mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+ : Color.WHITE;
+ } else {
+ mSenderName.setText(sender);
+ mAvatarView.setVisibility(VISIBLE);
+ mSenderName.setVisibility(VISIBLE);
+ setGravity(Gravity.START);
+ mMessageBackgroundFilter = null;
+ mTextColor = getNormalTextColor();
+ }
+ }
+
+ private int getNormalTextColor() {
+ return mContext.getColor(R.color.notification_primary_text_color_light);
+ }
+
+ public void setAvatar(Icon icon) {
+ mAvatarIcon = icon;
+ mAvatarView.setImageIcon(icon);
+ mAvatarSymbol = "";
+ mLayoutColor = 0;
+ mAvatarName = "";
+ }
+
+ static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+ MessagingGroup createdGroup = sInstancePool.acquire();
+ if (createdGroup == null) {
+ createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_group, layout,
+ false);
+ createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+ }
+ layout.addView(createdGroup);
+ return createdGroup;
+ }
+
+ public void removeMessage(MessagingMessage messagingMessage) {
+ mMessageContainer.removeView(messagingMessage);
+ Runnable recycleRunnable = () -> {
+ mMessageContainer.removeTransientView(messagingMessage);
+ messagingMessage.recycle();
+ if (mMessageContainer.getChildCount() == 0
+ && mMessageContainer.getTransientViewCount() == 0) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(MessagingGroup.this);
+ }
+ setAvatar(null);
+ mAvatarView.setAlpha(1.0f);
+ mAvatarView.setTranslationY(0.0f);
+ mSenderName.setAlpha(1.0f);
+ mSenderName.setTranslationY(0.0f);
+ sInstancePool.release(MessagingGroup.this);
+ }
+ };
+ if (isShown()) {
+ mMessageContainer.addTransientView(messagingMessage, 0);
+ performRemoveAnimation(messagingMessage, recycleRunnable);
+ if (mMessageContainer.getChildCount() == 0) {
+ removeGroupAnimated(null);
+ }
+ } else {
+ recycleRunnable.run();
+ }
+
+ }
+
+ private void removeGroupAnimated(Runnable endAction) {
+ MessagingPropertyAnimator.fadeOut(mAvatarView, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ MessagingPropertyAnimator.fadeOut(mSenderName, null);
+ MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ boolean endActionTriggered = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide
+ && !((MessagingLinearLayout.LayoutParams) lp).visibleBefore) {
+ continue;
+ }
+ Runnable childEndAction = endActionTriggered ? null : endAction;
+ performRemoveAnimation(child, childEndAction);
+ endActionTriggered = true;
+ }
+ if (!endActionTriggered && endAction != null) {
+ endAction.run();
+ }
+ }
+
+ public void performRemoveAnimation(View message,
+ Runnable recycleRunnable) {
+ MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+ MessagingPropertyAnimator.startLocalTranslationTo(message,
+ (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+ }
+
+ public CharSequence getSenderName() {
+ return mSenderName.getText();
+ }
+
+ public void setSenderVisible(boolean visible) {
+ mSenderName.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean hasNormal = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+ if (type == MEASURED_TOO_SMALL) {
+ if (hasNormal) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_TOO_SMALL;
+ }
+ } else if (type == MEASURED_SHORTENED) {
+ return MEASURED_SHORTENED;
+ } else {
+ hasNormal = true;
+ }
+ }
+ }
+ return MEASURED_NORMAL;
+ }
+
+ @Override
+ public int getConsumedLines() {
+ int result = 0;
+ for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+ }
+ }
+ // A group is usually taking up quite some space with the padding and the name, let's add 1
+ return result + 1;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ mMessageContainer.setMaxDisplayedLines(lines);
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ removeGroupAnimated(() -> setIsHidingAnimated(false));
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+ && layoutColor == mLayoutColor) {
+ return mAvatarIcon;
+ }
+ return null;
+ }
+
+ public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+ || layoutColor != mLayoutColor) {
+ setAvatar(cachedIcon);
+ mAvatarSymbol = avatarSymbol;
+ mLayoutColor = layoutColor;
+ mAvatarName = avatarName;
+ }
+ }
+
+ public void setLayoutColor(int layoutColor) {
+ mLayoutColor = layoutColor;
+ }
+
+ public void setMessages(List<MessagingMessage> group) {
+ // Let's now make sure all children are added and in the correct order
+ for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+ MessagingMessage message = group.get(messageIndex);
+ if (message.getGroup() != this) {
+ message.setMessagingGroup(this);
+ ViewParent parent = mMessageContainer.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(message);
+ }
+ mMessageContainer.addView(message, messageIndex);
+ mAddedMessages.add(message);
+ }
+ if (messageIndex != mMessageContainer.indexOfChild(message)) {
+ mMessageContainer.removeView(message);
+ mMessageContainer.addView(message, messageIndex);
+ }
+ // Let's make sure the message color is correct
+ Drawable targetDrawable = message.getBackground();
+
+ if (targetDrawable != null) {
+ targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+ }
+ message.setTextColor(mTextColor);
+ }
+ mMessages = group;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedMessages.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingMessage message : mAddedMessages) {
+ if (!message.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(message);
+ if (!mFirstLayout) {
+ MessagingPropertyAnimator.startLocalTranslationFrom(message,
+ message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+ }
+ }
+ mAddedMessages.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ mFirstLayout = false;
+ }
+
+ /**
+ * Calculates the group compatibility between this and another group.
+ *
+ * @param otherGroup the other group to compare it with
+ *
+ * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
+ * they match.
+ */
+ public int calculateGroupCompatibility(MessagingGroup otherGroup) {
+ if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
+ int result = 1;
+ for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
+ MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
+ MessagingMessage otherMessage = otherGroup.mMessages.get(
+ otherGroup.mMessages.size() - 1 - i);
+ if (!ownMessage.sameAs(otherMessage)) {
+ return result;
+ }
+ result++;
+ }
+ return result;
+ }
+ return 0;
+ }
+
+ public View getSender() {
+ return mSenderName;
+ }
+
+ public View getAvatar() {
+ return mAvatarView;
+ }
+
+ public MessagingLinearLayout getMessageContainer() {
+ return mMessageContainer;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 0000000..2acdc01
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+ = MessagingMessage::removeMessage;
+ public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
+ public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
+ = new MessagingPropertyAnimator();
+ private List<MessagingMessage> mMessages = new ArrayList<>();
+ private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+ private MessagingLinearLayout mMessagingLinearLayout;
+ private View mContractedMessage;
+ private boolean mShowHistoricMessages;
+ private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+ private TextView mTitleView;
+ private int mLayoutColor;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+ private CharSequence mConversationTitle;
+ private Icon mLargeIcon;
+ private boolean mIsOneToOne;
+ private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
+
+ public MessagingLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessagingLinearLayout = findViewById(R.id.notification_messaging);
+ mMessagingLinearLayout.setMessagingLayout(this);
+ // We still want to clip, but only on the top, since views can temporarily out of bounds
+ // during transitions.
+ DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+ Rect rect = new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels);
+ mMessagingLinearLayout.setClipBounds(rect);
+ mTitleView = findViewById(R.id.title);
+ mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon icon) {
+ mLargeIcon = icon;
+ }
+
+ @RemotableViewMethod
+ public void setData(Bundle extras) {
+ Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ List<Notification.MessagingStyle.Message> newMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ List<Notification.MessagingStyle.Message> newHistoricMessages
+ = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ mConversationTitle = null;
+ TextView headerText = findViewById(R.id.header_text);
+ if (headerText != null) {
+ mConversationTitle = headerText.getText();
+ }
+ bind(newMessages, newHistoricMessages);
+ }
+
+ private void bind(List<Notification.MessagingStyle.Message> newMessages,
+ List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+ List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ addMessagesToGroups(historicMessages, messages);
+
+ // Let's remove the remaining messages
+ mMessages.forEach(REMOVE_MESSAGE);
+ mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+ mMessages = messages;
+ mHistoricMessages = historicMessages;
+
+ updateContractedMessage();
+ updateHistoricMessageVisibility();
+ updateTitleAndNamesDisplay();
+ }
+
+ private void updateTitleAndNamesDisplay() {
+ ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
+ ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ boolean visible = !mIsOneToOne;
+ group.setSenderVisible(visible);
+ if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+ char c = senderName.charAt(0);
+ if (uniqueCharacters.containsKey(c)) {
+ // this character was already used, lets make it more unique. We first need to
+ // resolve the existing character if it exists
+ CharSequence existingName = uniqueCharacters.get(c);
+ if (existingName != null) {
+ uniqueNames.put(existingName, findNameSplit((String) existingName));
+ uniqueCharacters.put(c, null);
+ }
+ uniqueNames.put(senderName, findNameSplit((String) senderName));
+ } else {
+ uniqueNames.put(senderName, Character.toString(c));
+ uniqueCharacters.put(c, senderName);
+ }
+ }
+ }
+
+ // Now that we have the correct symbols, let's look what we have cached
+ ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+ continue;
+ }
+ String symbol = uniqueNames.get(senderName);
+ Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
+ symbol, mLayoutColor);
+ if (cachedIcon != null) {
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ }
+
+ for (int i = 0; i < mGroups.size(); i++) {
+ // Let's now set the avatars
+ MessagingGroup group = mGroups.get(i);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ if (mIsOneToOne && mLargeIcon != null) {
+ group.setAvatar(mLargeIcon);
+ } else {
+ Icon cachedIcon = cachedAvatars.get(senderName);
+ if (cachedIcon == null) {
+ cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ cachedAvatars.put(senderName, cachedIcon);
+ }
+ group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
+ mLayoutColor);
+ }
+ }
+ }
+
+ public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+ Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ float radius = mAvatarSize / 2.0f;
+ int color = findColor(senderName, layoutColor);
+ mPaint.setColor(color);
+ canvas.drawCircle(radius, radius, radius, mPaint);
+ boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
+ mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
+ mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.75f : mAvatarSize * 0.4f);
+ int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2)) ;
+ canvas.drawText(symbol, radius, yPos, mTextPaint);
+ return Icon.createWithBitmap(bitmap);
+ }
+
+ private int findColor(CharSequence senderName, int layoutColor) {
+ double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+ float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
+
+ // we need to offset the range if the luminance is too close to the borders
+ shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
+ shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
+ return NotificationColorUtil.getShiftedColor(layoutColor,
+ (int) (shift * COLOR_SHIFT_AMOUNT));
+ }
+
+ private String findNameSplit(String existingName) {
+ String[] split = existingName.split(" ");
+ if (split.length > 1) {
+ return Character.toString(split[0].charAt(0))
+ + Character.toString(split[1].charAt(0));
+ }
+ return existingName.substring(0, 1);
+ }
+
+ @RemotableViewMethod
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ @RemotableViewMethod
+ public void setIsOneToOne(boolean oneToOne) {
+ mIsOneToOne = oneToOne;
+ }
+
+ private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
+ // Let's first find our groups!
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<CharSequence> senders = new ArrayList<>();
+
+ // Lets first find the groups
+ findGroups(historicMessages, messages, groups, senders);
+
+ // Let's now create the views and reorder them accordingly
+ createGroupViews(groups, senders);
+ }
+
+ private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+ mGroups.clear();
+ for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
+ List<MessagingMessage> group = groups.get(groupIndex);
+ MessagingGroup newGroup = null;
+ // we'll just take the first group that exists or create one there is none
+ for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
+ MessagingMessage message = group.get(messageIndex);
+ newGroup = message.getGroup();
+ if (newGroup != null) {
+ break;
+ }
+ }
+ if (newGroup == null) {
+ newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+ mAddedGroups.add(newGroup);
+ }
+ newGroup.setLayoutColor(mLayoutColor);
+ newGroup.setSender(senders.get(groupIndex));
+ mGroups.add(newGroup);
+
+ if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
+ mMessagingLinearLayout.removeView(newGroup);
+ mMessagingLinearLayout.addView(newGroup, groupIndex);
+ }
+ newGroup.setMessages(group);
+ }
+ }
+
+ private void findGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
+ List<CharSequence> senders) {
+ CharSequence currentSender = null;
+ List<MessagingMessage> currentGroup = null;
+ int histSize = historicMessages.size();
+ for (int i = 0; i < histSize + messages.size(); i++) {
+ MessagingMessage message;
+ if (i < histSize) {
+ message = historicMessages.get(i);
+ } else {
+ message = messages.get(i - histSize);
+ }
+ boolean isNewGroup = currentGroup == null;
+ CharSequence sender = message.getMessage().getSender();
+ isNewGroup |= !TextUtils.equals(sender, currentSender);
+ if (isNewGroup) {
+ currentGroup = new ArrayList<>();
+ groups.add(currentGroup);
+ senders.add(sender);
+ currentSender = sender;
+ }
+ currentGroup.add(message);
+ }
+ }
+
+ private void updateContractedMessage() {
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingMessage m = mMessages.get(i);
+ // Incoming messages have a non-empty sender.
+ if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+ mContractedMessage = m;
+ return;
+ }
+ }
+ if (!mMessages.isEmpty()) {
+ // No incoming messages, fall back to outgoing message
+ mContractedMessage = mMessages.get(mMessages.size() - 1);
+ return;
+ }
+ mContractedMessage = null;
+ }
+
+ /**
+ * Creates new messages, reusing existing ones if they are available.
+ *
+ * @param newMessages the messages to parse.
+ */
+ private List<MessagingMessage> createMessages(
+ List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
+ List<MessagingMessage> result = new ArrayList<>();;
+ for (int i = 0; i < newMessages.size(); i++) {
+ Notification.MessagingStyle.Message m = newMessages.get(i);
+ MessagingMessage message = findAndRemoveMatchingMessage(m);
+ if (message == null) {
+ message = MessagingMessage.createMessage(this, m);
+ message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
+ }
+ message.setIsHistoric(historic);
+ result.add(message);
+ }
+ return result;
+ }
+
+ private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
+ for (int i = 0; i < mMessages.size(); i++) {
+ MessagingMessage existing = mMessages.get(i);
+ if (existing.sameAs(m)) {
+ mMessages.remove(i);
+ return existing;
+ }
+ }
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ if (existing.sameAs(m)) {
+ mHistoricMessages.remove(i);
+ return existing;
+ }
+ }
+ return null;
+ }
+
+ public void showHistoricMessages(boolean show) {
+ mShowHistoricMessages = show;
+ updateHistoricMessageVisibility();
+ }
+
+ private void updateHistoricMessageVisibility() {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!mAddedGroups.isEmpty()) {
+ getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ for (MessagingGroup group : mAddedGroups) {
+ if (!group.isShown()) {
+ continue;
+ }
+ MessagingPropertyAnimator.fadeIn(group.getAvatar());
+ MessagingPropertyAnimator.fadeIn(group.getSender());
+ MessagingPropertyAnimator.startLocalTranslationFrom(group,
+ group.getHeight(), LINEAR_OUT_SLOW_IN);
+ }
+ mAddedGroups.clear();
+ getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+ }
+
+ public View getContractedMessage() {
+ return mContractedMessage;
+ }
+
+ public MessagingLinearLayout getMessagingLinearLayout() {
+ return mMessagingLinearLayout;
+ }
+
+ public ArrayList<MessagingGroup> getMessagingGroups() {
+ return mGroups;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 70473a0..f0ef370 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -36,28 +36,14 @@
@RemoteViews.RemoteView
public class MessagingLinearLayout extends ViewGroup {
- private static final int NOT_MEASURED_BEFORE = -1;
/**
* Spacing to be applied between views.
*/
private int mSpacing;
- /**
- * The maximum height allowed.
- */
- private int mMaxHeight;
+ private int mMaxDisplayedLines = Integer.MAX_VALUE;
- private int mIndentLines;
-
- /**
- * Id of the child that's also visible in the contracted layout.
- */
- private int mContractedChildId;
- /**
- * The last measured with in a layout pass if it was measured before or
- * {@link #NOT_MEASURED_BEFORE} if this is the first layout pass.
- */
- private int mLastMeasuredWidth = NOT_MEASURED_BEFORE;
+ private MessagingLayout mMessagingLayout;
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -79,7 +65,6 @@
a.recycle();
}
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -91,118 +76,67 @@
break;
}
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- boolean recalculateVisibility = mLastMeasuredWidth == NOT_MEASURED_BEFORE
- || getMeasuredHeight() != targetHeight
- || mLastMeasuredWidth != widthSize;
-
- final int count = getChildCount();
- if (recalculateVisibility) {
- // We only need to recalculate the view visibilities if the view wasn't measured already
- // in this pass, otherwise we may drop messages here already since we are measured
- // exactly with what we returned before, which was optimized already with the
- // line-indents.
- for (int i = 0; i < count; ++i) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- lp.hide = true;
- }
-
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
-
- // Starting from the bottom: we measure every view as if it were the only one. If it still
-
- // fits, we take it, otherwise we stop there.
- for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
- if (getChildAt(i).getVisibility() == GONE) {
- continue;
- }
- final View child = getChildAt(i);
- LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- ImageFloatingTextView textChild = null;
- if (child instanceof ImageFloatingTextView) {
- // Pretend we need the image padding for all views, we don't know which
- // one will end up needing to do this (might end up not using all the space,
- // but calculating this exactly would be more expensive).
- textChild = (ImageFloatingTextView) child;
- textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
- }
-
- int spacing = first ? 0 : mSpacing;
- measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
- - mPaddingTop - mPaddingBottom + spacing);
-
- final int childHeight = child.getMeasuredHeight();
- int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
- lp.bottomMargin + spacing);
- first = false;
- boolean measuredTooSmall = false;
- if (textChild != null) {
- measuredTooSmall = childHeight < textChild.getLayoutHeight()
- + textChild.getPaddingTop() + textChild.getPaddingBottom();
- }
-
- if (newHeight <= targetHeight && !measuredTooSmall) {
- totalHeight = newHeight;
- lp.hide = false;
- } else {
- break;
- }
- }
- }
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
- int imageLines = mIndentLines;
- // Need to redo the height because it may change due to changing indents.
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
- for (int i = 0; i < count; i++) {
+ final int count = getChildCount();
+ int totalHeight;
+ for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE || lp.hide) {
- continue;
- }
-
- if (child instanceof ImageFloatingTextView) {
- ImageFloatingTextView textChild = (ImageFloatingTextView) child;
- if (imageLines == 2 && textChild.getLineCount() > 2) {
- // HACK: If we need indent for two lines, and they're coming from the same
- // view, we need extra spacing to compensate for the lack of margins,
- // so add an extra line of indent.
- imageLines = 3;
- }
- boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
- if (changed || !recalculateVisibility) {
- final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
- lp.width);
- // we want to measure it at most as high as it is currently, otherwise we'll
- // drop later lines
- final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
- targetHeight - child.getMeasuredHeight(), lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
- }
- imageLines -= textChild.getLineCount();
- }
-
- measuredWidth = Math.max(measuredWidth,
- child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
- + mPaddingLeft + mPaddingRight);
- totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
- lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
- first = false;
+ lp.hide = true;
}
+ totalHeight = mPaddingTop + mPaddingBottom;
+ boolean first = true;
+ int linesRemaining = mMaxDisplayedLines;
+
+ // Starting from the bottom: we measure every view as if it were the only one. If it still
+ // fits, we take it, otherwise we stop there.
+ for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
+ if (getChildAt(i).getVisibility() == GONE) {
+ continue;
+ }
+ final View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ MessagingChild messagingChild = null;
+ if (child instanceof MessagingChild) {
+ messagingChild = (MessagingChild) child;
+ messagingChild.setMaxDisplayedLines(linesRemaining);
+ }
+ int spacing = first ? 0 : mSpacing;
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
+ - mPaddingTop - mPaddingBottom + spacing);
+
+ final int childHeight = child.getMeasuredHeight();
+ int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
+ lp.bottomMargin + spacing);
+ first = false;
+ int measureType = MessagingChild.MEASURED_NORMAL;
+ if (messagingChild != null) {
+ measureType = messagingChild.getMeasuredType();
+ linesRemaining -= messagingChild.getConsumedLines();
+ }
+ boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+ boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+ if (newHeight <= targetHeight && !isTooSmall) {
+ totalHeight = newHeight;
+ measuredWidth = Math.max(measuredWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+ + mPaddingLeft + mPaddingRight);
+ lp.hide = false;
+ if (isShortened || linesRemaining <= 0) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
setMeasuredDimension(
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
- resolveSize(Math.max(getSuggestedMinimumHeight(), totalHeight),
- heightMeasureSpec));
- mLastMeasuredWidth = widthSize;
+ Math.max(getSuggestedMinimumHeight(), totalHeight));
}
@Override
@@ -221,14 +155,23 @@
childTop = mPaddingTop;
boolean first = true;
-
+ final boolean shown = isShown();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE || lp.hide) {
+ if (child.getVisibility() == GONE) {
continue;
}
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (lp.hide) {
+ if (shown && lp.visibleBefore) {
+ messagingChild.hideAnimated();
+ }
+ lp.visibleBefore = false;
+ continue;
+ } else {
+ lp.visibleBefore = true;
+ }
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
@@ -251,14 +194,16 @@
first = false;
}
- mLastMeasuredWidth = NOT_MEASURED_BEFORE;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.hide) {
- return true;
+ MessagingChild messagingChild = (MessagingChild) child;
+ if (!messagingChild.isHidingAnimated()) {
+ return true;
+ }
}
return super.drawChild(canvas, child, drawingTime);
}
@@ -284,31 +229,37 @@
}
/**
- * Sets how many lines should be indented to avoid a floating image.
+ * Sets how many lines should be displayed at most
*/
@RemotableViewMethod
- public void setNumIndentLines(int numberLines) {
- mIndentLines = numberLines;
+ public void setMaxDisplayedLines(int numberLines) {
+ mMaxDisplayedLines = numberLines;
}
- /**
- * Set id of the child that's also visible in the contracted layout.
- */
- @RemotableViewMethod
- public void setContractedChildId(int contractedChildId) {
- mContractedChildId = contractedChildId;
+ public void setMessagingLayout(MessagingLayout layout) {
+ mMessagingLayout = layout;
}
- /**
- * Get id of the child that's also visible in the contracted layout.
- */
- public int getContractedChildId() {
- return mContractedChildId;
+ public MessagingLayout getMessagingLayout() {
+ return mMessagingLayout;
+ }
+
+ public interface MessagingChild {
+ int MEASURED_NORMAL = 0;
+ int MEASURED_SHORTENED = 1;
+ int MEASURED_TOO_SMALL = 2;
+
+ int getMeasuredType();
+ int getConsumedLines();
+ void setMaxDisplayedLines(int lines);
+ void hideAnimated();
+ boolean isHidingAnimated();
}
public static class LayoutParams extends MarginLayoutParams {
- boolean hide = false;
+ public boolean hide = false;
+ public boolean visibleBefore = false;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 0000000..f09621f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+ MessagingLinearLayout.MessagingChild {
+
+ private static Pools.SimplePool<MessagingMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private Notification.MessagingStyle.Message mMessage;
+ private MessagingGroup mGroup;
+ private boolean mIsHistoric;
+ private boolean mIsHidingAnimated;
+
+ public MessagingMessage(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void setMessage(Notification.MessagingStyle.Message message) {
+ mMessage = message;
+ setText(message.getText());
+ }
+
+ public Notification.MessagingStyle.Message getMessage() {
+ return mMessage;
+ }
+
+ boolean sameAs(Notification.MessagingStyle.Message message) {
+ if (!Objects.equals(message.getText(), mMessage.getText())) {
+ return false;
+ }
+ if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+ return false;
+ }
+ if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean sameAs(MessagingMessage message) {
+ return sameAs(message.getMessage());
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_message, messagingLinearLayout,
+ false);
+ }
+ createdMessage.setMessage(m);
+ return createdMessage;
+ }
+
+ public void removeMessage() {
+ mGroup.removeMessage(this);
+ }
+
+ public void recycle() {
+ mGroup = null;
+ mMessage = null;
+ setAlpha(1.0f);
+ setTranslationY(0);
+ sInstancePool.release(this);
+ }
+
+ public void setMessagingGroup(MessagingGroup group) {
+ mGroup = group;
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ public void setIsHistoric(boolean isHistoric) {
+ mIsHistoric = isHistoric;
+ }
+
+ public MessagingGroup getGroup() {
+ return mGroup;
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return MEASURED_TOO_SMALL;
+ }
+ if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public void hideAnimated() {
+ setIsHidingAnimated(true);
+ mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+ }
+
+ private void setIsHidingAnimated(boolean isHiding) {
+ ViewParent parent = getParent();
+ mIsHidingAnimated = isHiding;
+ invalidate();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).invalidate();
+ }
+ }
+
+ @Override
+ public boolean isHidingAnimated() {
+ return mIsHidingAnimated;
+ }
+
+ @Override
+ public void setMaxDisplayedLines(int lines) {
+ setMaxLines(lines);
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return getLineCount();
+ }
+
+ public int getLayoutHeight() {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return 0;
+ }
+ return layout.getHeight();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingPropertyAnimator.java b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
new file mode 100644
index 0000000..7c3ab7f
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingPropertyAnimator.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.util.IntProperty;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.internal.R;
+
+/**
+ * A listener that automatically starts animations when the layout bounds change.
+ */
+public class MessagingPropertyAnimator implements View.OnLayoutChangeListener {
+ static final long APPEAR_ANIMATION_LENGTH = 210;
+ private static final Interpolator ALPHA_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
+ public static final Interpolator ALPHA_OUT = new PathInterpolator(0f, 0f, 0.8f, 1f);
+ private static final int TAG_LOCAL_TRANSLATION_ANIMATOR = R.id.tag_local_translation_y_animator;
+ private static final int TAG_LOCAL_TRANSLATION_Y = R.id.tag_local_translation_y;
+ private static final int TAG_LAYOUT_TOP = R.id.tag_layout_top;
+ private static final int TAG_ALPHA_ANIMATOR = R.id.tag_alpha_animator;
+ private static final ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS =
+ view -> view.getId() == com.android.internal.R.id.notification_messaging;
+ private static final IntProperty<View> LOCAL_TRANSLATION_Y =
+ new IntProperty<View>("localTranslationY") {
+ @Override
+ public void setValue(View object, int value) {
+ setLocalTranslationY(object, value);
+ }
+
+ @Override
+ public Integer get(View object) {
+ return getLocalTranslationY(object);
+ }
+ };
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ int oldHeight = oldBottom - oldTop;
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop != null) {
+ oldTop = layoutTop;
+ }
+ int topChange = oldTop - top;
+ if (oldHeight == 0 || topChange == 0 || !v.isShown() || isGone(v)) {
+ // First layout
+ return;
+ }
+ if (layoutTop != null) {
+ v.setTagInternal(TAG_LAYOUT_TOP, top);
+ }
+ int newHeight = bottom - top;
+ int heightDifference = oldHeight - newHeight;
+ // Only add the difference if the height changes and it's getting smaller
+ heightDifference = Math.max(heightDifference, 0);
+ startLocalTranslationFrom(v, topChange + heightDifference + getLocalTranslationY(v));
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation) {
+ startLocalTranslationFrom(v, startTranslation, MessagingLayout.FAST_OUT_SLOW_IN);
+ }
+
+ public static void startLocalTranslationFrom(View v, int startTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, startTranslation, 0, interpolator);
+ }
+
+ public static void startLocalTranslationTo(View v, int endTranslation,
+ Interpolator interpolator) {
+ startLocalTranslation(v, getLocalTranslationY(v), endTranslation, interpolator);
+ }
+
+ public static int getLocalTranslationY(View v) {
+ Integer tag = (Integer) v.getTag(TAG_LOCAL_TRANSLATION_Y);
+ if (tag == null) {
+ return 0;
+ }
+ return tag;
+ }
+
+ private static void setLocalTranslationY(View v, int value) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_Y, value);
+ updateTopAndBottom(v);
+ }
+
+ private static void updateTopAndBottom(View v) {
+ int layoutTop = (int) v.getTag(TAG_LAYOUT_TOP);
+ int localTranslation = getLocalTranslationY(v);
+ int height = v.getHeight();
+ v.setTop(layoutTop + localTranslation);
+ v.setBottom(layoutTop + height + localTranslation);
+ }
+
+ private static void startLocalTranslation(final View v, int start, int end,
+ Interpolator interpolator) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofInt(v, LOCAL_TRANSLATION_Y, start, end);
+ Integer layoutTop = (Integer) v.getTag(TAG_LAYOUT_TOP);
+ if (layoutTop == null) {
+ layoutTop = v.getTop();
+ v.setTagInternal(TAG_LAYOUT_TOP, layoutTop);
+ }
+ setLocalTranslationY(v, start);
+ animator.setInterpolator(interpolator);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ public boolean mCancelled;
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, null);
+ setClippingDeactivated(v, false);
+ if (!mCancelled) {
+ setLocalTranslationY(v, 0);
+ v.setTagInternal(TAG_LAYOUT_TOP, null);
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+ });
+ setClippingDeactivated(v, true);
+ v.setTagInternal(TAG_LOCAL_TRANSLATION_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void fadeIn(final View v) {
+ ObjectAnimator existing = (ObjectAnimator) v.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ if (v.getVisibility() == View.INVISIBLE) {
+ v.setVisibility(View.VISIBLE);
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(v, View.ALPHA,
+ 0.0f, 1.0f);
+ v.setAlpha(0.0f);
+ animator.setInterpolator(ALPHA_IN);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(v, false /* animating */);
+ }
+ });
+ updateLayerType(v, true /* animating */);
+ v.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ private static void updateLayerType(View view, boolean animating) {
+ if (view.hasOverlappingRendering() && animating) {
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ } else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE) {
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ }
+
+ public static void fadeOut(final View view, Runnable endAction) {
+ ObjectAnimator existing = (ObjectAnimator) view.getTag(TAG_ALPHA_ANIMATOR);
+ if (existing != null) {
+ existing.cancel();
+ }
+ ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.ALPHA,
+ view.getAlpha(), 0.0f);
+ animator.setInterpolator(ALPHA_OUT);
+ animator.setDuration(APPEAR_ANIMATION_LENGTH);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, null);
+ updateLayerType(view, false /* animating */);
+ if (endAction != null) {
+ endAction.run();
+ }
+ }
+ });
+ updateLayerType(view, true /* animating */);
+ view.setTagInternal(TAG_ALPHA_ANIMATOR, animator);
+ animator.start();
+ }
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated,
+ CLIPPING_PARAMETERS);
+ }
+
+ public static boolean isAnimatingTranslation(View v) {
+ return v.getTag(TAG_LOCAL_TRANSLATION_ANIMATOR) != null;
+ }
+
+ public static boolean isAnimatingAlpha(View v) {
+ return v.getTag(TAG_ALPHA_ANIMATOR) != null;
+ }
+}
diff --git a/core/java/com/android/internal/widget/RemeasuringLinearLayout.java b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
new file mode 100644
index 0000000..e352b45
--- /dev/null
+++ b/core/java/com/android/internal/widget/RemeasuringLinearLayout.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+/**
+ * A LinearLayout that sets it's height again after the last measure pass. This is needed for
+ * MessagingLayouts where groups need to be able to snap it's height to.
+ */
+@RemoteViews.RemoteView
+public class RemeasuringLinearLayout extends LinearLayout {
+
+ public RemeasuringLinearLayout(Context context) {
+ super(context);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public RemeasuringLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public RemeasuringLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ int count = getChildCount();
+ int height = 0;
+ for (int i = 0; i < count; ++i) {
+ final View child = getChildAt(i);
+ if (child == null || child.getVisibility() == View.GONE) {
+ continue;
+ }
+
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
+ lp.bottomMargin);
+ }
+ setMeasuredDimension(getMeasuredWidth(), height);
+ }
+}
diff --git a/core/java/com/android/internal/widget/ViewClippingUtil.java b/core/java/com/android/internal/widget/ViewClippingUtil.java
new file mode 100644
index 0000000..59bbed4
--- /dev/null
+++ b/core/java/com/android/internal/widget/ViewClippingUtil.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.util.ArraySet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import com.android.internal.R;
+
+/**
+ * A utility class that allows to clip views and their parents to allow for better transitions
+ */
+public class ViewClippingUtil {
+ private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
+ private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
+ private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
+
+ public static void setClippingDeactivated(final View transformedView, boolean deactivated,
+ ClippingParameters clippingParameters) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ if (!(transformedView.getParent() instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup parent = (ViewGroup) transformedView.getParent();
+ while (true) {
+ if (!deactivated && !clippingParameters.isClippingEnablingAllowed(transformedView)) {
+ return;
+ }
+ ArraySet<View> clipSet = (ArraySet<View>) parent.getTag(CLIP_CLIPPING_SET);
+ if (clipSet == null) {
+ clipSet = new ArraySet<>();
+ parent.setTagInternal(CLIP_CLIPPING_SET, clipSet);
+ }
+ Boolean clipChildren = (Boolean) parent.getTag(CLIP_CHILDREN_TAG);
+ if (clipChildren == null) {
+ clipChildren = parent.getClipChildren();
+ parent.setTagInternal(CLIP_CHILDREN_TAG, clipChildren);
+ }
+ Boolean clipToPadding = (Boolean) parent.getTag(CLIP_TO_PADDING);
+ if (clipToPadding == null) {
+ clipToPadding = parent.getClipToPadding();
+ parent.setTagInternal(CLIP_TO_PADDING, clipToPadding);
+ }
+ if (!deactivated) {
+ clipSet.remove(transformedView);
+ if (clipSet.isEmpty()) {
+ parent.setClipChildren(clipChildren);
+ parent.setClipToPadding(clipToPadding);
+ parent.setTagInternal(CLIP_CLIPPING_SET, null);
+ clippingParameters.onClippingStateChanged(parent, true);
+ }
+ } else {
+ clipSet.add(transformedView);
+ parent.setClipChildren(false);
+ parent.setClipToPadding(false);
+ clippingParameters.onClippingStateChanged(parent, false);
+ }
+ if (clippingParameters.shouldFinish(parent)) {
+ return;
+ }
+ final ViewParent viewParent = parent.getParent();
+ if (viewParent instanceof ViewGroup) {
+ parent = (ViewGroup) viewParent;
+ } else {
+ return;
+ }
+ }
+ }
+
+ public interface ClippingParameters {
+ /**
+ * Should we stop clipping at this view? If true is returned, {@param view} is the last view
+ * where clipping is activated / deactivated.
+ */
+ boolean shouldFinish(View view);
+
+ /**
+ * Is it allowed to enable clipping on this view.
+ */
+ default boolean isClippingEnablingAllowed(View view) {
+ return !MessagingPropertyAnimator.isAnimatingTranslation(view);
+ }
+
+ /**
+ * A method that is called whenever the view starts clipping again / stops clipping to the
+ * children and padding.
+ */
+ default void onClippingStateChanged(View view, boolean isClipping) {};
+ }
+}
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index f0ac79a..d18c172 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -554,18 +554,6 @@
}
}
-static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
-{
- if (object == NULL) {
- jniThrowNullPointerException(env, NULL);
- return;
- }
- int fd = jniGetFDFromFileDescriptor(env, object);
- if (fd >= 0) {
- jniSetFileDescriptorOfFD(env, object, -1);
- }
-}
-
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
Parcel* parcel = new Parcel();
@@ -811,7 +799,6 @@
{"openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
{"dupFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
{"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
- {"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
{"nativeCreate", "()J", (void*)android_os_Parcel_create},
{"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer},
diff --git a/core/res/res/drawable/ic_reply_notification_large.xml b/core/res/res/drawable/ic_reply_notification_large.xml
new file mode 100644
index 0000000..e75afdd
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification_large.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ 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
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:inset="8dp">
+ <vector android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0 0h24v24H0z"/>
+ </vector>
+</inset>
diff --git a/core/res/res/drawable/messaging_message_background.xml b/core/res/res/drawable/messaging_message_background.xml
new file mode 100644
index 0000000..8a2096a
--- /dev/null
+++ b/core/res/res/drawable/messaging_message_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:tint="#14000000">
+ <corners android:radius="4dp" />
+ <padding android:bottom="6dp"
+ android:left="8dp"
+ android:right="8dp"
+ android:top="6dp" />
+</shape>
diff --git a/core/res/res/layout/chooser_row.xml b/core/res/res/layout/chooser_row.xml
index 6c1271d..cf81260 100644
--- a/core/res/res/layout/chooser_row.xml
+++ b/core/res/res/layout/chooser_row.xml
@@ -22,8 +22,7 @@
android:layout_height="100dp"
android:gravity="start|top"
android:paddingStart="@dimen/chooser_grid_padding"
- android:paddingEnd="@dimen/chooser_grid_padding"
- android:weightSum="4">
+ android:paddingEnd="@dimen/chooser_grid_padding">
</LinearLayout>
diff --git a/core/res/res/layout/notification_material_action.xml b/core/res/res/layout/notification_material_action.xml
index 548ee05..3c9f6ee 100644
--- a/core/res/res/layout/notification_material_action.xml
+++ b/core/res/res/layout/notification_material_action.xml
@@ -25,6 +25,7 @@
android:layout_marginStart="4dp"
android:textColor="@color/notification_default_color"
android:singleLine="true"
+ android:textAlignment="viewStart"
android:ellipsize="end"
android:background="@drawable/notification_material_action_background"
/>
diff --git a/core/res/res/layout/notification_material_action_tombstone.xml b/core/res/res/layout/notification_material_action_tombstone.xml
index 1f59ea0..817f298 100644
--- a/core/res/res/layout/notification_material_action_tombstone.xml
+++ b/core/res/res/layout/notification_material_action_tombstone.xml
@@ -26,6 +26,7 @@
android:textColor="#555555"
android:singleLine="true"
android:ellipsize="end"
+ android:textAlignment="viewStart"
android:alpha="0.5"
android:enabled="false"
android:background="@drawable/notification_material_action_background"
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index fd5154a..f72230b 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -14,13 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="messaging"
>
- <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_header_height"
+ android:layout_marginEnd="56dp"/>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
@@ -39,7 +43,6 @@
android:paddingEnd="@dimen/notification_content_margin_end"
android:minHeight="@dimen/notification_min_content_height"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
- android:clipToPadding="false"
android:orientation="vertical"
>
<include layout="@layout/notification_template_part_line1"
@@ -50,31 +53,14 @@
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:spacing="@dimen/notification_messaging_spacing" >
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- </com.android.internal.widget.MessagingLinearLayout>
+ android:layout_marginTop="8dp"
+ android:spacing="@dimen/notification_messaging_spacing" />
</LinearLayout>
</LinearLayout>
<include layout="@layout/notification_material_action_list" />
- <include layout="@layout/notification_template_right_icon" />
-</FrameLayout>
+ <include layout="@layout/notification_template_right_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:layout_gravity="top|end"/>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
new file mode 100644
index 0000000..8973ceb
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<com.android.internal.widget.MessagingGroup
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <ImageView
+ android:id="@+id/message_icon"
+ android:layout_width="@dimen/messaging_avatar_size"
+ android:layout_height="@dimen/messaging_avatar_size"
+ android:layout_marginEnd="8dp"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no" />
+ <com.android.internal.widget.RemeasuringLinearLayout
+ android:id="@+id/message_group_and_sender_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/group_message_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:spacing="2dp"
+ android:layout_weight="1"/>
+ <com.android.internal.widget.ImageFloatingTextView
+ android:id="@+id/message_name"
+ style="@style/Widget.Material.Notification.MessagingName"
+ android:layout_width="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="2dp"
+ />
+ </com.android.internal.widget.RemeasuringLinearLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_message.xml
new file mode 100644
index 0000000..ab6466c
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_message.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<com.android.internal.widget.MessagingMessage
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/message_text"
+ style="@style/Widget.Material.Notification.MessagingText"
+/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index d379256..8fb2887 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -19,12 +19,12 @@
android:id="@+id/right_icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="36dp"
android:layout_gravity="top|end">
<ImageView android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="top|end"
- android:layout_marginTop="36dp"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
@@ -32,7 +32,7 @@
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="top|end"
- android:layout_marginTop="64dp"
+ android:layout_marginTop="28dp"
android:layout_marginEnd="12dp"
android:background="@drawable/notification_reply_background"
android:src="@drawable/ic_reply_notification"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 947fcf1..946216c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -605,6 +605,8 @@
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">40dp</dimen>
+ <dimen name="messaging_avatar_size">24dp</dimen>
+
<!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
<dimen name="autofill_dataset_picker_max_size">90%</dimen>
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index cd3624d..b40117e 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -139,6 +139,27 @@
<!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}. -->
<item type="id" name="accessibilityActionMoveWindow" />
+ <!-- A tag used to save an animator in local y translation -->
+ <item type="id" name="tag_local_translation_y_animator" />
+
+ <!-- A tag used to save the local translation y -->
+ <item type="id" name="tag_local_translation_y" />
+
+ <!-- A tag used to save the original top of a view -->
+ <item type="id" name="tag_layout_top" />
+
+ <!-- A tag used to save an animator in alpha -->
+ <item type="id" name="tag_alpha_animator" />
+
+ <!-- A tag used to save the clip children in a tag -->
+ <item type="id" name="clip_children_tag" />
+
+ <!-- A tag used to save the set of deactivated children that clip -->
+ <item type="id" name="clip_children_set_tag" />
+
+ <!-- A tag used to save the clip to padding in a tag -->
+ <item type="id" name="clip_to_padding_tag" />
+
<!-- Action used to manually trigger an autofill request -->
<item type="id" name="autofill" />
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index cddf99a..2cd4dcb 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -505,8 +505,17 @@
<item name="layout_width">match_parent</item>
<item name="layout_height">wrap_content</item>
<item name="ellipsize">end</item>
- <item name="visibility">gone</item>
<item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="background">@drawable/messaging_message_background</item>
+ </style>
+
+ <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">
+ <item name="layout_width">wrap_content</item>
+ <item name="layout_height">wrap_content</item>
+ <item name="ellipsize">end</item>
+ <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="textColor">@color/notification_primary_text_color_light</item>
+ <item name="textSize">12sp</item>
</style>
<!-- Widget Styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32758e8..fd0012d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,6 +3105,22 @@
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
+ <java-symbol type="layout" name="notification_template_messaging_message" />
+ <java-symbol type="layout" name="notification_template_messaging_group" />
+ <java-symbol type="id" name="message_text" />
+ <java-symbol type="id" name="message_name" />
+ <java-symbol type="id" name="message_icon" />
+ <java-symbol type="id" name="group_message_container" />
+ <java-symbol type="id" name="tag_local_translation_y_animator" />
+ <java-symbol type="id" name="tag_local_translation_y" />
+ <java-symbol type="id" name="tag_layout_top" />
+ <java-symbol type="id" name="tag_alpha_animator" />
+ <java-symbol type="id" name="clip_children_set_tag" />
+ <java-symbol type="id" name="clip_to_padding_tag" />
+ <java-symbol type="id" name="clip_children_tag" />
+ <java-symbol type="drawable" name="ic_reply_notification_large" />
+ <java-symbol type="dimen" name="messaging_avatar_size" />
+
<java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
<java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
<java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ebe0527..1002939 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -180,7 +180,6 @@
Settings.Global.DNS_RESOLVER_MIN_SAMPLES,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
- Settings.Global.DNS_TLS_DISABLED,
Settings.Global.DOCK_SOUNDS_ENABLED_WHEN_ACCESSIBILITY,
Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE,
Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index bbdbdb1..0245570 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -49,6 +49,11 @@
import static org.hamcrest.Matchers.anyOf;
import static org.hamcrest.Matchers.is;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.app.Instrumentation;
@@ -633,15 +638,21 @@
@Test
public void testSetSelectionAndActionMode() throws Throwable {
+ final TextView textView = mActivity.findViewById(R.id.textview);
+ final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
+ when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
+ .thenReturn(true);
+ when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
+ .thenReturn(true);
+ textView.setCustomSelectionActionModeCallback(amCallback);
+
final String text = "abc def";
onView(withId(R.id.textview)).perform(replaceText(text));
-
- final TextView textView = mActivity.findViewById(R.id.textview);
mActivityRule.runOnUiThread(
() -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
mInstrumentation.waitForIdleSync();
// Don't automatically start action mode.
- // TODO: Implement assertActionModeNotStarted()
+ verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
// Make sure that "Select All" is included in the selection action mode when the entire text
// is not selected.
onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index dfe8511..3919fdd 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -23,11 +23,11 @@
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View.MeasureSpec;
import com.android.frameworks.coretests.R;
-import com.google.common.base.Function;
import org.junit.Before;
import org.junit.Test;
@@ -52,33 +52,28 @@
@Test
public void testSingleChild() {
- FakeImageFloatingTextView child = fakeChild((i) -> 3);
+ FakeImageFloatingTextView child = fakeChild(3);
- mView.setNumIndentLines(2);
mView.addView(child);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child.getNumIndentLines());
assertFalse(child.isHidden());
assertEquals(150, mView.getMeasuredHeight());
}
@Test
public void testLargeSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(3);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(205, mView.getMeasuredHeight());
@@ -86,18 +81,15 @@
@Test
public void testSmallSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(1);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(2, child1.getNumIndentLines());
- assertEquals(1, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(105, mView.getMeasuredHeight());
@@ -105,17 +97,15 @@
@Test
public void testLargeLarge() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(7);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child2.getNumIndentLines());
assertTrue("child1 should be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(350, mView.getMeasuredHeight());
@@ -123,10 +113,9 @@
@Test
public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
@@ -135,51 +124,18 @@
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(355, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
+ assertEquals(355, mView.getMeasuredHeight());;
}
- @Test
- public void testLargeSmall_largeWrapsWith3indentbutnot3() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
-
- mView.setNumIndentLines(2);
- mView.addView(child1);
- mView.addView(child2);
-
- mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
- mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
-
- assertFalse("child1 should not be hidden", child1.isHidden());
- assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(255, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
- }
-
- private class FakeImageFloatingTextView extends ImageFloatingTextView {
+ private class FakeImageFloatingTextView extends MessagingMessage {
public static final int LINE_HEIGHT = 50;
- private final Function<Integer, Integer> mLinesForIndent;
- private int mNumIndentLines;
+ private final int mNumLines;
public FakeImageFloatingTextView(Context context,
- Function<Integer, Integer> linesForIndent) {
+ int linesForIndent) {
super(context, null, 0, 0);
- mLinesForIndent = linesForIndent;
- }
-
- @Override
- public boolean setNumIndentLines(int lines) {
- boolean changed = (mNumIndentLines != lines);
- mNumIndentLines = lines;
- return changed;
- }
-
- public int getNumIndentLines() {
- return mNumIndentLines;
+ mNumLines = linesForIndent;
}
@Override
@@ -195,6 +151,20 @@
heightMeasureSpec)));
}
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ if (getMeasuredHeight() == getDesiredHeight()) {
+ return MEASURED_NORMAL;
+ } else {
+ return MEASURED_SHORTENED;
+ }
+ }
+ }
+
private int clampToMultiplesOfLineHeight(int size) {
if (size <= LINE_HEIGHT) {
return size;
@@ -204,7 +174,7 @@
@Override
public int getLineCount() {
- return mLinesForIndent.apply(mNumIndentLines);
+ return mNumLines;
}
public int getDesiredHeight() {
@@ -229,7 +199,7 @@
}
}
- private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
- return new FakeImageFloatingTextView(mContext, linesForIndent);
+ private FakeImageFloatingTextView fakeChild(int numLines) {
+ return new FakeImageFloatingTextView(mContext, numLines);
}
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 82aa803..f7e0b46 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -328,6 +328,7 @@
<privapp-permissions package="com.android.systemui">
<permission name="android.permission.BATTERY_STATS"/>
<permission name="android.permission.BIND_APPWIDGET"/>
+ <permission name="android.permission.BIND_SLICE" />
<permission name="android.permission.BLUETOOTH_PRIVILEGED"/>
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 70dfa86..508869a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -521,7 +521,11 @@
float sweepAngle, bool useCenter, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
- mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+ if (fabs(sweepAngle) >= 360.0f) {
+ mCanvas->drawOval(arc, paint);
+ } else {
+ mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+ }
}
void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 1d85c97..597336b 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -35,6 +35,7 @@
"libutils",
"libbinder",
"libmedia",
+ "libmedia_omx",
"libmediametrics",
"libmediadrm",
"libmidi",
diff --git a/packages/MtpDocumentsProvider/res/values-mr/strings.xml b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
index 89a9d14..d581e10 100644
--- a/packages/MtpDocumentsProvider/res/values-mr/strings.xml
+++ b/packages/MtpDocumentsProvider/res/values-mr/strings.xml
@@ -19,7 +19,7 @@
<string name="app_label" msgid="6271216747302322594">"MTP होस्ट"</string>
<string name="downloads_app_label" msgid="7120690641874849726">"डाउनलोड"</string>
<string name="root_name" msgid="5819495383921089536">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> <xliff:g id="STORAGE_NAME">%2$s</xliff:g>"</string>
- <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करीत आहे"</string>
+ <string name="accessing_notification_title" msgid="3030133609230917944">"<xliff:g id="DEVICE_MODEL">%1$s</xliff:g> मधून फायलींंमध्ये प्रवेश करत आहे"</string>
<string name="error_busy_device" msgid="3997316850357386589">"दुसरे डिव्हाइस व्यस्त आहे. ते उपलब्ध होईपर्यंत तुम्ही फायली ट्रांसफर करू शकत नाही."</string>
<string name="error_locked_device" msgid="7557872102188356147">"कोणत्याही फायली आढळल्या नाहीत. दुसरे डिव्हाइस कदाचित बंद असू शकते. तसे असल्यास, ते अनलॉक करा आणि पुन्हा प्रयत्न करा."</string>
</resources>
diff --git a/packages/PrintSpooler/res/values-mr/strings.xml b/packages/PrintSpooler/res/values-mr/strings.xml
index 05eb853..862d193 100644
--- a/packages/PrintSpooler/res/values-mr/strings.xml
+++ b/packages/PrintSpooler/res/values-mr/strings.xml
@@ -34,7 +34,7 @@
<string name="print_preview" msgid="8010217796057763343">"मुद्रण पूर्वावलोकन"</string>
<string name="install_for_print_preview" msgid="6366303997385509332">"पूर्वावलोकनासाठी पीडीएफ व्ह्यूअर इंस्टॉल करा"</string>
<string name="printing_app_crashed" msgid="854477616686566398">"प्रिंटिंग अॅप क्रॅश झाले"</string>
- <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्युत्पन्न करीत आहे"</string>
+ <string name="generating_print_job" msgid="3119608742651698916">"मुद्रण कार्य व्युत्पन्न करत आहे"</string>
<string name="save_as_pdf" msgid="5718454119847596853">"पीडीएफ म्हणून सेव्ह करा"</string>
<string name="all_printers" msgid="5018829726861876202">"सर्व प्रिंटर..."</string>
<string name="print_dialog" msgid="32628687461331979">"मुद्रण संवाद"</string>
@@ -79,8 +79,8 @@
<item quantity="one"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
<item quantity="other"><xliff:g id="COUNT_1">%1$s</xliff:g> प्रिंटर शोधण्यासाठी इंस्टॉल करा</item>
</plurals>
- <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करीत आहे"</string>
- <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करीत आहे"</string>
+ <string name="printing_notification_title_template" msgid="295903957762447362">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> मुद्रण करत आहे"</string>
+ <string name="cancelling_notification_title_template" msgid="1821759594704703197">"<xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> रद्द करत आहे"</string>
<string name="failed_notification_title_template" msgid="2256217208186530973">"प्रिंटर एरर <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
<string name="blocked_notification_title_template" msgid="1175435827331588646">"प्रिंटरने <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g> अवरोधित केले"</string>
<string name="cancel" msgid="4373674107267141885">"रद्द करा"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index a3430fc..2a4983a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"Couldn\'t pair with <xliff:g id="DEVICE_NAME">%1$s</xliff:g> because of an incorrect PIN or passkey."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"Can\'t communicate with <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Pairing rejected by <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Computer"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Headset"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Phone"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imaging"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Headphone"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Input Peripheral"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"Wifi off."</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"Wifi disconnected."</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Wifi one bar."</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index ea62383..f351e70 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"No se pudo sincronizar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g> debido a que el PIN o la clave de acceso son incorrectos."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"No se puede establecer la comunicación con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"Vínculo rechazado por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>"</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"Computadora"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"Auriculares"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"Teléfono"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"Imágenes"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"Auriculares"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"Periférico de entrada"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"Bluetooth"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"Wi-Fi inhabilitado"</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"Wi-Fi desconectado"</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"Una barra de Wi-Fi"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 42ee581..17d7191 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="wifi_fail_to_scan" msgid="1265540342578081461">"नेटवर्कसाठी स्कॅन करू शकत नाही"</string>
<string name="wifi_security_none" msgid="7985461072596594400">"काहीही नाही"</string>
- <string name="wifi_remembered" msgid="4955746899347821096">"जतन केले"</string>
+ <string name="wifi_remembered" msgid="4955746899347821096">"सेव्ह केले"</string>
<string name="wifi_disabled_generic" msgid="4259794910584943386">"अक्षम"</string>
<string name="wifi_disabled_network_failure" msgid="2364951338436007124">"IP कॉन्फिगरेशन अयशस्वी"</string>
<string name="wifi_disabled_by_recommendation_provider" msgid="5168315140978066096">"कमी दर्जाच्या नेटवर्कमुळे कनेक्ट केलेले नाही"</string>
@@ -34,7 +34,7 @@
<string name="wifi_not_in_range" msgid="1136191511238508967">"परिक्षेत्रामध्ये नाही"</string>
<string name="wifi_no_internet_no_reconnect" msgid="5724903347310541706">"स्वयंचलितपणे कनेक्ट करणार नाही"</string>
<string name="wifi_no_internet" msgid="3880396223819116454">"इंटरनेट अॅक्सेस नाही"</string>
- <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे जतन केले"</string>
+ <string name="saved_network" msgid="4352716707126620811">"<xliff:g id="NAME">%1$s</xliff:g> द्वारे सेव्ह केले"</string>
<string name="connected_via_network_scorer" msgid="5713793306870815341">"%1$s द्वारे स्वयंचलितपणे कनेक्ट केले"</string>
<string name="connected_via_network_scorer_default" msgid="7867260222020343104">"नेटवर्क रेटिंग प्रदात्याद्वारे स्वयंचलितपणे कनेक्ट केले"</string>
<string name="connected_via_passpoint" msgid="2826205693803088747">"%1$s द्वारे कनेक्ट केले"</string>
@@ -52,7 +52,7 @@
<string name="preference_summary_default_combination" msgid="8532964268242666060">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
<string name="bluetooth_disconnected" msgid="6557104142667339895">"डिस्कनेक्ट केले"</string>
<string name="bluetooth_disconnecting" msgid="8913264760027764974">"डिस्कनेक्ट करत आहे..."</string>
- <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करीत आहे..."</string>
+ <string name="bluetooth_connecting" msgid="8555009514614320497">"कनेक्ट करत आहे..."</string>
<string name="bluetooth_connected" msgid="6038755206916626419">"कनेक्ट केले"</string>
<string name="bluetooth_pairing" msgid="1426882272690346242">"जोडत आहे…"</string>
<string name="bluetooth_connected_no_headset" msgid="2866994875046035609">"कनेक्ट केले (फोन नाही)"</string>
@@ -99,6 +99,13 @@
<string name="bluetooth_pairing_pin_error_message" msgid="8337234855188925274">"अयोग्य पिन किंवा पासकीमुळे <xliff:g id="DEVICE_NAME">%1$s</xliff:g> सह जोडू शकलो नाही."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="7870998403045801381">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> शी संप्रेषण करू शकत नाही."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="1648157108520832454">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> द्वारे पेअरींग नाकारले."</string>
+ <string name="bluetooth_talkback_computer" msgid="4875089335641234463">"संगणक"</string>
+ <string name="bluetooth_talkback_headset" msgid="5140152177885220949">"हेडसेट"</string>
+ <string name="bluetooth_talkback_phone" msgid="4260255181240622896">"फोन"</string>
+ <string name="bluetooth_talkback_imaging" msgid="551146170554589119">"इमेज"</string>
+ <string name="bluetooth_talkback_headphone" msgid="26580326066627664">"हेडफोन"</string>
+ <string name="bluetooth_talkback_input_peripheral" msgid="5165842622743212268">"इनपुट परिधीय"</string>
+ <string name="bluetooth_talkback_bluetooth" msgid="5615463912185280812">"ब्लूटूथ"</string>
<string name="accessibility_wifi_off" msgid="1166761729660614716">"वाय फाय बंद."</string>
<string name="accessibility_no_wifi" msgid="8834610636137374508">"वाय फाय डिस्कनेक्ट झाले."</string>
<string name="accessibility_wifi_one_bar" msgid="4869376278894301820">"वाय फाय एक बार."</string>
@@ -135,7 +142,7 @@
<string name="tts_play_example_summary" msgid="8029071615047894486">"उच्चार संश्लेषणाचे एक छोटेसे प्रात्यक्षिक प्ले करा"</string>
<string name="tts_install_data_title" msgid="4264378440508149986">"व्हॉइस डेटा इंस्टॉल करा"</string>
<string name="tts_install_data_summary" msgid="5742135732511822589">"उच्चार संश्लेषणासाठी आवश्यक आवाज डेटा इंस्टॉल करा"</string>
- <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन संकेतशब्द आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
+ <string name="tts_engine_security_warning" msgid="8786238102020223650">"हे उच्चार संश्लेषण इंजिन पासवर्ड आणि क्रेडिट कार्ड नंबर यासारख्या वैयक्तिक मजकुरासह, बोलला जाणारा सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. हे <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g> इंजिनवरून येते. या उच्चार संश्लेषण इंजिनचा वापर सक्षम करायचा?"</string>
<string name="tts_engine_network_required" msgid="1190837151485314743">"या भाषेस टेक्स्ट टू स्पीचसाठी एका नेटवर्क कनेक्शनची आवश्यकता आहे."</string>
<string name="tts_default_sample_string" msgid="4040835213373086322">"हे उच्चार संश्लेषणाचे एक उदाहरण आहे"</string>
<string name="tts_status_title" msgid="7268566550242584413">"डीफॉल्ट भाषा स्थिती"</string>
@@ -245,7 +252,7 @@
<string name="debug_debugging_category" msgid="6781250159513471316">"डीबग करणे"</string>
<string name="debug_app" msgid="8349591734751384446">"डीबग अॅप निवडा"</string>
<string name="debug_app_not_set" msgid="718752499586403499">"कोणतेही डीबग अॅप्लिकेशन सेट नाही"</string>
- <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करीत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
+ <string name="debug_app_set" msgid="2063077997870280017">"अॅप्लिकेशन डीबग करत आहे: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
<string name="select_application" msgid="5156029161289091703">"अॅप्लिकेशन निवडा"</string>
<string name="no_application" msgid="2813387563129153880">"काहीही नाही"</string>
<string name="wait_for_debugger" msgid="1202370874528893091">"डीबगरची प्रतीक्षा करा"</string>
@@ -303,11 +310,11 @@
<string name="force_resizable_activities_summary" msgid="6667493494706124459">"manifest मूल्यांकडे दुर्लक्ष करून, एकाधिक-विंडोसाठी सर्व क्रियाकलापांचा आकार बदलण्यायोग्य करा."</string>
<string name="enable_freeform_support" msgid="1461893351278940416">"freeform विंडो सक्षम करा"</string>
<string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रायोगिक मुक्तस्वरूपाच्या विंडोसाठी समर्थन सक्षम करा."</string>
- <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप संकेतशब्द"</string>
+ <string name="local_backup_password_title" msgid="3860471654439418822">"डेस्कटॉप बॅकअप पासवर्ड"</string>
<string name="local_backup_password_summary_none" msgid="6951095485537767956">"डेस्कटॉप पूर्ण बॅक अप सध्या संरक्षित नाहीत"</string>
- <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला संकेतशब्द बदलण्यासाठी किंवा काढण्यासाठी टॅप करा"</string>
+ <string name="local_backup_password_summary_change" msgid="5376206246809190364">"डेस्कटॉपच्या पूर्ण बॅकअपसाठी असलेला पासवर्ड बदलण्यासाठी किंवा काढण्यासाठी टॅप करा"</string>
<string name="local_backup_password_toast_success" msgid="582016086228434290">"नवीन बॅक अप पासवर्ड सेट झाला"</string>
- <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन संकेतशब्द आणि पुष्टीकरण जुळत नाही"</string>
+ <string name="local_backup_password_toast_confirmation_mismatch" msgid="7805892532752708288">"नवीन पासवर्ड आणि पुष्टीकरण जुळत नाही"</string>
<string name="local_backup_password_toast_validation_failure" msgid="5646377234895626531">"बॅक अप पासवर्ड सेट करणे अयशस्वी"</string>
<string-array name="color_mode_names">
<item msgid="2425514299220523812">"सशक्त (डीफॉल्ट)"</item>
@@ -382,13 +389,13 @@
<string name="screen_zoom_summary_custom" msgid="5611979864124160447">"सानुकूल करा (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="help_feedback_label" msgid="6815040660801785649">"मदत आणि अभिप्राय"</string>
<string name="content_description_menu_button" msgid="8182594799812351266">"मेनू"</string>
- <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड प्रविष्ट करा"</string>
+ <string name="retail_demo_reset_message" msgid="118771671364131297">"डेमो मोडमध्ये फॅक्टरी रीसेट करण्यासाठी पासवर्ड एंटर करा"</string>
<string name="retail_demo_reset_next" msgid="8356731459226304963">"पुढील"</string>
- <string name="retail_demo_reset_title" msgid="696589204029930100">"संकेतशब्द आवश्यक"</string>
+ <string name="retail_demo_reset_title" msgid="696589204029930100">"पासवर्ड आवश्यक"</string>
<string name="active_input_method_subtypes" msgid="3596398805424733238">"सक्रिय इनपुट पद्धती"</string>
<string name="use_system_language_to_select_input_method_subtypes" msgid="5747329075020379587">"सिस्टम भाषा वापरा"</string>
<string name="failed_to_open_app_settings_toast" msgid="1251067459298072462">"<xliff:g id="SPELL_APPLICATION_NAME">%1$s</xliff:g> साठी सेटिंग्ज उघडण्यात अयशस्वी"</string>
- <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत संकेतशब्द आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
+ <string name="ime_security_warning" msgid="4135828934735934248">"ही इनपुट पद्धत पासवर्ड आणि क्रेडिट कार्ड नंबर यासह, आपण टाइप करता तो सर्व मजकूर संकलित करण्यात सक्षम होऊ शकते. ही <xliff:g id="IME_APPLICATION_NAME">%1$s</xliff:g> अॅपवरून येते. ही इनपुट पद्धत वापरायची?"</string>
<string name="direct_boot_unaware_dialog_message" msgid="7870273558547549125">"टीप: रीबूट केल्यानंतर, तुम्ही आपला फोन अनलॉक करे पर्यंत हे अॅप सुरू होऊ शकत नाही"</string>
<string name="ims_reg_title" msgid="7609782759207241443">"IMS नोंदणी स्थिती"</string>
<string name="ims_reg_status_registered" msgid="933003316932739188">"नोंदवलेले"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 360848f..4bb16ff 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -546,11 +546,13 @@
<!-- [CHAR LIMIT=NONE] Label for displaying Bluetooth Audio Codec Parameters while streaming -->
<string name="bluetooth_select_a2dp_codec_streaming_label">Streaming: <xliff:g id="streaming_parameter">%1$s</xliff:g></string>
- <!-- Title of the developer option for DNS over TLS. -->
- <string name="dns_tls">DNS over TLS</string>
- <!-- Summary to explain the developer option for DNS over TLS. This allows the user to
- request that the system attempt TLS with all DNS servers, or none. -->
- <string name="dns_tls_summary">If enabled, attempt DNS over TLS on port 853.</string>
+ <!-- Developer option setting for Private DNS -->
+ <string name="select_private_dns_configuration_title">Private DNS</string>
+ <string name="select_private_dns_configuration_dialog_title">Select Private DNS Mode</string>
+ <string name="private_dns_mode_off">Off</string>
+ <string name="private_dns_mode_opportunistic">Opportunistic</string>
+ <string name="private_dns_mode_provider">Private DNS provider hostname</string>
+ <string name="private_dns_mode_provider_hostname_hint">Enter hostname of DNS provider</string>
<!-- setting Checkbox summary whether to show options for wireless display certification -->
<string name="wifi_display_certification_summary">Show options for wireless display certification</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index 1cb255b..a8262c8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -20,6 +20,7 @@
import android.content.res.XmlResourceParser;
import android.icu.text.TimeZoneFormat;
import android.icu.text.TimeZoneNames;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.text.BidiFormatter;
import android.support.v4.text.TextDirectionHeuristicsCompat;
import android.text.SpannableString;
@@ -32,6 +33,8 @@
import com.android.settingslib.R;
+import libcore.util.TimeZoneFinder;
+
import org.xmlpull.v1.XmlPullParserException;
import java.util.ArrayList;
@@ -43,7 +46,6 @@
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
-import libcore.util.TimeZoneFinder;
/**
* ZoneGetter is the utility class to get time zone and zone list, and both of them have display
@@ -350,7 +352,8 @@
return gmtText;
}
- private static final class ZoneGetterData {
+ @VisibleForTesting
+ public static final class ZoneGetterData {
public final String[] olsonIdsToDisplay;
public final CharSequence[] gmtOffsetTexts;
public final TimeZone[] timeZones;
@@ -377,9 +380,13 @@
}
// Create a lookup of local zone IDs.
- List<String> zoneIds =
- TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(locale.getCountry());
+ final List<String> zoneIds = lookupTimeZoneIdsByCountry(locale.getCountry());
localZoneIds = new HashSet<>(zoneIds);
}
+
+ @VisibleForTesting
+ public List<String> lookupTimeZoneIdsByCountry(String country) {
+ return TimeZoneFinder.getInstance().lookupTimeZoneIdsByCountry(country);
+ }
}
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7b8d0db..29ecac0 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -120,6 +120,7 @@
<uses-permission android:name="android.permission.TRUST_LISTENER" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.RESET_FINGERPRINT_LOCKOUT" />
+ <uses-permission android:name="android.permission.BIND_SLICE" />
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
@@ -222,7 +223,7 @@
-->
<service android:name="SystemUIService"
android:exported="true"
- />
+ />
<!-- Recents depends on every user having their own SystemUI process, so on user switch,
ensure that the process is created by starting this service.
@@ -568,6 +569,11 @@
android:resource="@xml/fileprovider" />
</provider>
+ <provider android:name=".keyguard.KeyguardSliceProvider"
+ android:authorities="com.android.systemui.keyguard"
+ android:exported="true">
+ </provider>
+
<receiver
android:name=".statusbar.KeyboardShortcutsReceiver">
<intent-filter>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
index 39cba74..3018a02 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_emergency_carrier_area.xml
@@ -33,8 +33,10 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Keyguard.TextView"
+ android:singleLine="true"
android:ellipsize="marquee"
android:visibility="gone"
+ android:gravity="center"
androidprv:allCaps="@bool/kg_use_all_caps" />
<com.android.keyguard.EmergencyButton
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
index 97c8965..947f27d 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_pin_view.xml
@@ -32,6 +32,7 @@
android:id="@+id/keyguard_sim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:tint="@color/background_protected"
android:src="@drawable/ic_lockscreen_sim"/>
<include layout="@layout/keyguard_message_area"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
index d4c5d74..6f270b4 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_sim_puk_view.xml
@@ -33,6 +33,7 @@
android:id="@+id/keyguard_sim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:tint="@color/background_protected"
android:src="@drawable/ic_lockscreen_sim"/>
<include layout="@layout/keyguard_message_area"
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index 56fb73f..020cfee 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -18,34 +18,37 @@
-->
<!-- This is a view that shows general status information in Keyguard. -->
-<LinearLayout
+<com.android.keyguard.KeyguardSliceView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center">
- <com.android.systemui.statusbar.policy.DateView
- android:id="@+id/date_view"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textColor="?attr/wallpaperTextColor"
- style="@style/widget_label"
- android:letterSpacing="0.05"
- android:gravity="center"
- />
- <TextView android:id="@+id/alarm_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawablePadding="6dp"
- android:drawableStart="@drawable/ic_access_alarms_big"
- android:drawableTint="?attr/wallpaperTextColorSecondary"
- android:drawableTintMode="src_in"
- android:textColor="?attr/wallpaperTextColorSecondary"
- android:letterSpacing="0.05"
- style="@style/widget_label"
- android:layout_marginStart="6dp"
- android:gravity="center"
- android:visibility="gone"
- />
-</LinearLayout>
+ android:layout_marginTop="@dimen/date_owner_info_margin"
+ android:layout_gravity="center_horizontal"
+ android:paddingTop="4dp"
+ android:clipToPadding="false"
+ android:orientation="vertical"
+ android:layout_centerHorizontal="true">
+ <TextView android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:gravity="center"
+ android:textSize="22sp"
+ android:textColor="?attr/wallpaperTextColor"
+ />
+ <TextView android:id="@+id/text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center"
+ android:visibility="gone"
+ android:textSize="16sp"
+ android:textColor="?attr/wallpaperTextColor"
+ android:layout_marginTop="4dp"
+ android:ellipsize="end"
+ />
+</com.android.keyguard.KeyguardSliceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9901f6f..c678111f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -95,6 +95,10 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height_increased">188dp</dimen>
+ <!-- Height of a messaging notifications with actions at least. Not that this is an upper bound
+ and the notification won't use this much, but is measured with wrap_content -->
+ <dimen name="notification_messaging_actions_min_height">196dp</dimen>
+
<!-- a threshold in dp per second that is considered fast scrolling -->
<dimen name="scroll_fast_threshold">1500dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5f8029..cfd95b4 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -66,9 +66,6 @@
<!-- For notification icons for which targetSdk < L, this caches whether the icon is grayscale -->
<item type="id" name="icon_is_grayscale" />
- <item type="id" name="clip_children_tag" />
- <item type="id" name="clip_children_set_tag" />
- <item type="id" name="clip_to_padding_tag" />
<item type="id" name="image_icon_tag" />
<item type="id" name="contains_transformed_view" />
<item type="id" name="is_clicked_heads_up_tag" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
new file mode 100644
index 0000000..c18f9b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.KeyguardSliceProvider;
+
+/**
+ * View visible under the clock on the lock screen and AoD.
+ */
+public class KeyguardSliceView extends LinearLayout {
+
+ private final Uri mKeyguardSliceUri;
+ private TextView mTitle;
+ private TextView mText;
+ private Slice mSlice;
+ private PendingIntent mSliceAction;
+ private int mTextColor;
+ private float mDarkAmount = 0;
+
+ private final ContentObserver mObserver;
+
+ public KeyguardSliceView(Context context) {
+ this(context, null, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public KeyguardSliceView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mObserver = new KeyguardSliceObserver(new Handler());
+ mKeyguardSliceUri = Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI);;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mTitle = findViewById(R.id.title);
+ mText = findViewById(R.id.text);
+ mTextColor = mTitle.getCurrentTextColor();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ // Set initial content
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+
+ // Make sure we always have the most current slice
+ getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
+ false /* notifyDescendants */, mObserver);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ getContext().getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ private void showSlice(Slice slice) {
+ // Items will be wrapped into an action when they have tap targets.
+ SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+ if (actionSlice != null) {
+ mSlice = actionSlice.getSlice();
+ mSliceAction = actionSlice.getAction();
+ } else {
+ mSlice = slice;
+ mSliceAction = null;
+ }
+
+ if (mSlice == null) {
+ setVisibility(GONE);
+ return;
+ }
+
+ SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+ if (title == null) {
+ mTitle.setVisibility(GONE);
+ } else {
+ mTitle.setVisibility(VISIBLE);
+ mTitle.setText(title.getText());
+ }
+
+ SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+ if (text == null) {
+ mText.setVisibility(GONE);
+ } else {
+ mText.setVisibility(VISIBLE);
+ mText.setText(text.getText());
+ }
+
+ final int visibility = title == null && text == null ? GONE : VISIBLE;
+ if (visibility != getVisibility()) {
+ setVisibility(visibility);
+ }
+ }
+
+ public void setDark(float darkAmount) {
+ mDarkAmount = darkAmount;
+ updateTextColors();
+ }
+
+ public void setTextColor(int textColor) {
+ mTextColor = textColor;
+ }
+
+ private void updateTextColors() {
+ final int blendedColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mTitle.setTextColor(blendedColor);
+ mText.setTextColor(blendedColor);
+ }
+
+ private class KeyguardSliceObserver extends ContentObserver {
+ KeyguardSliceObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ this.onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index bc2a59d..78cf2b9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,11 +19,9 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
-import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PorterDuff;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -42,8 +40,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.LockPatternUtils;
+import com.android.settingslib.Utils;
import com.android.systemui.ChargingView;
-import com.android.systemui.statusbar.policy.DateView;
import java.util.Locale;
@@ -55,13 +53,11 @@
private final LockPatternUtils mLockPatternUtils;
private final AlarmManager mAlarmManager;
- private TextView mAlarmStatusView;
- private DateView mDateView;
private TextClock mClockView;
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
- private View mKeyguardStatusArea;
+ private KeyguardSliceView mKeyguardSlice;
private Runnable mPendingMarqueeStart;
private Handler mHandler;
@@ -69,8 +65,6 @@
private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
- private int mDateTextColor;
- private int mAlarmTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -141,7 +135,6 @@
private void setEnableMarqueeImpl(boolean enabled) {
if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
- if (mAlarmStatusView != null) mAlarmStatusView.setSelected(enabled);
if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
}
@@ -149,8 +142,6 @@
protected void onFinishInflate() {
super.onFinishInflate();
mClockContainer = findViewById(R.id.keyguard_clock_container);
- mAlarmStatusView = findViewById(R.id.alarm_status);
- mDateView = findViewById(R.id.date_view);
mClockView = findViewById(R.id.clock_view);
mClockView.setShowCurrentUserTime(true);
if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
@@ -158,11 +149,9 @@
}
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
- mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
+ mKeyguardSlice = findViewById(R.id.keyguard_status_area);
+ mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardSlice};
mTextColor = mClockView.getCurrentTextColor();
- mDateTextColor = mDateView.getCurrentTextColor();
- mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -184,8 +173,6 @@
layoutParams.bottomMargin = getResources().getDimensionPixelSize(
R.dimen.bottom_text_spacing_digital);
mClockView.setLayoutParams(layoutParams);
- mDateView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
if (mOwnerInfo != null) {
mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
@@ -193,8 +180,6 @@
}
public void refreshTime() {
- mDateView.setDatePattern(Patterns.dateViewSkel);
-
mClockView.setFormat12Hour(Patterns.clockView12);
mClockView.setFormat24Hour(Patterns.clockView24);
}
@@ -205,23 +190,11 @@
Patterns.update(mContext, nextAlarm != null);
refreshTime();
- refreshAlarmStatus(nextAlarm);
- }
-
- void refreshAlarmStatus(AlarmManager.AlarmClockInfo nextAlarm) {
- if (nextAlarm != null) {
- String alarm = formatNextAlarm(mContext, nextAlarm);
- mAlarmStatusView.setText(alarm);
- mAlarmStatusView.setContentDescription(
- getResources().getString(R.string.keyguard_accessibility_next_alarm, alarm));
- mAlarmStatusView.setVisibility(View.VISIBLE);
- } else {
- mAlarmStatusView.setVisibility(View.GONE);
- }
}
public int getClockBottom() {
- return mKeyguardStatusArea.getBottom();
+ return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
+ : mClockView.getBottom();
}
public float getClockTextSize() {
@@ -341,11 +314,8 @@
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
+ mKeyguardSlice.setDark(darkAmount);
mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
- mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
- int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
- mAlarmStatusView.setTextColor(blendedAlarmColor);
- mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
index 5d0a9d7..03b0082 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenStatePreventingAdapter.java
@@ -33,8 +33,10 @@
@Override
public void setDozeScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
+ if (state == Display.STATE_DOZE) {
state = Display.STATE_ON;
+ } else if (state == Display.STATE_DOZE_SUSPEND) {
+ state = Display.STATE_ON_SUSPEND;
}
super.setDozeScreenState(state);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 98b1106..6650cc6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -92,6 +92,7 @@
@Override
protected void dumpOnHandler(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dumpOnHandler(fd, pw, args);
if (mDozeMachine != null) {
mDozeMachine.dump(pw);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
new file mode 100644
index 0000000..03018f7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.icu.text.DateFormat;
+import android.icu.text.DisplayContext;
+import android.net.Uri;
+import android.os.Handler;
+import android.app.slice.Slice;
+import android.app.slice.SliceProvider;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Simple Slice provider that shows the current date.
+ */
+public class KeyguardSliceProvider extends SliceProvider {
+
+ public static final String KEYGUARD_SLICE_URI = "content://com.android.systemui.keyguard/main";
+
+ private final Date mCurrentTime = new Date();
+ protected final Uri mSliceUri;
+ private final Handler mHandler;
+ private String mDatePattern;
+ private DateFormat mDateFormat;
+ private String mLastText;
+ private boolean mRegistered;
+ private boolean mRegisteredEveryMinute;
+
+ /**
+ * Receiver responsible for time ticking and updating the date format.
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_TIME_TICK.equals(action)
+ || Intent.ACTION_DATE_CHANGED.equals(action)
+ || Intent.ACTION_TIME_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)
+ || Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
+ // need to get a fresh date format
+ mHandler.post(KeyguardSliceProvider.this::cleanDateFormat);
+ }
+ mHandler.post(KeyguardSliceProvider.this::updateClock);
+ }
+ }
+ };
+
+ public KeyguardSliceProvider() {
+ this(new Handler());
+ }
+
+ @VisibleForTesting
+ KeyguardSliceProvider(Handler handler) {
+ mHandler = handler;
+ mSliceUri = Uri.parse(KEYGUARD_SLICE_URI);
+ }
+
+ @Override
+ public Slice onBindSlice(Uri sliceUri) {
+ return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+ }
+
+ @Override
+ public boolean onCreate() {
+
+ mDatePattern = getContext().getString(R.string.system_ui_date_pattern);
+
+ registerClockUpdate(false /* everyMinute */);
+ updateClock();
+ return true;
+ }
+
+ protected void registerClockUpdate(boolean everyMinute) {
+ if (mRegistered) {
+ if (mRegisteredEveryMinute == everyMinute) {
+ return;
+ } else {
+ unregisterClockUpdate();
+ }
+ }
+
+ IntentFilter filter = new IntentFilter();
+ if (everyMinute) {
+ filter.addAction(Intent.ACTION_TIME_TICK);
+ }
+ filter.addAction(Intent.ACTION_DATE_CHANGED);
+ filter.addAction(Intent.ACTION_TIME_CHANGED);
+ filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+ getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
+ null /* scheduler */);
+ mRegistered = true;
+ mRegisteredEveryMinute = everyMinute;
+ }
+
+ protected void unregisterClockUpdate() {
+ if (!mRegistered) {
+ return;
+ }
+ getContext().unregisterReceiver(mIntentReceiver);
+ mRegistered = false;
+ }
+
+ @VisibleForTesting
+ boolean isRegistered() {
+ return mRegistered;
+ }
+
+ protected void updateClock() {
+ final String text = getFormattedDate();
+ if (!text.equals(mLastText)) {
+ mLastText = text;
+ getContext().getContentResolver().notifyChange(mSliceUri, null /* observer */);
+ }
+ }
+
+ protected String getFormattedDate() {
+ if (mDateFormat == null) {
+ final Locale l = Locale.getDefault();
+ DateFormat format = DateFormat.getInstanceForSkeleton(mDatePattern, l);
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE);
+ mDateFormat = format;
+ }
+ mCurrentTime.setTime(System.currentTimeMillis());
+ return mDateFormat.format(mCurrentTime);
+ }
+
+ @VisibleForTesting
+ void cleanDateFormat() {
+ mDateFormat = null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 6c5f4b2..8ff950e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -69,6 +69,7 @@
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -453,6 +454,11 @@
} else {
headsUpheight = mMaxHeadsUpHeight;
}
+ NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
+ NotificationContentView.VISIBLE_TYPE_HEADSUP);
+ if (headsUpWrapper != null) {
+ headsUpheight = Math.max(headsUpheight, headsUpWrapper.getMinLayoutHeight());
+ }
layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
mNotificationAmbientHeight);
}
@@ -1256,16 +1262,21 @@
}
private void initDimens() {
- mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy);
- mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height);
- mNotificationMinHeightLarge = getFontScaledHeight(
+ mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_legacy);
+ mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height);
+ mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_increased);
- mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height);
- mNotificationAmbientHeight = getFontScaledHeight(R.dimen.notification_ambient_height);
- mMaxHeadsUpHeightLegacy = getFontScaledHeight(
+ mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_height);
+ mNotificationAmbientHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_ambient_height);
+ mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
- mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
- mMaxHeadsUpHeightIncreased = getFontScaledHeight(
+ mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height);
+ mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_increased);
Resources res = getResources();
@@ -1280,17 +1291,6 @@
}
/**
- * @param dimenId the dimen to look up
- * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
- */
- private int getFontScaledHeight(int dimenId) {
- int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId);
- float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity /
- getResources().getDisplayMetrics().density);
- return (int) (dimensionPixelSize * factor);
- }
-
- /**
* Resets this view so it can be re-used for an updated notification.
*/
public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 5353005..09b11c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -35,7 +35,8 @@
/**
* A view that can be transformed to and from.
*/
-public class ViewTransformationHelper implements TransformableView {
+public class ViewTransformationHelper implements TransformableView,
+ TransformState.TransformInfo {
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
@@ -59,7 +60,7 @@
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
- return TransformState.createFrom(view);
+ return TransformState.createFrom(view, this);
}
return null;
}
@@ -88,6 +89,7 @@
endRunnable.run();
}
setVisible(false);
+ mViewTransformationAnimation = null;
} else {
abortTransformations();
}
@@ -245,7 +247,7 @@
}
public void resetTransformedView(View view) {
- TransformState state = TransformState.createFrom(view);
+ TransformState state = TransformState.createFrom(view, this);
state.setVisible(true /* visible */, true /* force */);
state.recycle();
}
@@ -257,6 +259,11 @@
return new ArraySet<>(mTransformedViews.values());
}
+ @Override
+ public boolean isAnimating() {
+ return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
+ }
+
public static abstract class CustomTransformation {
/**
* Transform a state to the given view
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 92f26d6..8227b77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -39,8 +39,8 @@
private Icon mIcon;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof ImageView) {
mIcon = (Icon) view.getTag(ICON_TAG);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
new file mode 100644
index 0000000..4c2a2f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.content.res.Resources;
+import android.util.Pools;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingLayout;
+import com.android.internal.widget.MessagingLinearLayout;
+import com.android.internal.widget.MessagingMessage;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * A transform state of the action list
+*/
+public class MessagingLayoutTransformState extends TransformState {
+
+ private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
+ = new Pools.SimplePool<>(40);
+ private MessagingLinearLayout mMessageContainer;
+ private MessagingLayout mMessagingLayout;
+ private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
+ private float mRelativeTranslationOffset;
+
+ public static MessagingLayoutTransformState obtain() {
+ MessagingLayoutTransformState instance = sInstancePool.acquire();
+ if (instance != null) {
+ return instance;
+ }
+ return new MessagingLayoutTransformState();
+ }
+
+ @Override
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
+ if (mTransformedView instanceof MessagingLinearLayout) {
+ mMessageContainer = (MessagingLinearLayout) mTransformedView;
+ mMessagingLayout = mMessageContainer.getMessagingLayout();
+ Resources resources = view.getContext().getResources();
+ mRelativeTranslationOffset = resources.getDisplayMetrics().density * 8;
+ }
+ }
+
+ @Override
+ public boolean transformViewTo(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ true /* to */);
+ return true;
+ } else {
+ return super.transformViewTo(otherState, transformationAmount);
+ }
+ }
+
+ @Override
+ public void transformViewFrom(TransformState otherState, float transformationAmount) {
+ if (otherState instanceof MessagingLayoutTransformState) {
+ // It's a party! Let's transform between these two layouts!
+ transformViewInternal((MessagingLayoutTransformState) otherState, transformationAmount,
+ false /* to */);
+ } else {
+ super.transformViewFrom(otherState, transformationAmount);
+ }
+ }
+
+ private void transformViewInternal(MessagingLayoutTransformState mlt,
+ float transformationAmount, boolean to) {
+ ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
+ mMessagingLayout.getMessagingGroups());
+ ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
+ mlt.mMessagingLayout.getMessagingGroups());
+ HashMap<MessagingGroup, MessagingGroup> pairs = findPairs(ownGroups, otherGroups);
+ MessagingGroup lastPairedGroup = null;
+ float currentTranslation = 0;
+ float transformationDistanceRemaining = 0;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup matchingGroup = pairs.get(ownGroup);
+ if (!isGone(ownGroup)) {
+ if (matchingGroup != null) {
+ transformGroups(ownGroup, matchingGroup, transformationAmount, to);
+ if (lastPairedGroup == null) {
+ lastPairedGroup = ownGroup;
+ if (to){
+ float totalTranslation = ownGroup.getTop() - matchingGroup.getTop();
+ transformationDistanceRemaining
+ = matchingGroup.getAvatar().getTranslationY();
+ currentTranslation = transformationDistanceRemaining - totalTranslation;
+ } else {
+ float totalTranslation = matchingGroup.getTop() - ownGroup.getTop();
+ currentTranslation = ownGroup.getAvatar().getTranslationY();
+ transformationDistanceRemaining = currentTranslation - totalTranslation;
+ }
+ }
+ } else {
+ float groupTransformationAmount = transformationAmount;
+ if (lastPairedGroup != null) {
+ adaptGroupAppear(ownGroup, transformationAmount, currentTranslation,
+ to);
+ int distance = lastPairedGroup.getTop() - ownGroup.getTop();
+ float transformationDistance = mTransformInfo.isAnimating()
+ ? distance
+ : ownGroup.getHeight() * 0.75f;
+ float translationProgress = transformationDistanceRemaining
+ - (distance - transformationDistance);
+ groupTransformationAmount =
+ translationProgress / transformationDistance;
+ groupTransformationAmount = Math.max(0.0f, Math.min(1.0f,
+ groupTransformationAmount));
+ if (to) {
+ groupTransformationAmount = 1.0f - groupTransformationAmount;
+ }
+ }
+ if (to) {
+ disappear(ownGroup, groupTransformationAmount);
+ } else {
+ appear(ownGroup, groupTransformationAmount);
+ }
+ }
+ }
+ }
+ }
+
+ private void appear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ appear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ appear(ownGroup.getAvatar(), transformationAmount);
+ appear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void adaptGroupAppear(MessagingGroup ownGroup, float transformationAmount,
+ float overallTranslation, boolean to) {
+ float relativeOffset;
+ if (to) {
+ relativeOffset = transformationAmount * mRelativeTranslationOffset;
+ } else {
+ relativeOffset = (1.0f - transformationAmount) * mRelativeTranslationOffset;
+ }
+ if (ownGroup.getSender().getVisibility() != View.GONE) {
+ relativeOffset *= 0.5f;
+ }
+ ownGroup.getMessageContainer().setTranslationY(relativeOffset);
+ ownGroup.setTranslationY(overallTranslation * 0.85f);
+ }
+
+ private void disappear(MessagingGroup ownGroup, float transformationAmount) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ disappear(child, transformationAmount);
+ setClippingDeactivated(child, true);
+ }
+ disappear(ownGroup.getAvatar(), transformationAmount);
+ disappear(ownGroup.getSender(), transformationAmount);
+ setClippingDeactivated(ownGroup.getSender(), true);
+ setClippingDeactivated(ownGroup.getAvatar(), true);
+ }
+
+ private void appear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.appear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private void disappear(View child, float transformationAmount) {
+ if (child.getVisibility() == View.GONE) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.disappear(transformationAmount, null);
+ ownState.recycle();
+ }
+
+ private ArrayList<MessagingGroup> filterHiddenGroups(
+ ArrayList<MessagingGroup> groups) {
+ ArrayList<MessagingGroup> result = new ArrayList<>(groups);
+ for (int i = 0; i < result.size(); i++) {
+ MessagingGroup messagingGroup = result.get(i);
+ if (isGone(messagingGroup)) {
+ result.remove(i);
+ i--;
+ }
+ }
+ return result;
+ }
+
+ private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
+ float transformationAmount, boolean to) {
+ transformView(transformationAmount, to, ownGroup.getSender(), otherGroup.getSender(),
+ true /* sameAsAny */);
+ transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
+ true /* sameAsAny */);
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+ float previousTranslation = 0;
+ for (int i = 0; i < ownMessages.getChildCount(); i++) {
+ View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+ if (isGone(child)) {
+ continue;
+ }
+ int otherIndex = otherMessages.getChildCount() - 1 - i;
+ View otherChild = null;
+ if (otherIndex >= 0) {
+ otherChild = otherMessages.getChildAt(otherIndex);
+ if (isGone(otherChild)) {
+ otherChild = null;
+ }
+ }
+ if (otherChild == null) {
+ float distanceToTop = child.getTop() + child.getHeight() + previousTranslation;
+ transformationAmount = distanceToTop / child.getHeight();
+ transformationAmount = Math.max(0.0f, Math.min(1.0f, transformationAmount));
+ if (to) {
+ transformationAmount = 1.0f - transformationAmount;
+ }
+ }
+ transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+ if (otherChild == null) {
+ child.setTranslationY(previousTranslation);
+ setClippingDeactivated(child, true);
+ } else if (to) {
+ float totalTranslation = child.getTop() + ownGroup.getTop()
+ - otherChild.getTop() - otherChild.getTop();
+ previousTranslation = otherChild.getTranslationY() - totalTranslation;
+ } else {
+ previousTranslation = child.getTranslationY();
+ }
+ }
+ }
+
+ private void transformView(float transformationAmount, boolean to, View ownView,
+ View otherView, boolean sameAsAny) {
+ TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
+ if (!mTransformInfo.isAnimating()) {
+ ownState.setDefaultInterpolator(Interpolators.LINEAR);
+ }
+ ownState.setIsSameAsAnyView(sameAsAny);
+ if (to) {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewTo(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.disappear(transformationAmount, null);
+ }
+ } else {
+ if (otherView != null) {
+ TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
+ ownState.transformViewFrom(otherState, transformationAmount);
+ otherState.recycle();
+ } else {
+ ownState.appear(transformationAmount, null);
+ }
+ }
+ ownState.recycle();
+ }
+
+ private HashMap<MessagingGroup, MessagingGroup> findPairs(ArrayList<MessagingGroup> ownGroups,
+ ArrayList<MessagingGroup> otherGroups) {
+ mGroupMap.clear();
+ int lastMatch = Integer.MAX_VALUE;
+ for (int i = ownGroups.size() - 1; i >= 0; i--) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ MessagingGroup bestMatch = null;
+ int bestCompatibility = 0;
+ for (int j = Math.min(otherGroups.size(), lastMatch) - 1; j >= 0; j--) {
+ MessagingGroup otherGroup = otherGroups.get(j);
+ int compatibility = ownGroup.calculateGroupCompatibility(otherGroup);
+ if (compatibility > bestCompatibility) {
+ bestCompatibility = compatibility;
+ bestMatch = otherGroup;
+ lastMatch = j;
+ }
+ }
+ if (bestMatch != null) {
+ mGroupMap.put(ownGroup, bestMatch);
+ }
+ }
+ return mGroupMap;
+ }
+
+ private boolean isGone(View view) {
+ if (view.getVisibility() == View.GONE) {
+ return true;
+ }
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MessagingLinearLayout.LayoutParams
+ && ((MessagingLinearLayout.LayoutParams) lp).hide) {
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setVisible(boolean visible, boolean force) {
+ resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+ setVisible(child, visible, force);
+ }
+ setVisible(ownGroup.getAvatar(), visible, force);
+ setVisible(ownGroup.getSender(), visible, force);
+ }
+ }
+ }
+
+ private void setVisible(View child, boolean visible, boolean force) {
+ if (isGone(child) || MessagingPropertyAnimator.isAnimatingAlpha(child)) {
+ return;
+ }
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.setVisible(visible, force);
+ ownState.recycle();
+ }
+
+ @Override
+ protected void resetTransformedView() {
+ super.resetTransformedView();
+ ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
+ for (int i = 0; i < ownGroups.size(); i++) {
+ MessagingGroup ownGroup = ownGroups.get(i);
+ if (!isGone(ownGroup)) {
+ MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
+ for (int j = 0; j < ownMessages.getChildCount(); j++) {
+ View child = ownMessages.getChildAt(j);
+ if (isGone(child)) {
+ continue;
+ }
+ resetTransformedView(child);
+ setClippingDeactivated(child, false);
+ }
+ resetTransformedView(ownGroup.getAvatar());
+ resetTransformedView(ownGroup.getSender());
+ setClippingDeactivated(ownGroup.getAvatar(), false);
+ setClippingDeactivated(ownGroup.getSender(), false);
+ ownGroup.setTranslationY(0);
+ ownGroup.getMessageContainer().setTranslationY(0);
+ }
+ }
+ }
+
+ private void resetTransformedView(View child) {
+ TransformState ownState = TransformState.createFrom(child, mTransformInfo);
+ ownState.resetTransformedView();
+ ownState.recycle();
+ }
+
+ @Override
+ protected void reset() {
+ super.reset();
+ mMessageContainer = null;
+ mMessagingLayout = null;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ mGroupMap.clear();;
+ sInstancePool.release(this);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1ca..fb5644f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,7 +16,9 @@
package com.android.systemui.statusbar.notification;
+import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
+import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -32,41 +34,20 @@
*/
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
- private View mContractedMessage;
- private ArrayList<View> mHistoricMessages = new ArrayList<View>();
+ private final int mMinHeightWithActions;
+ private MessagingLayout mMessagingLayout;
+ private MessagingLinearLayout mMessagingLinearLayout;
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mMessagingLayout = (MessagingLayout) view;
+ mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
+ R.dimen.notification_messaging_actions_min_height);
}
private void resolveViews() {
- mContractedMessage = null;
-
- View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
- if (container instanceof MessagingLinearLayout
- && ((MessagingLinearLayout) container).getChildCount() > 0) {
- MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
- int childCount = messagingContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = messagingContainer.getChildAt(i);
-
- if (child.getVisibility() == View.GONE
- && child instanceof TextView
- && !TextUtils.isEmpty(((TextView) child).getText())) {
- mHistoricMessages.add(child);
- }
-
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
- } else if (child.getVisibility() == View.VISIBLE) {
- break;
- }
- }
- }
+ mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
}
@Override
@@ -81,16 +62,22 @@
protected void updateTransformedTypes() {
// This also clears the existing types
super.updateTransformedTypes();
- if (mContractedMessage != null) {
- mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TEXT,
- mContractedMessage);
+ if (mMessagingLinearLayout != null) {
+ mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
+ mMessagingLinearLayout);
}
}
@Override
public void setRemoteInputVisible(boolean visible) {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
- mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
+ mMessagingLayout.showHistoricMessages(visible);
+ }
+
+ @Override
+ public int getMinLayoutHeight() {
+ if (mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE) {
+ return mMinHeightWithActions;
}
+ return super.getMinLayoutHeight();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index bb979eb..fd085d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -41,7 +41,7 @@
private ProgressBar mProgressBar;
private TextView mTitle;
private TextView mText;
- private View mActionsContainer;
+ protected View mActionsContainer;
private View mReplyAction;
private Rect mTmpRect = new Rect();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 3115361..af393c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -66,4 +66,14 @@
Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
}
+ /**
+ * @param dimenId the dimen to look up
+ * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp
+ */
+ public static int getFontScaledHeight(Context context, int dimenId) {
+ int dimensionPixelSize = context.getResources().getDimensionPixelSize(dimenId);
+ float factor = Math.max(1.0f, context.getResources().getDisplayMetrics().scaledDensity /
+ context.getResources().getDisplayMetrics().density);
+ return (int) (dimensionPixelSize * factor);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 5200d69..1cd5f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,4 +190,8 @@
public boolean disallowSingleClick(float x, float y) {
return false;
}
+
+ public int getMinLayoutHeight() {
+ return 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
index c4aabe4..178c813 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TextViewTransformState.java
@@ -33,8 +33,8 @@
private TextView mText;
@Override
- public void initFrom(View view) {
- super.initFrom(view);
+ public void initFrom(View view, TransformInfo transformInfo) {
+ super.initFrom(view, transformInfo);
if (view instanceof TextView) {
mText = (TextView) view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index bafedff..ad07af0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,8 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import com.android.internal.widget.MessagingPropertyAnimator;
+import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -43,23 +45,46 @@
public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
private static final float UNDEFINED = -1f;
- private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
- private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
- private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
+ private static ViewClippingUtil.ClippingParameters CLIPPING_PARAMETERS
+ = new ViewClippingUtil.ClippingParameters() {
+ @Override
+ public boolean shouldFinish(View view) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ return !row.isChildInGroup();
+ }
+ return false;
+ }
+
+ @Override
+ public void onClippingStateChanged(View view, boolean isClipping) {
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isClipping) {
+ row.setClipToActualHeight(true);
+ } else if (row.isChildInGroup()) {
+ row.setClipToActualHeight(false);
+ }
+ }
+ }
+ };
protected View mTransformedView;
+ protected TransformInfo mTransformInfo;
private int[] mOwnPosition = new int[2];
private boolean mSameAsAny;
private float mTransformationEndY = UNDEFINED;
private float mTransformationEndX = UNDEFINED;
+ private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
- public void initFrom(View view) {
+ public void initFrom(View view, TransformInfo transformInfo) {
mTransformedView = view;
+ mTransformInfo = transformInfo;
}
/**
@@ -108,13 +133,16 @@
final View transformedView = mTransformedView;
boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
- boolean transformScale = transformScale(otherState);
+ boolean differentHeight = otherState.getViewHeight() != getViewHeight();
+ boolean differentWidth = otherState.getViewWidth() != getViewWidth();
+ boolean transformScale = transformScale(otherState) && (differentHeight || differentWidth);
// lets animate the positions correctly
if (transformationAmount == 0.0f
|| transformX && getTransformationStartX() == UNDEFINED
|| transformY && getTransformationStartY() == UNDEFINED
- || transformScale && getTransformationStartScaleX() == UNDEFINED
- || transformScale && getTransformationStartScaleY() == UNDEFINED) {
+ || transformScale && getTransformationStartScaleX() == UNDEFINED && differentWidth
+ || transformScale && getTransformationStartScaleY() == UNDEFINED
+ && differentHeight) {
int[] otherPosition;
if (transformationAmount != 0.0f) {
otherPosition = otherState.getLaidOutLocationOnScreen();
@@ -132,14 +160,14 @@
}
// we also want to animate the scale if we're the same
View otherView = otherState.getTransformedView();
- if (transformScale && otherState.getViewWidth() != getViewWidth()) {
+ if (transformScale && differentWidth) {
setTransformationStartScaleX(otherState.getViewWidth() * otherView.getScaleX()
/ (float) getViewWidth());
transformedView.setPivotX(0);
} else {
setTransformationStartScaleX(UNDEFINED);
}
- if (transformScale && otherState.getViewHeight() != getViewHeight()) {
+ if (transformScale && differentHeight) {
setTransformationStartScaleY(otherState.getViewHeight() * otherView.getScaleY()
/ (float) getViewHeight());
transformedView.setPivotY(0);
@@ -159,7 +187,7 @@
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
if (transformX) {
float interpolation = interpolatedValue;
@@ -297,7 +325,7 @@
}
setClippingDeactivated(transformedView, true);
}
- float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+ float interpolatedValue = mDefaultInterpolator.getInterpolation(
transformationAmount);
int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
int[] ownPosition = getLaidOutLocationOnScreen();
@@ -354,59 +382,8 @@
}
}
- public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
- if (!(transformedView.getParent() instanceof ViewGroup)) {
- return;
- }
- ViewGroup view = (ViewGroup) transformedView.getParent();
- while (true) {
- ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
- if (clipSet == null) {
- clipSet = new ArraySet<>();
- view.setTag(CLIP_CLIPPING_SET, clipSet);
- }
- Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
- if (clipChildren == null) {
- clipChildren = view.getClipChildren();
- view.setTag(CLIP_CHILDREN_TAG, clipChildren);
- }
- Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
- if (clipToPadding == null) {
- clipToPadding = view.getClipToPadding();
- view.setTag(CLIP_TO_PADDING, clipToPadding);
- }
- ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) view
- : null;
- if (!deactivated) {
- clipSet.remove(transformedView);
- if (clipSet.isEmpty()) {
- view.setClipChildren(clipChildren);
- view.setClipToPadding(clipToPadding);
- view.setTag(CLIP_CLIPPING_SET, null);
- if (row != null) {
- row.setClipToActualHeight(true);
- }
- }
- } else {
- clipSet.add(transformedView);
- view.setClipChildren(false);
- view.setClipToPadding(false);
- if (row != null && row.isChildInGroup()) {
- // We still want to clip to the parent's height
- row.setClipToActualHeight(false);
- }
- }
- if (row != null && !row.isChildInGroup()) {
- return;
- }
- final ViewParent parent = view.getParent();
- if (parent instanceof ViewGroup) {
- view = (ViewGroup) parent;
- } else {
- return;
- }
- }
+ protected void setClippingDeactivated(final View transformedView, boolean deactivated) {
+ ViewClippingUtil.setClippingDeactivated(transformedView, deactivated, CLIPPING_PARAMETERS);
}
public int[] getLaidOutLocationOnScreen() {
@@ -423,6 +400,9 @@
// remove scale
mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
+
+ // Remove local translations
+ mOwnPosition[1] -= MessagingPropertyAnimator.getLocalTranslationY(mTransformedView);
return mOwnPosition;
}
@@ -444,20 +424,26 @@
CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
}
- public static TransformState createFrom(View view) {
+ public static TransformState createFrom(View view,
+ TransformInfo transformInfo) {
if (view instanceof TextView) {
TextViewTransformState result = TextViewTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
if (view.getId() == com.android.internal.R.id.actions_container) {
ActionListTransformState result = ActionListTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
+ return result;
+ }
+ if (view.getId() == com.android.internal.R.id.notification_messaging) {
+ MessagingLayoutTransformState result = MessagingLayoutTransformState.obtain();
+ result.initFrom(view, transformInfo);
return result;
}
if (view instanceof ImageView) {
ImageTransformState result = ImageTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
if (view.getId() == com.android.internal.R.id.reply_icon_action) {
((TransformState) result).setIsSameAsAnyView(true);
}
@@ -465,15 +451,15 @@
}
if (view instanceof ProgressBar) {
ProgressTransformState result = ProgressTransformState.obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
TransformState result = obtain();
- result.initFrom(view);
+ result.initFrom(view, transformInfo);
return result;
}
- private void setIsSameAsAnyView(boolean sameAsAny) {
+ public void setIsSameAsAnyView(boolean sameAsAny) {
mSameAsAny = sameAsAny;
}
@@ -533,6 +519,7 @@
mSameAsAny = false;
mTransformationEndX = UNDEFINED;
mTransformationEndY = UNDEFINED;
+ mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
}
public void setVisible(boolean visible, boolean force) {
@@ -578,4 +565,12 @@
public View getTransformedView() {
return mTransformedView;
}
+
+ public void setDefaultInterpolator(Interpolator interpolator) {
+ mDefaultInterpolator = interpolator;
+ }
+
+ public interface TransformInfo {
+ boolean isAnimating();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d226fed..1c361ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -175,9 +175,8 @@
private boolean isLight(int vis, int barMode, int flag) {
boolean isTransparentBar = (barMode == MODE_TRANSPARENT
|| barMode == MODE_LIGHTS_OUT_TRANSPARENT);
- boolean allowLight = isTransparentBar && !mBatteryController.isPowerSave();
boolean light = (vis & flag) != 0;
- return allowLight && light;
+ return isTransparentBar && light;
}
private boolean animateChange() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4a58c6d..46e3aa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,6 +146,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -273,7 +275,7 @@
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+ SystemProperties.getBoolean("debug.force_remoteinput_history", true);
private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1232,6 +1234,8 @@
}
public void onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache();
+ MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
@@ -1685,8 +1689,9 @@
clearCurrentMediaNotification();
updateMediaMetaData(true, true);
}
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
- Entry entry = mNotificationData.get(key);
+ Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
StatusBarNotification sbn = entry.notification;
Notification.Builder b = Notification.Builder
@@ -1722,6 +1727,7 @@
deferRemoval = false;
}
if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
mKeysKeptForRemoteInput.add(entry.key);
return;
}
@@ -1731,7 +1737,6 @@
mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
return;
}
- Entry entry = mNotificationData.get(key);
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {
@@ -3278,12 +3283,8 @@
}
void checkBarMode(int mode, int windowState, BarTransitions transitions) {
- final boolean powerSave = mBatteryController.isPowerSave();
final boolean anim = !mNoAnimationOnNextBarModeChange && mDeviceInteractive
- && windowState != WINDOW_STATE_HIDDEN && !powerSave;
- if (powerSave && getBarState() == StatusBarState.SHADE) {
- mode = MODE_WARNING;
- }
+ && windowState != WINDOW_STATE_HIDDEN;
transitions.transitionTo(mode, anim);
}
@@ -7117,7 +7118,7 @@
mAllowLockscreenRemoteInput = allowLockscreenRemoteInput;
}
- private void updateLockscreenNotificationSetting() {
+ protected void updateLockscreenNotificationSetting() {
final boolean show = Settings.Secure.getIntForUser(mContext.getContentResolver(),
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
1,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
index 720e9f0..a17a95f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStatePreventingAdapterTest.java
@@ -70,7 +70,7 @@
@Test
public void forwards_setDozeScreenState_doze_suspend() throws Exception {
mWrapper.setDozeScreenState(Display.STATE_DOZE_SUSPEND);
- verify(mInner).setDozeScreenState(Display.STATE_ON);
+ verify(mInner).setDozeScreenState(Display.STATE_ON_SUSPEND);
}
@Test
@@ -95,4 +95,4 @@
assertSame(mInner, DozeScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
index d78e739..ed93561 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuspendScreenStatePreventingAdapterTest.java
@@ -74,6 +74,12 @@
}
@Test
+ public void forwards_setDozeScreenState_on_suspend() throws Exception {
+ mWrapper.setDozeScreenState(Display.STATE_ON_SUSPEND);
+ verify(mInner).setDozeScreenState(Display.STATE_ON_SUSPEND);
+ }
+
+ @Test
public void forwards_requestWakeUp() throws Exception {
mWrapper.requestWakeUp();
verify(mInner).requestWakeUp();
@@ -95,4 +101,4 @@
assertSame(mInner, DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(mInner, params));
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
new file mode 100644
index 0000000..4437aac
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.keyguard;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
+import android.app.slice.SliceQuery;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper(setAsMainLooper = true)
+public class KeyguardSliceProviderTest extends SysuiTestCase {
+
+ private TestableKeyguardSliceProvider mProvider;
+
+ @Before
+ public void setup() {
+ mProvider = new TestableKeyguardSliceProvider();
+ mProvider.attachInfo(getContext(), null);
+ }
+
+ @Test
+ public void registersClockUpdate() {
+ Assert.assertTrue("registerClockUpdate should have been called during initialization.",
+ mProvider.isRegistered());
+ }
+
+ @Test
+ public void unregisterClockUpdate() {
+ mProvider.unregisterClockUpdate();
+ Assert.assertFalse("Clock updates should have been unregistered.",
+ mProvider.isRegistered());
+ }
+
+ @Test
+ public void returnsValidSlice() {
+ Slice slice = mProvider.onBindSlice(Uri.parse(KeyguardSliceProvider.KEYGUARD_SLICE_URI));
+ SliceItem text = SliceQuery.find(slice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE,
+ null /* nonHints */);
+ Assert.assertNotNull("Slice must provide a title.", text);
+ }
+
+ @Test
+ public void cleansDateFormat() {
+ mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIMEZONE_CHANGED));
+ TestableLooper.get(this).processAllMessages();
+ Assert.assertEquals("Date format should have been cleaned.", 1 /* expected */,
+ mProvider.mCleanDateFormatInvokations);
+ }
+
+ @Test
+ public void updatesClock() {
+ mProvider.mIntentReceiver.onReceive(getContext(), new Intent(Intent.ACTION_TIME_TICK));
+ TestableLooper.get(this).processAllMessages();
+ Assert.assertEquals("Clock should have been updated.", 2 /* expected */,
+ mProvider.mUpdateClockInvokations);
+ }
+
+ private class TestableKeyguardSliceProvider extends KeyguardSliceProvider {
+ int mCleanDateFormatInvokations;
+ int mUpdateClockInvokations;
+
+ TestableKeyguardSliceProvider() {
+ super(new Handler(TestableLooper.get(KeyguardSliceProviderTest.this).getLooper()));
+ }
+
+ @Override
+ void cleanDateFormat() {
+ super.cleanDateFormat();
+ mCleanDateFormatInvokations++;
+ }
+
+ @Override
+ protected void updateClock() {
+ super.updateClock();
+ mUpdateClockInvokations++;
+ }
+ }
+
+}
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 1b61866..ea0ed27 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -38,7 +38,7 @@
import android.content.pm.PackageManager;
import android.hidl.manager.V1_0.IServiceManager;
import android.hidl.manager.V1_0.IServiceNotification;
-import android.hardware.health.V2_0.HealthInfo;
+import android.hardware.health.V1_0.HealthInfo;
import android.hardware.health.V2_0.IHealthInfoCallback;
import android.hardware.health.V2_0.IHealth;
import android.hardware.health.V2_0.Result;
@@ -49,6 +49,7 @@
import android.os.Binder;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.IBatteryPropertiesListener;
import android.os.IBatteryPropertiesRegistrar;
import android.os.IBinder;
@@ -75,6 +76,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -176,6 +178,7 @@
private HealthServiceWrapper mHealthServiceWrapper;
private HealthHalCallback mHealthHalCallback;
private BatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
+ private HandlerThread mHandlerThread;
public BatteryService(Context context) {
super(context);
@@ -308,16 +311,16 @@
private boolean isPoweredLocked(int plugTypeSet) {
// assume we are powered if battery state is unknown so
// the "stay on while plugged in" option will work.
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.legacy.chargerAcOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mHealthInfo.chargerAcOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.legacy.chargerUsbOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mHealthInfo.chargerUsbOnline) {
return true;
}
- if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.legacy.chargerWirelessOnline) {
+ if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mHealthInfo.chargerWirelessOnline) {
return true;
}
return false;
@@ -334,15 +337,15 @@
* (becomes <= mLowBatteryWarningLevel).
*/
return !plugged
- && mHealthInfo.legacy.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel
+ && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
&& (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
}
private void shutdownIfNoPowerLocked() {
// shut down gracefully if our battery is critically low and we are not powered.
// wait until the system has booted before attempting to display the shutdown dialog.
- if (mHealthInfo.legacy.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+ if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -363,7 +366,7 @@
// shut down gracefully if temperature is too high (> 68.0C by default)
// wait until the system has booted before attempting to display the
// shutdown dialog.
- if (mHealthInfo.legacy.batteryTemperature > mShutdownBatteryTemperature) {
+ if (mHealthInfo.batteryTemperature > mShutdownBatteryTemperature) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -396,37 +399,34 @@
}
private static void copy(HealthInfo dst, HealthInfo src) {
- dst.legacy.chargerAcOnline = src.legacy.chargerAcOnline;
- dst.legacy.chargerUsbOnline = src.legacy.chargerUsbOnline;
- dst.legacy.chargerWirelessOnline = src.legacy.chargerWirelessOnline;
- dst.legacy.maxChargingCurrent = src.legacy.maxChargingCurrent;
- dst.legacy.maxChargingVoltage = src.legacy.maxChargingVoltage;
- dst.legacy.batteryStatus = src.legacy.batteryStatus;
- dst.legacy.batteryHealth = src.legacy.batteryHealth;
- dst.legacy.batteryPresent = src.legacy.batteryPresent;
- dst.legacy.batteryLevel = src.legacy.batteryLevel;
- dst.legacy.batteryVoltage = src.legacy.batteryVoltage;
- dst.legacy.batteryTemperature = src.legacy.batteryTemperature;
- dst.legacy.batteryCurrent = src.legacy.batteryCurrent;
- dst.legacy.batteryCycleCount = src.legacy.batteryCycleCount;
- dst.legacy.batteryFullCharge = src.legacy.batteryFullCharge;
- dst.legacy.batteryChargeCounter = src.legacy.batteryChargeCounter;
- dst.legacy.batteryTechnology = src.legacy.batteryTechnology;
- dst.batteryCurrentAverage = src.batteryCurrentAverage;
- dst.batteryCapacity = src.batteryCapacity;
- dst.energyCounter = src.energyCounter;
+ dst.chargerAcOnline = src.chargerAcOnline;
+ dst.chargerUsbOnline = src.chargerUsbOnline;
+ dst.chargerWirelessOnline = src.chargerWirelessOnline;
+ dst.maxChargingCurrent = src.maxChargingCurrent;
+ dst.maxChargingVoltage = src.maxChargingVoltage;
+ dst.batteryStatus = src.batteryStatus;
+ dst.batteryHealth = src.batteryHealth;
+ dst.batteryPresent = src.batteryPresent;
+ dst.batteryLevel = src.batteryLevel;
+ dst.batteryVoltage = src.batteryVoltage;
+ dst.batteryTemperature = src.batteryTemperature;
+ dst.batteryCurrent = src.batteryCurrent;
+ dst.batteryCycleCount = src.batteryCycleCount;
+ dst.batteryFullCharge = src.batteryFullCharge;
+ dst.batteryChargeCounter = src.batteryChargeCounter;
+ dst.batteryTechnology = src.batteryTechnology;
}
private void processValuesLocked(boolean force) {
boolean logOutlier = false;
long dischargeDuration = 0;
- mBatteryLevelCritical = (mHealthInfo.legacy.batteryLevel <= mCriticalBatteryLevel);
- if (mHealthInfo.legacy.chargerAcOnline) {
+ mBatteryLevelCritical = (mHealthInfo.batteryLevel <= mCriticalBatteryLevel);
+ if (mHealthInfo.chargerAcOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
} else {
mPlugType = BATTERY_PLUGGED_NONE;
@@ -441,10 +441,10 @@
// Let the battery stats keep track of the current level.
try {
- mBatteryStats.setBatteryState(mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth,
- mPlugType, mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryTemperature,
- mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryChargeCounter,
- mHealthInfo.legacy.batteryFullCharge);
+ mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+ mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
+ mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
+ mHealthInfo.batteryFullCharge);
} catch (RemoteException e) {
// Should never happen.
}
@@ -452,16 +452,16 @@
shutdownIfNoPowerLocked();
shutdownIfOverTempLocked();
- if (force || (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
- mHealthInfo.legacy.batteryLevel != mLastBatteryLevel ||
+ if (force || (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
+ mHealthInfo.batteryLevel != mLastBatteryLevel ||
mPlugType != mLastPlugType ||
- mHealthInfo.legacy.batteryVoltage != mLastBatteryVoltage ||
- mHealthInfo.legacy.batteryTemperature != mLastBatteryTemperature ||
- mHealthInfo.legacy.maxChargingCurrent != mLastMaxChargingCurrent ||
- mHealthInfo.legacy.maxChargingVoltage != mLastMaxChargingVoltage ||
- mHealthInfo.legacy.batteryChargeCounter != mLastChargeCounter ||
+ mHealthInfo.batteryVoltage != mLastBatteryVoltage ||
+ mHealthInfo.batteryTemperature != mLastBatteryTemperature ||
+ mHealthInfo.maxChargingCurrent != mLastMaxChargingCurrent ||
+ mHealthInfo.maxChargingVoltage != mLastMaxChargingVoltage ||
+ mHealthInfo.batteryChargeCounter != mLastChargeCounter ||
mInvalidCharger != mLastInvalidCharger)) {
if (mPlugType != mLastPlugType) {
@@ -470,33 +470,33 @@
// There's no value in this data unless we've discharged at least once and the
// battery level has changed; so don't log until it does.
- if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.legacy.batteryLevel) {
+ if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
logOutlier = true;
EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
- mDischargeStartLevel, mHealthInfo.legacy.batteryLevel);
+ mDischargeStartLevel, mHealthInfo.batteryLevel);
// make sure we see a discharge event before logging again
mDischargeStartTime = 0;
}
} else if (mPlugType == BATTERY_PLUGGED_NONE) {
// charging -> discharging or we just powered up
mDischargeStartTime = SystemClock.elapsedRealtime();
- mDischargeStartLevel = mHealthInfo.legacy.batteryLevel;
+ mDischargeStartLevel = mHealthInfo.batteryLevel;
}
}
- if (mHealthInfo.legacy.batteryStatus != mLastBatteryStatus ||
- mHealthInfo.legacy.batteryHealth != mLastBatteryHealth ||
- mHealthInfo.legacy.batteryPresent != mLastBatteryPresent ||
+ if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
+ mHealthInfo.batteryHealth != mLastBatteryHealth ||
+ mHealthInfo.batteryPresent != mLastBatteryPresent ||
mPlugType != mLastPlugType) {
EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
- mHealthInfo.legacy.batteryStatus, mHealthInfo.legacy.batteryHealth, mHealthInfo.legacy.batteryPresent ? 1 : 0,
- mPlugType, mHealthInfo.legacy.batteryTechnology);
+ mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+ mPlugType, mHealthInfo.batteryTechnology);
}
- if (mHealthInfo.legacy.batteryLevel != mLastBatteryLevel) {
+ if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
// Don't do this just from voltage or temperature changes, that is
// too noisy.
EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
- mHealthInfo.legacy.batteryLevel, mHealthInfo.legacy.batteryVoltage, mHealthInfo.legacy.batteryTemperature);
+ mHealthInfo.batteryLevel, mHealthInfo.batteryVoltage, mHealthInfo.batteryTemperature);
}
if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
mPlugType == BATTERY_PLUGGED_NONE) {
@@ -509,16 +509,16 @@
if (!mBatteryLevelLow) {
// Should we now switch in to low battery mode?
if (mPlugType == BATTERY_PLUGGED_NONE
- && mHealthInfo.legacy.batteryLevel <= mLowBatteryWarningLevel) {
+ && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
mBatteryLevelLow = true;
}
} else {
// Should we now switch out of low battery mode?
if (mPlugType != BATTERY_PLUGGED_NONE) {
mBatteryLevelLow = false;
- } else if (mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mBatteryLevelLow = false;
- } else if (force && mHealthInfo.legacy.batteryLevel >= mLowBatteryWarningLevel) {
+ } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
// If being forced, the previous state doesn't matter, we will just
// absolutely check to see if we are now above the warning level.
mBatteryLevelLow = false;
@@ -565,7 +565,7 @@
}
});
} else if (mSentLowBatteryBroadcast &&
- mHealthInfo.legacy.batteryLevel >= mLowBatteryCloseWarningLevel) {
+ mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
mSentLowBatteryBroadcast = false;
final Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
@@ -591,16 +591,16 @@
logOutlierLocked(dischargeDuration);
}
- mLastBatteryStatus = mHealthInfo.legacy.batteryStatus;
- mLastBatteryHealth = mHealthInfo.legacy.batteryHealth;
- mLastBatteryPresent = mHealthInfo.legacy.batteryPresent;
- mLastBatteryLevel = mHealthInfo.legacy.batteryLevel;
+ mLastBatteryStatus = mHealthInfo.batteryStatus;
+ mLastBatteryHealth = mHealthInfo.batteryHealth;
+ mLastBatteryPresent = mHealthInfo.batteryPresent;
+ mLastBatteryLevel = mHealthInfo.batteryLevel;
mLastPlugType = mPlugType;
- mLastBatteryVoltage = mHealthInfo.legacy.batteryVoltage;
- mLastBatteryTemperature = mHealthInfo.legacy.batteryTemperature;
- mLastMaxChargingCurrent = mHealthInfo.legacy.maxChargingCurrent;
- mLastMaxChargingVoltage = mHealthInfo.legacy.maxChargingVoltage;
- mLastChargeCounter = mHealthInfo.legacy.batteryChargeCounter;
+ mLastBatteryVoltage = mHealthInfo.batteryVoltage;
+ mLastBatteryTemperature = mHealthInfo.batteryTemperature;
+ mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrent;
+ mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltage;
+ mLastChargeCounter = mHealthInfo.batteryChargeCounter;
mLastBatteryLevelCritical = mBatteryLevelCritical;
mLastInvalidCharger = mInvalidCharger;
}
@@ -612,23 +612,23 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
- int icon = getIconLocked(mHealthInfo.legacy.batteryLevel);
+ int icon = getIconLocked(mHealthInfo.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
- intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.legacy.batteryStatus);
- intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.legacy.batteryHealth);
- intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.legacy.batteryPresent);
- intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.legacy.batteryLevel);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, mHealthInfo.batteryStatus);
+ intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
+ intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
+ intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
- intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mHealthInfo.batteryVoltage);
+ intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mHealthInfo.batteryTemperature);
+ intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mHealthInfo.batteryTechnology);
intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ intent.putExtra(BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ intent.putExtra(BatteryManager.EXTRA_CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
if (DEBUG) {
Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. scale:" + BATTERY_SCALE
+ ", info:" + mHealthInfo.toString());
@@ -692,14 +692,14 @@
long durationThreshold = Long.parseLong(durationThresholdString);
int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
if (duration <= durationThreshold &&
- mDischargeStartLevel - mHealthInfo.legacy.batteryLevel >= dischargeThreshold) {
+ mDischargeStartLevel - mHealthInfo.batteryLevel >= dischargeThreshold) {
// If the discharge cycle is bad enough we want to know about it.
logBatteryStatsLocked();
}
if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
" discharge threshold: " + dischargeThreshold);
if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
- (mDischargeStartLevel - mHealthInfo.legacy.batteryLevel));
+ (mDischargeStartLevel - mHealthInfo.batteryLevel));
} catch (NumberFormatException e) {
Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
durationThresholdString + " or " + dischargeThresholdString);
@@ -708,14 +708,14 @@
}
private int getIconLocked(int level) {
- if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+ if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
return com.android.internal.R.drawable.stat_sys_battery;
- } else if (mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
- || mHealthInfo.legacy.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+ } else if (mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+ || mHealthInfo.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
- && mHealthInfo.legacy.batteryLevel >= 100) {
+ && mHealthInfo.batteryLevel >= 100) {
return com.android.internal.R.drawable.stat_sys_battery_charge;
} else {
return com.android.internal.R.drawable.stat_sys_battery;
@@ -779,9 +779,9 @@
if (!mUpdatesStopped) {
copy(mLastHealthInfo, mHealthInfo);
}
- mHealthInfo.legacy.chargerAcOnline = false;
- mHealthInfo.legacy.chargerUsbOnline = false;
- mHealthInfo.legacy.chargerWirelessOnline = false;
+ mHealthInfo.chargerAcOnline = false;
+ mHealthInfo.chargerUsbOnline = false;
+ mHealthInfo.chargerWirelessOnline = false;
long ident = Binder.clearCallingIdentity();
try {
mUpdatesStopped = true;
@@ -813,25 +813,25 @@
boolean update = true;
switch (key) {
case "present":
- mHealthInfo.legacy.batteryPresent = Integer.parseInt(value) != 0;
+ mHealthInfo.batteryPresent = Integer.parseInt(value) != 0;
break;
case "ac":
- mHealthInfo.legacy.chargerAcOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerAcOnline = Integer.parseInt(value) != 0;
break;
case "usb":
- mHealthInfo.legacy.chargerUsbOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerUsbOnline = Integer.parseInt(value) != 0;
break;
case "wireless":
- mHealthInfo.legacy.chargerWirelessOnline = Integer.parseInt(value) != 0;
+ mHealthInfo.chargerWirelessOnline = Integer.parseInt(value) != 0;
break;
case "status":
- mHealthInfo.legacy.batteryStatus = Integer.parseInt(value);
+ mHealthInfo.batteryStatus = Integer.parseInt(value);
break;
case "level":
- mHealthInfo.legacy.batteryLevel = Integer.parseInt(value);
+ mHealthInfo.batteryLevel = Integer.parseInt(value);
break;
case "temp":
- mHealthInfo.legacy.batteryTemperature = Integer.parseInt(value);
+ mHealthInfo.batteryTemperature = Integer.parseInt(value);
break;
case "invalid":
mInvalidCharger = Integer.parseInt(value);
@@ -890,20 +890,20 @@
if (mUpdatesStopped) {
pw.println(" (UPDATES STOPPED -- use 'reset' to restart)");
}
- pw.println(" AC powered: " + mHealthInfo.legacy.chargerAcOnline);
- pw.println(" USB powered: " + mHealthInfo.legacy.chargerUsbOnline);
- pw.println(" Wireless powered: " + mHealthInfo.legacy.chargerWirelessOnline);
- pw.println(" Max charging current: " + mHealthInfo.legacy.maxChargingCurrent);
- pw.println(" Max charging voltage: " + mHealthInfo.legacy.maxChargingVoltage);
- pw.println(" Charge counter: " + mHealthInfo.legacy.batteryChargeCounter);
- pw.println(" status: " + mHealthInfo.legacy.batteryStatus);
- pw.println(" health: " + mHealthInfo.legacy.batteryHealth);
- pw.println(" present: " + mHealthInfo.legacy.batteryPresent);
- pw.println(" level: " + mHealthInfo.legacy.batteryLevel);
+ pw.println(" AC powered: " + mHealthInfo.chargerAcOnline);
+ pw.println(" USB powered: " + mHealthInfo.chargerUsbOnline);
+ pw.println(" Wireless powered: " + mHealthInfo.chargerWirelessOnline);
+ pw.println(" Max charging current: " + mHealthInfo.maxChargingCurrent);
+ pw.println(" Max charging voltage: " + mHealthInfo.maxChargingVoltage);
+ pw.println(" Charge counter: " + mHealthInfo.batteryChargeCounter);
+ pw.println(" status: " + mHealthInfo.batteryStatus);
+ pw.println(" health: " + mHealthInfo.batteryHealth);
+ pw.println(" present: " + mHealthInfo.batteryPresent);
+ pw.println(" level: " + mHealthInfo.batteryLevel);
pw.println(" scale: " + BATTERY_SCALE);
- pw.println(" voltage: " + mHealthInfo.legacy.batteryVoltage);
- pw.println(" temperature: " + mHealthInfo.legacy.batteryTemperature);
- pw.println(" technology: " + mHealthInfo.legacy.batteryTechnology);
+ pw.println(" voltage: " + mHealthInfo.batteryVoltage);
+ pw.println(" temperature: " + mHealthInfo.batteryTemperature);
+ pw.println(" technology: " + mHealthInfo.batteryTechnology);
} else {
Shell shell = new Shell();
shell.exec(mBinderService, null, fd, null, args, null, new ResultReceiver(null));
@@ -917,25 +917,25 @@
synchronized (mLock) {
proto.write(BatteryServiceDumpProto.ARE_UPDATES_STOPPED, mUpdatesStopped);
int batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_NONE;
- if (mHealthInfo.legacy.chargerAcOnline) {
+ if (mHealthInfo.chargerAcOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_AC;
- } else if (mHealthInfo.legacy.chargerUsbOnline) {
+ } else if (mHealthInfo.chargerUsbOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_USB;
- } else if (mHealthInfo.legacy.chargerWirelessOnline) {
+ } else if (mHealthInfo.chargerWirelessOnline) {
batteryPluggedValue = BatteryManagerProto.PLUG_TYPE_WIRELESS;
}
proto.write(BatteryServiceDumpProto.PLUGGED, batteryPluggedValue);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.legacy.maxChargingCurrent);
- proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.legacy.maxChargingVoltage);
- proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.legacy.batteryChargeCounter);
- proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.legacy.batteryStatus);
- proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.legacy.batteryHealth);
- proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.legacy.batteryPresent);
- proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.legacy.batteryLevel);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_CURRENT, mHealthInfo.maxChargingCurrent);
+ proto.write(BatteryServiceDumpProto.MAX_CHARGING_VOLTAGE, mHealthInfo.maxChargingVoltage);
+ proto.write(BatteryServiceDumpProto.CHARGE_COUNTER, mHealthInfo.batteryChargeCounter);
+ proto.write(BatteryServiceDumpProto.STATUS, mHealthInfo.batteryStatus);
+ proto.write(BatteryServiceDumpProto.HEALTH, mHealthInfo.batteryHealth);
+ proto.write(BatteryServiceDumpProto.IS_PRESENT, mHealthInfo.batteryPresent);
+ proto.write(BatteryServiceDumpProto.LEVEL, mHealthInfo.batteryLevel);
proto.write(BatteryServiceDumpProto.SCALE, BATTERY_SCALE);
- proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.legacy.batteryVoltage);
- proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.legacy.batteryTemperature);
- proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.legacy.batteryTechnology);
+ proto.write(BatteryServiceDumpProto.VOLTAGE, mHealthInfo.batteryVoltage);
+ proto.write(BatteryServiceDumpProto.TEMPERATURE, mHealthInfo.batteryTemperature);
+ proto.write(BatteryServiceDumpProto.TECHNOLOGY, mHealthInfo.batteryTechnology);
}
proto.flush();
}
@@ -976,8 +976,8 @@
* Synchronize on BatteryService.
*/
public void updateLightsLocked() {
- final int level = mHealthInfo.legacy.batteryLevel;
- final int status = mHealthInfo.legacy.batteryStatus;
+ final int level = mHealthInfo.batteryLevel;
+ final int status = mHealthInfo.batteryStatus;
if (level < mLowBatteryWarningLevel) {
if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
// Solid red when battery is charging
@@ -1154,7 +1154,7 @@
@Override
public int getBatteryLevel() {
synchronized (mLock) {
- return mHealthInfo.legacy.batteryLevel;
+ return mHealthInfo.batteryLevel;
}
}
@@ -1195,14 +1195,13 @@
Arrays.asList(INSTANCE_VENDOR, INSTANCE_HEALTHD);
private final IServiceNotification mNotification = new Notification();
+ private final HandlerThread mHandlerThread = new HandlerThread("HealthServiceRefresh");
// These variables are fixed after init.
private Callback mCallback;
private IHealthSupplier mHealthSupplier;
private String mInstanceName;
- private final Object mLastServiceSetLock = new Object();
// Last IHealth service received.
- // set must be also be guarded with mLastServiceSetLock to ensure ordering.
private final AtomicReference<IHealth> mLastService = new AtomicReference<>();
/**
@@ -1220,6 +1219,10 @@
* Start monitoring registration of new IHealth services. Only instances that are in
* {@code sAllInstances} and in device / framework manifest are used. This function should
* only be called once.
+ *
+ * mCallback.onRegistration() is called synchronously (aka in init thread) before
+ * this method returns.
+ *
* @throws RemoteException transaction error when talking to IServiceManager
* @throws NoSuchElementException if one of the following cases:
* - No service manager;
@@ -1240,40 +1243,48 @@
mCallback = callback;
mHealthSupplier = healthSupplier;
- traceBegin("HealthInitGetManager");
- try {
- manager = managerSupplier.get();
- } finally {
- traceEnd();
- }
+ // Initialize mLastService and call callback for the first time (in init thread)
+ IHealth newService = null;
for (String name : sAllInstances) {
- traceBegin("HealthInitGetTransport_" + name);
+ traceBegin("HealthInitGetService_" + name);
try {
- if (manager.getTransport(IHealth.kInterfaceName, name) !=
- IServiceManager.Transport.EMPTY) {
- mInstanceName = name;
- break;
- }
+ newService = healthSupplier.get(name);
+ } catch (NoSuchElementException ex) {
+ /* ignored, handled below */
} finally {
traceEnd();
}
+ if (newService != null) {
+ mInstanceName = name;
+ mLastService.set(newService);
+ break;
+ }
}
- if (mInstanceName == null) {
+ if (mInstanceName == null || newService == null) {
throw new NoSuchElementException(String.format(
"No IHealth service instance among %s is available. Perhaps no permission?",
sAllInstances.toString()));
}
+ mCallback.onRegistration(null, newService, mInstanceName);
+ // Register for future service registrations
traceBegin("HealthInitRegisterNotification");
+ mHandlerThread.start();
try {
- manager.registerForNotifications(IHealth.kInterfaceName, mInstanceName, mNotification);
+ managerSupplier.get().registerForNotifications(
+ IHealth.kInterfaceName, mInstanceName, mNotification);
} finally {
traceEnd();
}
Slog.i(TAG, "health: HealthServiceWrapper listening to instance " + mInstanceName);
}
+ @VisibleForTesting
+ HandlerThread getHandlerThread() {
+ return mHandlerThread;
+ }
+
interface Callback {
/**
* This function is invoked asynchronously when a new and related IServiceNotification
@@ -1302,7 +1313,7 @@
*/
interface IHealthSupplier {
default IHealth get(String name) throws NoSuchElementException, RemoteException {
- return IHealth.getService(name);
+ return IHealth.getService(name, true /* retry */);
}
}
@@ -1312,18 +1323,27 @@
boolean preexisting) {
if (!IHealth.kInterfaceName.equals(interfaceName)) return;
if (!mInstanceName.equals(instanceName)) return;
- try {
- // ensures the order of multiple onRegistration on different threads.
- synchronized (mLastServiceSetLock) {
- IHealth newService = mHealthSupplier.get(instanceName);
- IHealth oldService = mLastService.getAndSet(newService);
- Slog.i(TAG, "health: new instance registered " + instanceName);
- mCallback.onRegistration(oldService, newService, instanceName);
+
+ // This runnable only runs on mHandlerThread and ordering is ensured, hence
+ // no locking is needed inside the runnable.
+ mHandlerThread.getThreadHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ IHealth newService = mHealthSupplier.get(mInstanceName);
+ IHealth oldService = mLastService.getAndSet(newService);
+
+ // preexisting may be inaccurate (race). Check for equality here.
+ if (Objects.equals(newService, oldService)) return;
+
+ Slog.i(TAG, "health: new instance registered " + mInstanceName);
+ mCallback.onRegistration(oldService, newService, mInstanceName);
+ } catch (NoSuchElementException | RemoteException ex) {
+ Slog.e(TAG, "health: Cannot get instance '" + mInstanceName
+ + "': " + ex.getMessage() + ". Perhaps no permission?");
+ }
}
- } catch (NoSuchElementException | RemoteException ex) {
- Slog.e(TAG, "health: Cannot get instance '" + instanceName + "': " +
- ex.getMessage() + ". Perhaps no permission?");
- }
+ });
}
}
}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index c60d7b0..8a15ded 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -20,6 +20,9 @@
import static android.Manifest.permission.DUMP;
import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
+import static android.net.ConnectivityManager.PRIVATE_DNS_DEFAULT_MODE;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_NONE;
@@ -92,6 +95,7 @@
import android.telephony.PhoneStateListener;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
@@ -1946,9 +1950,9 @@
public void setDnsConfigurationForNetwork(int netId, String[] servers, String domains) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
- ContentResolver resolver = mContext.getContentResolver();
+ final ContentResolver cr = mContext.getContentResolver();
- int sampleValidity = Settings.Global.getInt(resolver,
+ int sampleValidity = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS,
DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS);
if (sampleValidity < 0 || sampleValidity > 65535) {
@@ -1957,7 +1961,7 @@
sampleValidity = DNS_RESOLVER_DEFAULT_SAMPLE_VALIDITY_SECONDS;
}
- int successThreshold = Settings.Global.getInt(resolver,
+ int successThreshold = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT,
DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT);
if (successThreshold < 0 || successThreshold > 100) {
@@ -1966,9 +1970,9 @@
successThreshold = DNS_RESOLVER_DEFAULT_SUCCESS_THRESHOLD_PERCENT;
}
- int minSamples = Settings.Global.getInt(resolver,
+ int minSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
- int maxSamples = Settings.Global.getInt(resolver,
+ int maxSamples = Settings.Global.getInt(cr,
Settings.Global.DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
if (minSamples < 0 || minSamples > maxSamples || maxSamples > 64) {
Slog.w(TAG, "Invalid sample count (min, max)=(" + minSamples + ", " + maxSamples +
@@ -1980,8 +1984,24 @@
final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
- final boolean useTls = Settings.Global.getInt(resolver,
- Settings.Global.DNS_TLS_DISABLED, 0) == 0;
+ final boolean useTls = shouldUseTls(cr);
+ // TODO: Populate tlsHostname once it's decided how the hostname's IP
+ // addresses will be resolved:
+ //
+ // [1] network-provided DNS servers are included here with the
+ // hostname and netd will use the network-provided servers to
+ // resolve the hostname and fix up its internal structures, or
+ //
+ // [2] network-provided DNS servers are included here without the
+ // hostname, the ConnectivityService layer resolves the given
+ // hostname, and then reconfigures netd with this information.
+ //
+ // In practice, there will always be a need for ConnectivityService or
+ // the captive portal app to use the network-provided services to make
+ // some queries. This argues in favor of [1], in concert with another
+ // mechanism, perhaps setting a high bit in the netid, to indicate
+ // via existing DNS APIs which set of servers (network-provided or
+ // non-network-provided private DNS) should be queried.
final String tlsHostname = "";
final String[] tlsFingerprints = new String[0];
try {
@@ -1992,6 +2012,15 @@
}
}
+ private static boolean shouldUseTls(ContentResolver cr) {
+ String privateDns = Settings.Global.getString(cr, Settings.Global.PRIVATE_DNS_MODE);
+ if (TextUtils.isEmpty(privateDns)) {
+ privateDns = PRIVATE_DNS_DEFAULT_MODE;
+ }
+ return privateDns.equals(PRIVATE_DNS_MODE_OPPORTUNISTIC) ||
+ privateDns.startsWith(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
+ }
+
@Override
public void addVpnUidRanges(int netId, UidRange[] ranges) {
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 1924a86..a9f190c 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -16,6 +16,8 @@
package com.android.server;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
import android.content.ContentResolver;
import android.content.Context;
import android.os.Build;
@@ -146,7 +148,7 @@
SystemProperties.set(PROP_RESCUE_LEVEL, Integer.toString(level));
EventLogTags.writeRescueLevel(level, triggerUid);
- PackageManagerService.logCriticalInfo(Log.WARN, "Incremented rescue level to "
+ logCriticalInfo(Log.WARN, "Incremented rescue level to "
+ levelToString(level) + " triggered by UID " + triggerUid);
}
@@ -166,12 +168,12 @@
try {
executeRescueLevelInternal(context, level);
EventLogTags.writeRescueSuccess(level);
- PackageManagerService.logCriticalInfo(Log.DEBUG,
+ logCriticalInfo(Log.DEBUG,
"Finished rescue level " + levelToString(level));
} catch (Throwable t) {
final String msg = ExceptionUtils.getCompleteMessage(t);
EventLogTags.writeRescueFailure(level, msg);
- PackageManagerService.logCriticalInfo(Log.ERROR,
+ logCriticalInfo(Log.ERROR,
"Failed rescue level " + levelToString(level) + ": " + msg);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index bfbce40..75e8000 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -56,6 +56,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IProgressListener;
import android.os.IStoraged;
import android.os.IVold;
import android.os.IVoldListener;
@@ -570,7 +571,7 @@
}
// TODO: Reintroduce shouldBenchmark() test
- fstrim(0);
+ fstrim(0, null);
// invoke the completion callback, if any
// TODO: fstrim is non-blocking, so remove this useless callback
@@ -1576,21 +1577,19 @@
}
@Override
- public long benchmark(String volId) {
+ public void benchmark(String volId, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
- // TODO: refactor for callers to provide a listener
try {
- final CompletableFuture<PersistableBundle> result = new CompletableFuture<>();
mVold.benchmark(volId, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnStatus(listener, status, extras);
}
@Override
public void onFinished(int status, PersistableBundle extras) {
- result.complete(extras);
+ dispatchOnFinished(listener, status, extras);
final String path = extras.getString("path");
final String ident = extras.getString("ident");
@@ -1611,10 +1610,8 @@
}
}
});
- return result.get(3, TimeUnit.MINUTES).getLong("run", Long.MAX_VALUE);
- } catch (Exception e) {
- Slog.wtf(TAG, e);
- return Long.MAX_VALUE;
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -1742,13 +1739,15 @@
}
@Override
- public void fstrim(int flags) {
+ public void fstrim(int flags, IVoldTaskListener listener) {
enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
try {
mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@Override
public void onStatus(int status, PersistableBundle extras) {
+ dispatchOnStatus(listener, status, extras);
+
// Ignore trim failures
if (status != 0) return;
@@ -1770,12 +1769,13 @@
@Override
public void onFinished(int status, PersistableBundle extras) {
- // Not currently used
+ dispatchOnFinished(listener, status, extras);
+
// TODO: benchmark when desired
}
});
- } catch (Exception e) {
- Slog.wtf(TAG, e);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
}
}
@@ -3239,6 +3239,26 @@
}
}
+ private void dispatchOnStatus(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onStatus(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchOnFinished(IVoldTaskListener listener, int status,
+ PersistableBundle extras) {
+ if (listener != null) {
+ try {
+ listener.onFinished(status, extras);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
private static class Callbacks extends Handler {
private static final int MSG_STORAGE_STATE_CHANGED = 1;
private static final int MSG_VOLUME_STATE_CHANGED = 2;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2df5dc9..9c649e3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -89,6 +89,7 @@
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
import com.android.server.pm.UserManagerService;
@@ -369,13 +370,17 @@
// Prepare app storage before we go any further
uss.mUnlockProgress.setProgress(5,
mInjector.getContext().getString(R.string.android_start_title));
- mInjector.getUserManager().onBeforeUnlockUser(userId);
- uss.mUnlockProgress.setProgress(20);
- // Dispatch unlocked to system services; when fully dispatched,
- // that calls through to the next "unlocked" phase
- mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
- .sendToTarget();
+ // Call onBeforeUnlockUser on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.getUserManager().onBeforeUnlockUser(userId);
+ uss.mUnlockProgress.setProgress(20);
+
+ // Dispatch unlocked to system services; when fully dispatched,
+ // that calls through to the next "unlocked" phase
+ mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+ .sendToTarget();
+ });
}
/**
@@ -1819,7 +1824,10 @@
case SYSTEM_USER_UNLOCK_MSG:
final int userId = msg.arg1;
mInjector.getSystemServiceManager().unlockUser(userId);
- mInjector.loadUserRecents(userId);
+ // Loads recents on a worker thread that allows disk I/O
+ FgThread.getHandler().post(() -> {
+ mInjector.loadUserRecents(userId);
+ });
if (userId == UserHandle.USER_SYSTEM) {
mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 5eb2a8d..3f23737 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -122,6 +122,7 @@
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.XmlUtils;
import com.android.server.EventLogTags;
@@ -398,8 +399,9 @@
* {@link AudioManager#RINGER_MODE_SILENT}, or
* {@link AudioManager#RINGER_MODE_VIBRATE}.
*/
- // protected by mSettingsLock
+ @GuardedBy("mSettingsLock")
private int mRingerMode; // internal ringer mode, affects muting of underlying streams
+ @GuardedBy("mSettingsLock")
private int mRingerModeExternal = -1; // reported ringer mode to outside clients (AudioManager)
/** @see System#MODE_RINGER_STREAMS_AFFECTED */
@@ -929,8 +931,11 @@
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_RECORD, mForcedUseForComm,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_RECORD, mForcedUseForComm);
- final int forSys = mCameraSoundForced ?
- AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ final int forSys;
+ synchronized (mSettingsLock) {
+ forSys = mCameraSoundForced ?
+ AudioSystem.FORCE_SYSTEM_ENFORCED : AudioSystem.FORCE_NONE;
+ }
mForceUseLogger.log(new ForceUseEvent(AudioSystem.FOR_SYSTEM, forSys,
"onAudioServerDied"));
AudioSystem.setForceUse(AudioSystem.FOR_SYSTEM, forSys);
@@ -3797,6 +3802,7 @@
return (mRingerModeMutedStreams & (1 << streamType)) != 0;
}
+ @GuardedBy("mSettingsLock")
private boolean updateRingerModeAffectedStreams() {
int ringerModeAffectedStreams = Settings.System.getIntForUser(mContentResolver,
Settings.System.MODE_RINGER_STREAMS_AFFECTED,
@@ -3810,12 +3816,10 @@
ringerModeAffectedStreams = mRingerModeDelegate
.getRingerModeAffectedStreams(ringerModeAffectedStreams);
}
- synchronized (mCameraSoundForced) {
- if (mCameraSoundForced) {
- ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- } else {
- ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
- }
+ if (mCameraSoundForced) {
+ ringerModeAffectedStreams &= ~(1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
+ } else {
+ ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_SYSTEM_ENFORCED);
}
if (mStreamVolumeAlias[AudioSystem.STREAM_DTMF] == AudioSystem.STREAM_RING) {
ringerModeAffectedStreams |= (1 << AudioSystem.STREAM_DTMF);
@@ -4185,7 +4189,6 @@
// 2 mSetModeDeathHandlers
// 3 mSettingsLock
// 4 VolumeStreamState.class
- // 5 mCameraSoundForced
public class VolumeStreamState {
private final int mStreamType;
private final int mIndexMin;
@@ -4252,27 +4255,28 @@
}
public void readSettings() {
- synchronized (VolumeStreamState.class) {
- // force maximum volume on all streams if fixed volume property is set
- if (mUseFixedVolume) {
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
- return;
- }
- // do not read system stream volume from settings: this stream is always aliased
- // to another stream type and its volume is never persisted. Values in settings can
- // only be stale values
- if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
- (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
- int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ // force maximum volume on all streams if fixed volume property is set
+ if (mUseFixedVolume) {
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
+ return;
+ }
+ // do not read system stream volume from settings: this stream is always aliased
+ // to another stream type and its volume is never persisted. Values in settings can
+ // only be stale values
+ if ((mStreamType == AudioSystem.STREAM_SYSTEM) ||
+ (mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED)) {
+ int index = 10 * AudioSystem.DEFAULT_STREAM_VOLUME[mStreamType];
if (mCameraSoundForced) {
index = mIndexMax;
}
+ mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
+ return;
}
- mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, index);
- return;
}
-
+ }
+ synchronized (VolumeStreamState.class) {
int remainingDevices = AudioSystem.DEVICE_OUT_ALL;
for (int i = 0; remainingDevices != 0; i++) {
@@ -4385,34 +4389,34 @@
public boolean setIndex(int index, int device, String caller) {
boolean changed = false;
int oldIndex;
- synchronized (VolumeStreamState.class) {
- oldIndex = getIndex(device);
- index = getValidIndex(index);
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
+ synchronized (VolumeStreamState.class) {
+ oldIndex = getIndex(device);
+ index = getValidIndex(index);
if ((mStreamType == AudioSystem.STREAM_SYSTEM_ENFORCED) && mCameraSoundForced) {
index = mIndexMax;
}
- }
- mIndexMap.put(device, index);
+ mIndexMap.put(device, index);
- changed = oldIndex != index;
- // Apply change to all streams using this one as alias if:
- // - the index actually changed OR
- // - there is no volume index stored for this device on alias stream.
- // If changing volume of current device, also change volume of current
- // device on aliased stream
- final boolean currentDevice = (device == getDeviceForStream(mStreamType));
- final int numStreamTypes = AudioSystem.getNumStreamTypes();
- for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
- final VolumeStreamState aliasStreamState = mStreamStates[streamType];
- if (streamType != mStreamType &&
- mStreamVolumeAlias[streamType] == mStreamType &&
- (changed || !aliasStreamState.hasIndexForDevice(device))) {
- final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
- aliasStreamState.setIndex(scaledIndex, device, caller);
- if (currentDevice) {
- aliasStreamState.setIndex(scaledIndex,
- getDeviceForStream(streamType), caller);
+ changed = oldIndex != index;
+ // Apply change to all streams using this one as alias if:
+ // - the index actually changed OR
+ // - there is no volume index stored for this device on alias stream.
+ // If changing volume of current device, also change volume of current
+ // device on aliased stream
+ final boolean currentDevice = (device == getDeviceForStream(mStreamType));
+ final int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) {
+ final VolumeStreamState aliasStreamState = mStreamStates[streamType];
+ if (streamType != mStreamType &&
+ mStreamVolumeAlias[streamType] == mStreamType &&
+ (changed || !aliasStreamState.hasIndexForDevice(device))) {
+ final int scaledIndex = rescaleIndex(index, mStreamType, streamType);
+ aliasStreamState.setIndex(scaledIndex, device, caller);
+ if (currentDevice) {
+ aliasStreamState.setIndex(scaledIndex,
+ getDeviceForStream(streamType), caller);
+ }
}
}
}
@@ -6022,13 +6026,8 @@
boolean cameraSoundForced = readCameraSoundForced();
synchronized (mSettingsLock) {
- boolean cameraSoundForcedChanged = false;
- synchronized (mCameraSoundForced) {
- if (cameraSoundForced != mCameraSoundForced) {
- mCameraSoundForced = cameraSoundForced;
- cameraSoundForcedChanged = true;
- }
- }
+ final boolean cameraSoundForcedChanged = (cameraSoundForced != mCameraSoundForced);
+ mCameraSoundForced = cameraSoundForced;
if (cameraSoundForcedChanged) {
if (!mIsSingleVolume) {
VolumeStreamState s = mStreamStates[AudioSystem.STREAM_SYSTEM_ENFORCED];
@@ -6408,11 +6407,12 @@
//==========================================================================================
// cached value of com.android.internal.R.bool.config_camera_sound_forced
- private Boolean mCameraSoundForced;
+ @GuardedBy("mSettingsLock")
+ private boolean mCameraSoundForced;
// called by android.hardware.Camera to populate CameraInfo.canDisableShutterSound
public boolean isCameraSoundForced() {
- synchronized (mCameraSoundForced) {
+ synchronized (mSettingsLock) {
return mCameraSoundForced;
}
}
@@ -6734,7 +6734,9 @@
public void setRingerModeDelegate(RingerModeDelegate delegate) {
mRingerModeDelegate = delegate;
if (mRingerModeDelegate != null) {
- updateRingerModeAffectedStreams();
+ synchronized (mSettingsLock) {
+ updateRingerModeAffectedStreams();
+ }
setRingerModeInternal(getRingerModeInternal(), TAG + ".setRingerModeDelegate");
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index f930b52..600bc42 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1120,6 +1120,27 @@
// Dismiss the black surface without fanfare.
mPowerState.setColorFadeLevel(1.0f);
mPowerState.dismissColorFade();
+ } else if (target == Display.STATE_ON_SUSPEND) {
+ // Want screen full-power and suspended.
+ // Wait for brightness animation to complete beforehand unless already
+ // suspended because we may not be able to change it after suspension.
+ if (mScreenBrightnessRampAnimator.isAnimating()
+ && mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ return;
+ }
+
+ // If not already suspending, temporarily set the state to on until the
+ // screen on is unblocked, then suspend.
+ if (mPowerState.getScreenState() != Display.STATE_ON_SUSPEND) {
+ if (!setScreenState(Display.STATE_ON)) {
+ return;
+ }
+ setScreenState(Display.STATE_ON_SUSPEND);
+ }
+
+ // Dismiss the black surface without fanfare.
+ mPowerState.setColorFadeLevel(1.0f);
+ mPowerState.dismissColorFade();
} else {
// Want screen off.
mPendingScreenOff = true;
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 9fdc35f..eb9ff58 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -148,6 +148,8 @@
return SurfaceControl.POWER_MODE_DOZE;
case Display.STATE_DOZE_SUSPEND:
return SurfaceControl.POWER_MODE_DOZE_SUSPEND;
+ case Display.STATE_ON_SUSPEND:
+ return SurfaceControl.POWER_MODE_ON_SUSPEND;
default:
return SurfaceControl.POWER_MODE_NORMAL;
}
@@ -470,6 +472,10 @@
|| oldState == Display.STATE_DOZE_SUSPEND) {
setDisplayState(Display.STATE_DOZE);
currentState = Display.STATE_DOZE;
+ } else if (state == Display.STATE_ON_SUSPEND
+ || oldState == Display.STATE_ON_SUSPEND) {
+ setDisplayState(Display.STATE_ON);
+ currentState = Display.STATE_ON;
} else {
return; // old state and new state is off
}
diff --git a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java b/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
deleted file mode 100644
index 791ee82..0000000
--- a/services/core/java/com/android/server/media/AudioPlaybackMonitor.java
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.media;
-
-import android.content.Context;
-import android.media.AudioManager.AudioPlaybackCallback;
-import android.media.AudioPlaybackConfiguration;
-import android.media.IAudioService;
-import android.media.IPlaybackConfigDispatcher;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.IntArray;
-import android.util.Log;
-import android.util.SparseArray;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Monitors changes in audio playback, and notify the newly started audio playback through the
- * {@link OnAudioPlaybackStartedListener} and the activeness change through the
- * {@link OnAudioPlaybackActiveStateListener}.
- */
-class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
- private static boolean DEBUG = MediaSessionService.DEBUG;
- private static String TAG = "AudioPlaybackMonitor";
-
- private static AudioPlaybackMonitor sInstance;
-
- /**
- * Called when audio playback is started for a given UID.
- */
- interface OnAudioPlaybackStartedListener {
- void onAudioPlaybackStarted(int uid);
- }
-
- /**
- * Called when audio player state is changed.
- */
- interface OnAudioPlayerActiveStateChangedListener {
- void onAudioPlayerActiveStateChanged(int uid, boolean active);
- }
-
- private final Object mLock = new Object();
- private final Context mContext;
- private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
- = new ArrayList<>();
- private final List<OnAudioPlayerActiveStateChangedListener>
- mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
- private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
- private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
-
- // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
- // The UID whose audio playback becomes active at the last comes first.
- // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
- private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
-
- static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
- if (sInstance == null) {
- sInstance = new AudioPlaybackMonitor(context, audioService);
- }
- return sInstance;
- }
-
- private AudioPlaybackMonitor(Context context, IAudioService audioService) {
- mContext = context;
- try {
- audioService.registerPlaybackCallback(this);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Failed to register playback callback", e);
- }
- }
-
- /**
- * Called when the {@link AudioPlaybackConfiguration} is updated.
- * <p>If an app starts audio playback, the app's local media session will be the media button
- * session. If the app has multiple media sessions, the playback active local session will be
- * picked.
- *
- * @param configs List of the current audio playback configuration
- */
- @Override
- public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
- boolean flush) {
- if (flush) {
- Binder.flushPendingCommands();
- }
- final long token = Binder.clearCallingIdentity();
- try {
- List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
- List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
- List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
- synchronized (mLock) {
- // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
- // and find newly activated audio playbacks.
- mActiveAudioPlaybackClientUids.clear();
- for (AudioPlaybackConfiguration config : configs) {
- // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
- // (i.e. playback from the SoundPool class which is only for sound effects)
- // playback.
- // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
- // specific audio/video players.
- if (!config.isActive() || config.getPlayerType()
- == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
- continue;
- }
-
- mActiveAudioPlaybackClientUids.add(config.getClientUid());
- Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
- if (!isActiveState(oldState)) {
- if (DEBUG) {
- Log.d(TAG, "Found a new active media playback. " +
- AudioPlaybackConfiguration.toLogFriendlyString(config));
- }
- // New active audio playback.
- newActiveAudioPlaybackClientUids.add(config.getClientUid());
- int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
- if (index == 0) {
- // It's the lastly played music app already. Skip updating.
- continue;
- } else if (index > 0) {
- mSortedAudioPlaybackClientUids.remove(index);
- }
- mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
- }
- }
- audioPlayerActiveStateChangedListeners = new ArrayList<>(
- mAudioPlayerActiveStateChangedListeners);
- audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
- }
- // Notify the change of audio playback states.
- for (AudioPlaybackConfiguration config : configs) {
- boolean wasActive = isActiveState(
- mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
- boolean isActive = config.isActive();
- if (wasActive != isActive) {
- for (OnAudioPlayerActiveStateChangedListener listener
- : audioPlayerActiveStateChangedListeners) {
- listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
- isActive);
- }
- }
- }
- // Notify the start of audio playback
- for (int uid : newActiveAudioPlaybackClientUids) {
- for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
- listener.onAudioPlaybackStarted(uid);
- }
- }
- mAudioPlaybackStates.clear();
- for (AudioPlaybackConfiguration config : configs) {
- mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
- }
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- /**
- * Registers OnAudioPlaybackStartedListener.
- */
- public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlaybackStartedListener.
- */
- public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
- synchronized (mLock) {
- mAudioPlaybackStartedListeners.remove(listener);
- }
- }
-
- /**
- * Registers OnAudioPlayerActiveStateChangedListener.
- */
- public void registerOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.add(listener);
- }
- }
-
- /**
- * Unregisters OnAudioPlayerActiveStateChangedListener.
- */
- public void unregisterOnAudioPlayerActiveStateChangedListener(
- OnAudioPlayerActiveStateChangedListener listener) {
- synchronized (mLock) {
- mAudioPlayerActiveStateChangedListeners.remove(listener);
- }
- }
-
- /**
- * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
- * audio/video) The UID whose audio playback becomes active at the last comes first.
- */
- public IntArray getSortedAudioPlaybackClientUids() {
- IntArray sortedAudioPlaybackClientUids = new IntArray();
- synchronized (mLock) {
- sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
- }
- return sortedAudioPlaybackClientUids;
- }
-
- /**
- * Returns if the audio playback is active for the uid.
- */
- public boolean isPlaybackActive(int uid) {
- synchronized (mLock) {
- return mActiveAudioPlaybackClientUids.contains(uid);
- }
- }
-
- /**
- * Cleans up the sorted list of audio playback client UIDs with given {@param
- * mediaButtonSessionUid}.
- * <p>UIDs whose audio playback started after the media button session's audio playback
- * cannot be the lastly played media app. So they won't needed anymore.
- *
- * @param mediaButtonSessionUid UID of the media button session.
- */
- public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mediaButtonSessionUid);
- for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
- if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
- break;
- }
- int uid = mSortedAudioPlaybackClientUids.get(i);
- if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
- // Clean up unnecessary UIDs.
- // It doesn't need to be managed profile aware because it's just to prevent
- // the list from increasing indefinitely. The media button session updating
- // shouldn't be affected by cleaning up.
- mSortedAudioPlaybackClientUids.remove(i);
- }
- }
- }
- }
-
- /**
- * Dumps {@link AudioPlaybackMonitor}.
- */
- public void dump(PrintWriter pw, String prefix) {
- synchronized (mLock) {
- pw.println(prefix + "Audio playback (lastly played comes first)");
- String indent = prefix + " ";
- for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
- int uid = mSortedAudioPlaybackClientUids.get(i);
- pw.print(indent + "uid=" + uid + " packages=");
- String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
- if (packages != null && packages.length > 0) {
- for (int j = 0; j < packages.length; j++) {
- pw.print(packages[j] + " ");
- }
- }
- pw.println();
- }
- }
- }
-
- private boolean isActiveState(Integer state) {
- return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
- }
-}
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
new file mode 100644
index 0000000..be223f1
--- /dev/null
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.media;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioPlaybackConfiguration;
+import android.media.IAudioService;
+import android.media.IPlaybackConfigDispatcher;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IntArray;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Monitors the state changes of audio players.
+ */
+class AudioPlayerStateMonitor extends IPlaybackConfigDispatcher.Stub {
+ private static boolean DEBUG = MediaSessionService.DEBUG;
+ private static String TAG = "AudioPlayerStateMonitor";
+
+ private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
+
+ /**
+ * Called when the state of audio player is changed.
+ */
+ interface OnAudioPlayerStateChangedListener {
+ void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+ }
+
+ private final static class MessageHandler extends Handler {
+ private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+
+ private final OnAudioPlayerStateChangedListener mListsner;
+
+ public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+ super(looper);
+ mListsner = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_AUDIO_PLAYER_STATE_CHANGED:
+ mListsner.onAudioPlayerStateChanged(
+ msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+ break;
+ }
+ }
+
+ public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
+ AudioPlaybackConfiguration config) {
+ obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+ }
+ }
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
+ new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+ @GuardedBy("mLock")
+ private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+ // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
+ // The UID whose audio playback becomes active at the last comes first.
+ // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
+ @GuardedBy("mLock")
+ private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
+
+ @GuardedBy("mLock")
+ private boolean mRegisteredToAudioService;
+
+ static AudioPlayerStateMonitor getInstance() {
+ return sInstance;
+ }
+
+ private AudioPlayerStateMonitor() {
+ }
+
+ /**
+ * Called when the {@link AudioPlaybackConfiguration} is updated.
+ * <p>If an app starts audio playback, the app's local media session will be the media button
+ * session. If the app has multiple media sessions, the playback active local session will be
+ * picked.
+ *
+ * @param configs List of the current audio playback configuration
+ */
+ @Override
+ public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
+ boolean flush) {
+ if (flush) {
+ Binder.flushPendingCommands();
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
+ final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
+ new HashMap<>(mAudioPlayersForUid);
+ synchronized (mLock) {
+ mAudioPlayerStates.clear();
+ mAudioPlayersForUid.clear();
+ for (AudioPlaybackConfiguration config : configs) {
+ int pii = config.getPlayerInterfaceId();
+ int uid = config.getClientUid();
+ mAudioPlayerStates.put(pii, config.getPlayerState());
+ HashSet<Integer> players = mAudioPlayersForUid.get(uid);
+ if (players == null) {
+ players = new HashSet<Integer>();
+ players.add(pii);
+ mAudioPlayersForUid.put(uid, players);
+ } else {
+ players.add(pii);
+ }
+ }
+ for (AudioPlaybackConfiguration config : configs) {
+ if (!config.isActive()) {
+ continue;
+ }
+
+ int uid = config.getClientUid();
+ if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+ if (DEBUG) {
+ Log.d(TAG, "Found a new active media playback. " +
+ AudioPlaybackConfiguration.toLogFriendlyString(config));
+ }
+ // New active audio playback.
+ int index = mSortedAudioPlaybackClientUids.indexOf(uid);
+ if (index == 0) {
+ // It's the lastly played music app already. Skip updating.
+ continue;
+ } else if (index > 0) {
+ mSortedAudioPlaybackClientUids.remove(index);
+ }
+ mSortedAudioPlaybackClientUids.add(0, uid);
+ }
+ }
+ // Notify the change of audio player states.
+ for (AudioPlaybackConfiguration config : configs) {
+ final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
+ final int prevStateInt =
+ (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
+ prevState.intValue();
+ if (prevStateInt != config.getPlayerState()) {
+ sendAudioPlayerStateChangedMessageLocked(
+ config.getClientUid(), prevStateInt, config);
+ }
+ }
+ for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
+ // If all players for prevUid is removed, notify the prev state was
+ // PLAYER_STATE_STARTED only when there were a player whose state was
+ // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
+ if (!mAudioPlayersForUid.containsKey(prevUid)) {
+ Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
+ int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
+ for (int pii : prevPlayers) {
+ Integer state = prevAudioPlayerStates.get(pii);
+ if (state == null) {
+ continue;
+ }
+ if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ prevState = state;
+ break;
+ } else if (prevState
+ == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
+ prevState = state;
+ }
+ }
+ sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
+ }
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Registers OnAudioPlayerStateChangedListener.
+ */
+ public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+ synchronized (mLock) {
+ mListenerMap.put(listener, new MessageHandler((handler == null) ?
+ Looper.myLooper() : handler.getLooper(), listener));
+ }
+ }
+
+ /**
+ * Unregisters OnAudioPlayerStateChangedListener.
+ */
+ public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+ synchronized (mLock) {
+ mListenerMap.remove(listener);
+ }
+ }
+
+ /**
+ * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
+ * audio/video) The UID whose audio playback becomes active at the last comes first.
+ */
+ public IntArray getSortedAudioPlaybackClientUids() {
+ IntArray sortedAudioPlaybackClientUids = new IntArray();
+ synchronized (mLock) {
+ sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
+ }
+ return sortedAudioPlaybackClientUids;
+ }
+
+ /**
+ * Returns if the audio playback is active for the uid.
+ */
+ public boolean isPlaybackActive(int uid) {
+ synchronized (mLock) {
+ Set<Integer> players = mAudioPlayersForUid.get(uid);
+ if (players == null) {
+ return false;
+ }
+ for (Integer pii : players) {
+ if (isActiveState(mAudioPlayerStates.get(pii))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Cleans up the sorted list of audio playback client UIDs with given {@param
+ * mediaButtonSessionUid}.
+ * <p>UIDs whose audio playback are inactive and have started before the media button session's
+ * audio playback cannot be the lastly played media app. So they won't needed anymore.
+ *
+ * @param mediaButtonSessionUid UID of the media button session.
+ */
+ public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
+ synchronized (mLock) {
+ int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
+ if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
+ break;
+ }
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+ // Clean up unnecessary UIDs.
+ // It doesn't need to be managed profile aware because it's just to prevent
+ // the list from increasing indefinitely. The media button session updating
+ // shouldn't be affected by cleaning up.
+ mSortedAudioPlaybackClientUids.remove(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Dumps {@link AudioPlayerStateMonitor}.
+ */
+ public void dump(Context context, PrintWriter pw, String prefix) {
+ synchronized (mLock) {
+ pw.println(prefix + "Audio playback (lastly played comes first)");
+ String indent = prefix + " ";
+ for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
+ int uid = mSortedAudioPlaybackClientUids.get(i);
+ pw.print(indent + "uid=" + uid + " packages=");
+ String[] packages = context.getPackageManager().getPackagesForUid(uid);
+ if (packages != null && packages.length > 0) {
+ for (int j = 0; j < packages.length; j++) {
+ pw.print(packages[j] + " ");
+ }
+ }
+ pw.println();
+ }
+ }
+ }
+
+ public void registerSelfIntoAudioServiceIfNeeded(IAudioService audioService) {
+ synchronized (mLock) {
+ try {
+ if (!mRegisteredToAudioService) {
+ audioService.registerPlaybackCallback(this);
+ mRegisteredToAudioService = true;
+ }
+ } catch (RemoteException e) {
+ Log.wtf(TAG, "Failed to register playback callback", e);
+ mRegisteredToAudioService = false;
+ }
+ }
+ }
+
+ private void sendAudioPlayerStateChangedMessageLocked(
+ final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+ for (MessageHandler messageHandler : mListenerMap.values()) {
+ messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+ }
+ }
+
+ private static boolean isActiveState(Integer state) {
+ return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 1cfd5f0..3c9e1d4 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -19,12 +19,14 @@
import com.android.internal.util.DumpUtils;
import com.android.server.Watchdog;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioRoutesInfo;
import android.media.AudioSystem;
import android.media.IAudioRoutesObserver;
@@ -96,7 +98,8 @@
private int mCurrentUserId = -1;
private boolean mGlobalBluetoothA2dpOn = false;
private final IAudioService mAudioService;
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
+ private final Handler mHandler = new Handler();
private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
public MediaRouterService(Context context) {
@@ -106,31 +109,57 @@
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(context, mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlayerActiveStateChangedListener(
- new AudioPlaybackMonitor.OnAudioPlayerActiveStateChangedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+ static final long WAIT_MS = 500;
+ final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
+ @Override
+ public void run() {
+ restoreBluetoothA2dp();
+ }
+ };
+
@Override
- public void onAudioPlayerActiveStateChanged(int uid, boolean active) {
+ public void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ int restoreUid = -1;
+ boolean active = config == null ? false : config.isActive();
if (active) {
- restoreRoute(uid);
+ restoreUid = uid;
+ } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+ // Noting to do if the prev state is not an active state.
+ return;
} else {
IntArray sortedAudioPlaybackClientUids =
- mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
- boolean restored = false;
- for (int i = 0; i < sortedAudioPlaybackClientUids.size(); i++) {
- if (mAudioPlaybackMonitor.isPlaybackActive(
+ mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
+ for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
+ if (mAudioPlayerStateMonitor.isPlaybackActive(
sortedAudioPlaybackClientUids.get(i))) {
- restoreRoute(sortedAudioPlaybackClientUids.get(i));
- restored = true;
+ restoreUid = sortedAudioPlaybackClientUids.get(i);
break;
}
}
- if (!restored) {
- restoreBluetoothA2dp();
+ }
+
+ mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
+ if (restoreUid >= 0) {
+ restoreRoute(restoreUid);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " restoring " + restoreUid);
+ }
+ } else {
+ mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
+ if (DEBUG) {
+ Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
+ + " active " + active + " delaying");
}
}
}
- });
+ }, mHandler);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
+
AudioRoutesInfo audioRoutes = null;
try {
audioRoutes = mAudioService.startWatchingRoutes(new IAudioRoutesObserver.Stub() {
@@ -261,9 +290,14 @@
final long token = Binder.clearCallingIdentity();
try {
+ ClientRecord clientRecord;
synchronized (mLock) {
- return isPlaybackActiveLocked(client);
+ clientRecord = mAllClientRecords.get(client.asBinder());
}
+ if (clientRecord != null) {
+ return mAudioPlayerStateMonitor.isPlaybackActive(clientRecord.mUid);
+ }
+ return false;
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -480,14 +514,6 @@
return null;
}
- private boolean isPlaybackActiveLocked(IMediaRouterClient client) {
- ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
- if (clientRecord != null) {
- return mAudioPlaybackMonitor.isPlaybackActive(clientRecord.mUid);
- }
- return false;
- }
-
private void setDiscoveryRequestLocked(IMediaRouterClient client,
int routeTypes, boolean activeScan) {
final IBinder binder = client.asBinder();
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index aa65244..f6a81d0 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -16,6 +16,7 @@
package com.android.server.media;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.KeyguardManager;
@@ -31,7 +32,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.media.AudioManager;
-import android.media.AudioManagerInternal;
+import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
@@ -68,7 +69,6 @@
import android.view.ViewConfiguration;
import com.android.internal.util.DumpUtils;
-import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.Watchdog;
import com.android.server.Watchdog.Monitor;
@@ -104,7 +104,6 @@
private KeyguardManager mKeyguardManager;
private IAudioService mAudioService;
- private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
private INotificationManager mNotificationManager;
@@ -114,7 +113,7 @@
// It's always not null after the MediaSessionService is started.
private FullUserRecord mCurrentFullUserRecord;
private MediaSessionRecord mGlobalPrioritySession;
- private AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private AudioPlayerStateMonitor mAudioPlayerStateMonitor;
// Used to notify system UI when remote volume was changed. TODO find a
// better way to handle this.
@@ -137,11 +136,16 @@
mKeyguardManager =
(KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
mAudioService = getAudioService();
- mAudioPlaybackMonitor = AudioPlaybackMonitor.getInstance(getContext(), mAudioService);
- mAudioPlaybackMonitor.registerOnAudioPlaybackStartedListener(
- new AudioPlaybackMonitor.OnAudioPlaybackStartedListener() {
+ mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
+ mAudioPlayerStateMonitor.registerListener(
+ new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
@Override
- public void onAudioPlaybackStarted(int uid) {
+ public void onAudioPlayerStateChanged(
+ int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+ if (config == null || !config.isActive() || config.getPlayerType()
+ == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+ return;
+ }
synchronized (mLock) {
FullUserRecord user =
getFullUserRecordLocked(UserHandle.getUserId(uid));
@@ -150,8 +154,8 @@
}
}
}
- });
- mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
+ }, null /* handler */);
+ mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
mContentResolver = getContext().getContentResolver();
mSettingsObserver = new SettingsObserver();
mSettingsObserver.observe();
@@ -650,7 +654,7 @@
public FullUserRecord(int fullUserId) {
mFullUserId = fullUserId;
- mPriorityStack = new MediaSessionStack(mAudioPlaybackMonitor, this);
+ mPriorityStack = new MediaSessionStack(mAudioPlayerStateMonitor, this);
// Restore the remembered media button receiver before the boot.
String mediaButtonReceiver = Settings.Secure.getStringForUser(mContentResolver,
Settings.System.MEDIA_BUTTON_RECEIVER, mFullUserId);
@@ -1309,7 +1313,7 @@
for (int i = 0; i < count; i++) {
mUserRecords.valueAt(i).dumpLocked(pw, "");
}
- mAudioPlaybackMonitor.dump(pw, "");
+ mAudioPlayerStateMonitor.dump(getContext(), pw, "");
}
}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index d9fe72e..719ec36 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -75,7 +75,7 @@
*/
private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
- private final AudioPlaybackMonitor mAudioPlaybackMonitor;
+ private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
/**
@@ -84,7 +84,6 @@
*/
private MediaSessionRecord mMediaButtonSession;
- private MediaSessionRecord mCachedDefault;
private MediaSessionRecord mCachedVolumeDefault;
/**
@@ -93,8 +92,8 @@
private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
new SparseArray<>();
- MediaSessionStack(AudioPlaybackMonitor monitor, OnMediaButtonSessionChangedListener listener) {
- mAudioPlaybackMonitor = monitor;
+ MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
+ mAudioPlayerStateMonitor = monitor;
mOnMediaButtonSessionChangedListener = listener;
}
@@ -187,13 +186,13 @@
if (DEBUG) {
Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
}
- IntArray audioPlaybackUids = mAudioPlaybackMonitor.getSortedAudioPlaybackClientUids();
+ IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
for (int i = 0; i < audioPlaybackUids.size(); i++) {
MediaSessionRecord mediaButtonSession =
findMediaButtonSession(audioPlaybackUids.get(i));
if (mediaButtonSession != null) {
// Found the media button session.
- mAudioPlaybackMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
+ mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
if (mMediaButtonSession != mediaButtonSession) {
updateMediaButtonSession(mediaButtonSession);
}
@@ -216,7 +215,7 @@
for (MediaSessionRecord session : mSessions) {
if (uid == session.getUid()) {
if (session.getPlaybackState() != null && session.isPlaybackActive() ==
- mAudioPlaybackMonitor.isPlaybackActive(session.getUid())) {
+ mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
// If there's a media session whose PlaybackState matches
// the audio playback state, return it immediately.
return session;
@@ -376,7 +375,6 @@
}
private void clearCache(int userId) {
- mCachedDefault = null;
mCachedVolumeDefault = null;
mCachedActiveLists.remove(userId);
// mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 3574466..fca9585 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -18,6 +18,8 @@
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
+import static com.android.server.pm.PackageManagerService.SCAN_INITIAL;
+
import com.android.internal.util.Preconditions;
import android.content.pm.PackageParser;
import android.util.ArrayMap;
@@ -341,6 +343,41 @@
return mKeySets.get(id) != null;
}
+ public boolean shouldCheckUpgradeKeySetLocked(PackageSettingBase oldPs, int scanFlags) {
+ // Can't rotate keys during boot or if sharedUser.
+ if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
+ || !oldPs.keySetData.isUsingUpgradeKeySets()) {
+ return false;
+ }
+ // app is using upgradeKeySets; make sure all are valid
+ long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
+ for (int i = 0; i < upgradeKeySets.length; i++) {
+ if (!isIdValidKeySetId(upgradeKeySets[i])) {
+ Slog.wtf(TAG, "Package "
+ + (oldPs.name != null ? oldPs.name : "<null>")
+ + " contains upgrade-key-set reference to unknown key-set: "
+ + upgradeKeySets[i]
+ + " reverting to signatures check.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean checkUpgradeKeySetLocked(PackageSettingBase oldPS,
+ PackageParser.Package newPkg) {
+ // Upgrade keysets are being used. Determine if new package has a superset of the
+ // required keys.
+ long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
+ for (int i = 0; i < upgradeKeySets.length; i++) {
+ Set<PublicKey> upgradeSet = getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
+ if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Fetches the {@link PublicKey public keys} which belong to the specified
* KeySet id.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6c42f4f..6a0ea4ee 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -102,6 +102,15 @@
import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
+import static com.android.server.pm.PackageManagerServiceUtils.decompressFile;
+import static com.android.server.pm.PackageManagerServiceUtils.deriveAbiOverride;
+import static com.android.server.pm.PackageManagerServiceUtils.dumpCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
+import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
@@ -408,7 +417,7 @@
private static final boolean DEBUG_FILTERS = false;
public static final boolean DEBUG_PERMISSIONS = false;
private static final boolean DEBUG_SHARED_LIBRARIES = false;
- private static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
+ public static final boolean DEBUG_COMPRESSION = Build.IS_DEBUGGABLE;
// Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
// and PackageDexOptimizer. All these classes have their own flag to allow switching a single
@@ -462,9 +471,9 @@
private static final String STATIC_SHARED_LIB_DELIMITER = "_";
/** Extension of the compressed packages */
- private final static String COMPRESSED_EXTENSION = ".gz";
+ public final static String COMPRESSED_EXTENSION = ".gz";
/** Suffix of stub packages on the system partition */
- private final static String STUB_SUFFIX = "-Stub";
+ public final static String STUB_SUFFIX = "-Stub";
private static final int[] EMPTY_INT_ARRAY = new int[0];
@@ -3076,75 +3085,6 @@
}
}
- private int decompressFile(File srcFile, File dstFile) throws ErrnoException {
- if (DEBUG_COMPRESSION) {
- Slog.i(TAG, "Decompress file"
- + "; src: " + srcFile.getAbsolutePath()
- + ", dst: " + dstFile.getAbsolutePath());
- }
- try (
- InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
- OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
- ) {
- Streams.copy(fileIn, fileOut);
- Os.chmod(dstFile.getAbsolutePath(), 0644);
- return PackageManager.INSTALL_SUCCEEDED;
- } catch (IOException e) {
- logCriticalInfo(Log.ERROR, "Failed to decompress file"
- + "; src: " + srcFile.getAbsolutePath()
- + ", dst: " + dstFile.getAbsolutePath());
- }
- return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
- }
-
- private File[] getCompressedFiles(String codePath) {
- final File stubCodePath = new File(codePath);
- final String stubName = stubCodePath.getName();
-
- // The layout of a compressed package on a given partition is as follows :
- //
- // Compressed artifacts:
- //
- // /partition/ModuleName/foo.gz
- // /partation/ModuleName/bar.gz
- //
- // Stub artifact:
- //
- // /partition/ModuleName-Stub/ModuleName-Stub.apk
- //
- // In other words, stub is on the same partition as the compressed artifacts
- // and in a directory that's suffixed with "-Stub".
- int idx = stubName.lastIndexOf(STUB_SUFFIX);
- if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
- return null;
- }
-
- final File stubParentDir = stubCodePath.getParentFile();
- if (stubParentDir == null) {
- Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
- return null;
- }
-
- final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
- final File[] files = compressedPath.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
- }
- });
-
- if (DEBUG_COMPRESSION && files != null && files.length > 0) {
- Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
- }
-
- return files;
- }
-
- private boolean compressedFileExists(String codePath) {
- final File[] compressedFiles = getCompressedFiles(codePath);
- return compressedFiles != null && compressedFiles.length > 0;
- }
-
/**
* Decompresses the given package on the system image onto
* the /data partition.
@@ -5385,56 +5325,6 @@
}
/**
- * Compares two sets of signatures. Returns:
- * <br />
- * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
- * <br />
- * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
- * <br />
- * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
- * <br />
- * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
- * <br />
- * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
- */
- public static int compareSignatures(Signature[] s1, Signature[] s2) {
- if (s1 == null) {
- return s2 == null
- ? PackageManager.SIGNATURE_NEITHER_SIGNED
- : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
- }
-
- if (s2 == null) {
- return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
- }
-
- if (s1.length != s2.length) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- // Since both signature sets are of size 1, we can compare without HashSets.
- if (s1.length == 1) {
- return s1[0].equals(s2[0]) ?
- PackageManager.SIGNATURE_MATCH :
- PackageManager.SIGNATURE_NO_MATCH;
- }
-
- ArraySet<Signature> set1 = new ArraySet<Signature>();
- for (Signature sig : s1) {
- set1.add(sig);
- }
- ArraySet<Signature> set2 = new ArraySet<Signature>();
- for (Signature sig : s2) {
- set2.add(sig);
- }
- // Make sure s2 contains all signatures in s1.
- if (set1.equals(set2)) {
- return PackageManager.SIGNATURE_MATCH;
- }
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- /**
* If the database version for this type of package (internal storage or
* external storage) is less than the version where package signatures
* were updated, return true.
@@ -5444,76 +5334,11 @@
return ver.databaseVersion < DatabaseVersion.SIGNATURE_END_ENTITY;
}
- /**
- * Used for backward compatibility to make sure any packages with
- * certificate chains get upgraded to the new style. {@code existingSigs}
- * will be in the old format (since they were stored on disk from before the
- * system upgrade) and {@code scannedSigs} will be in the newer format.
- */
- private int compareSignaturesCompat(PackageSignatures existingSigs,
- PackageParser.Package scannedPkg) {
- if (!isCompatSignatureUpdateNeeded(scannedPkg)) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- ArraySet<Signature> existingSet = new ArraySet<Signature>();
- for (Signature sig : existingSigs.mSignatures) {
- existingSet.add(sig);
- }
- ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
- for (Signature sig : scannedPkg.mSignatures) {
- try {
- Signature[] chainSignatures = sig.getChainSignatures();
- for (Signature chainSig : chainSignatures) {
- scannedCompatSet.add(chainSig);
- }
- } catch (CertificateEncodingException e) {
- scannedCompatSet.add(sig);
- }
- }
- /*
- * Make sure the expanded scanned set contains all signatures in the
- * existing one.
- */
- if (scannedCompatSet.equals(existingSet)) {
- // Migrate the old signatures to the new scheme.
- existingSigs.assignSignatures(scannedPkg.mSignatures);
- // The new KeySets will be re-added later in the scanning process.
- synchronized (mPackages) {
- mSettings.mKeySetManagerService.removeAppKeySetDataLPw(scannedPkg.packageName);
- }
- return PackageManager.SIGNATURE_MATCH;
- }
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
private boolean isRecoverSignatureUpdateNeeded(PackageParser.Package scannedPkg) {
final VersionInfo ver = getSettingsVersionForPackage(scannedPkg);
return ver.databaseVersion < DatabaseVersion.SIGNATURE_MALFORMED_RECOVER;
}
- private int compareSignaturesRecover(PackageSignatures existingSigs,
- PackageParser.Package scannedPkg) {
- if (!isRecoverSignatureUpdateNeeded(scannedPkg)) {
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
- String msg = null;
- try {
- if (Signature.areEffectiveMatch(existingSigs.mSignatures, scannedPkg.mSignatures)) {
- logCriticalInfo(Log.INFO, "Recovered effectively matching certificates for "
- + scannedPkg.packageName);
- return PackageManager.SIGNATURE_MATCH;
- }
- } catch (CertificateException e) {
- msg = e.getMessage();
- }
-
- logCriticalInfo(Log.INFO,
- "Failed to recover certificates for " + scannedPkg.packageName + ": " + msg);
- return PackageManager.SIGNATURE_NO_MATCH;
- }
-
@Override
public List<String> getAllPackages() {
final int callingUid = Binder.getCallingUid();
@@ -8237,51 +8062,10 @@
parallelPackageParser.close();
}
- private static File getSettingsProblemFile() {
- File dataDir = Environment.getDataDirectory();
- File systemDir = new File(dataDir, "system");
- File fname = new File(systemDir, "uiderrors.txt");
- return fname;
- }
-
public static void reportSettingsProblem(int priority, String msg) {
logCriticalInfo(priority, msg);
}
- public static void logCriticalInfo(int priority, String msg) {
- Slog.println(priority, TAG, msg);
- EventLogTags.writePmCriticalInfo(msg);
- try {
- File fname = getSettingsProblemFile();
- FileOutputStream out = new FileOutputStream(fname, true);
- PrintWriter pw = new FastPrintWriter(out);
- SimpleDateFormat formatter = new SimpleDateFormat();
- String dateString = formatter.format(new Date(System.currentTimeMillis()));
- pw.println(dateString + ": " + msg);
- pw.close();
- FileUtils.setPermissions(
- fname.toString(),
- FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
- -1, -1);
- } catch (java.io.IOException e) {
- }
- }
-
- private long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
- if (srcFile.isDirectory()) {
- final File baseFile = new File(pkg.baseCodePath);
- long maxModifiedTime = baseFile.lastModified();
- if (pkg.splitCodePaths != null) {
- for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
- final File splitFile = new File(pkg.splitCodePaths[i]);
- maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
- }
- }
- return maxModifiedTime;
- }
- return srcFile.lastModified();
- }
-
private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
final int policyFlags) throws PackageManagerException {
// When upgrading from pre-N MR1, verify the package time stamp using the package
@@ -8294,7 +8078,7 @@
&& !isCompatSignatureUpdateNeeded(pkg)
&& !isRecoverSignatureUpdateNeeded(pkg)) {
long mSigningKeySetId = ps.keySetData.getProperSigningKeySet();
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
ArraySet<PublicKey> signingKs;
synchronized (mPackages) {
signingKs = ksms.getPublicKeysFromKeySetLPr(mSigningKeySetId);
@@ -8710,7 +8494,7 @@
return scannedPkg;
}
- private void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
+ private static void renameStaticSharedLibraryPackage(PackageParser.Package pkg) {
// Derive the new package synthetic package name
pkg.setPackageName(pkg.packageName + STATIC_SHARED_LIB_DELIMITER
+ pkg.staticSharedLibVersion);
@@ -8724,49 +8508,6 @@
return processName;
}
- private void verifySignaturesLP(PackageSetting pkgSetting, PackageParser.Package pkg)
- throws PackageManagerException {
- if (pkgSetting.signatures.mSignatures != null) {
- // Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures)
- == PackageManager.SIGNATURE_MATCH;
- if (!match) {
- match = compareSignaturesCompat(pkgSetting.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- match = compareSignaturesRecover(pkgSetting.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
- + pkg.packageName + " signatures do not match the "
- + "previously installed version; ignoring!");
- }
- }
-
- // Check for shared user signatures
- if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
- // Already existing package. Make sure signatures match
- boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
- if (!match) {
- match = compareSignaturesCompat(pkgSetting.sharedUser.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- match = compareSignaturesRecover(pkgSetting.sharedUser.signatures, pkg)
- == PackageManager.SIGNATURE_MATCH;
- }
- if (!match) {
- throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
- "Package " + pkg.packageName
- + " has no signatures that match those in shared user "
- + pkgSetting.sharedUser.name + "; ignoring!");
- }
- }
- }
-
/**
* Enforces that only the system UID or root's UID can call a method exposed
* via Binder.
@@ -9737,24 +9478,6 @@
return res;
}
- /**
- * Derive the value of the {@code cpuAbiOverride} based on the provided
- * value and an optional stored value from the package settings.
- */
- private static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
- String cpuAbiOverride = null;
-
- if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
- cpuAbiOverride = null;
- } else if (abiOverride != null) {
- cpuAbiOverride = abiOverride;
- } else if (settings != null) {
- cpuAbiOverride = settings.cpuAbiOverrideString;
- }
-
- return cpuAbiOverride;
- }
-
private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
throws PackageManagerException {
@@ -10102,8 +9825,9 @@
}
}
- if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
- if (checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+ if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10121,8 +9845,16 @@
}
} else {
try {
- // SIDE EFFECTS; compareSignaturesCompat() changes KeysetManagerService
- verifySignaturesLP(signatureCheckPs, pkg);
+ final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+ final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+ final boolean compatMatch = verifySignatures(signatureCheckPs, pkg.mSignatures,
+ compareCompat, compareRecover);
+ // The new KeySets will be re-added later in the scanning process.
+ if (compatMatch) {
+ synchronized (mPackages) {
+ ksms.removeAppKeySetDataLPw(pkg.packageName);
+ }
+ }
// We just determined the app is signed correctly, so bring
// over the latest parsed certs.
pkgSetting.signatures.mSignatures = pkg.mSignatures;
@@ -10410,7 +10142,7 @@
}
// Make sure we're not adding any bogus keyset info
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
ksms.assertScannedPackageValid(pkg);
synchronized (mPackages) {
@@ -15566,42 +15298,6 @@
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
- // Can't rotate keys during boot or if sharedUser.
- if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
- || !oldPs.keySetData.isUsingUpgradeKeySets()) {
- return false;
- }
- // app is using upgradeKeySets; make sure all are valid
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
- long[] upgradeKeySets = oldPs.keySetData.getUpgradeKeySets();
- for (int i = 0; i < upgradeKeySets.length; i++) {
- if (!ksms.isIdValidKeySetId(upgradeKeySets[i])) {
- Slog.wtf(TAG, "Package "
- + (oldPs.name != null ? oldPs.name : "<null>")
- + " contains upgrade-key-set reference to unknown key-set: "
- + upgradeKeySets[i]
- + " reverting to signatures check.");
- return false;
- }
- }
- return true;
- }
-
- private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
- // Upgrade keysets are being used. Determine if new package has a superset of the
- // required keys.
- long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
- for (int i = 0; i < upgradeKeySets.length; i++) {
- Set<PublicKey> upgradeSet = ksms.getPublicKeysFromKeySetLPr(upgradeKeySets[i]);
- if (upgradeSet != null && newPkg.mSigningKeys.containsAll(upgradeSet)) {
- return true;
- }
- }
- return false;
- }
-
private static void updateDigest(MessageDigest digest, File file) throws IOException {
try (DigestInputStream digestStream =
new DigestInputStream(new FileInputStream(file), digest)) {
@@ -15640,8 +15336,9 @@
ps = mSettings.mPackages.get(pkgName);
// verify signatures are valid
- if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
- if (!checkUpgradeKeySetLP(ps, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(ps, scanFlags)) {
+ if (!ksms.checkUpgradeKeySetLocked(ps, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package not signed by keys specified by upgrade-keysets: "
+ pkgName);
@@ -16542,8 +16239,9 @@
// Quick sanity check that we're signed correctly if updating;
// we'll check this again later when scanning, but we want to
// bail early here before tripping over redefined permissions.
- if (shouldCheckUpgradeKeySetLP(signatureCheckPs, scanFlags)) {
- if (!checkUpgradeKeySetLP(signatureCheckPs, pkg)) {
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
+ if (!ksms.checkUpgradeKeySetLocked(signatureCheckPs, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package "
+ pkg.packageName + " upgrade keys do not match the "
+ "previously installed version");
@@ -16551,7 +16249,16 @@
}
} else {
try {
- verifySignaturesLP(signatureCheckPs, pkg);
+ final boolean compareCompat = isCompatSignatureUpdateNeeded(pkg);
+ final boolean compareRecover = isRecoverSignatureUpdateNeeded(pkg);
+ final boolean compatMatch = verifySignatures(
+ signatureCheckPs, pkg.mSignatures, compareCompat, compareRecover);
+ // The new KeySets will be re-added later in the scanning process.
+ if (compatMatch) {
+ synchronized (mPackages) {
+ ksms.removeAppKeySetDataLPw(pkg.packageName);
+ }
+ }
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
@@ -16589,10 +16296,11 @@
final boolean sigsOk;
final String sourcePackageName = bp.getSourcePackageName();
final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
if (sourcePackageName.equals(pkg.packageName)
- && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
- scanFlags))) {
- sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
+ && (ksms.shouldCheckUpgradeKeySetLocked(
+ sourcePackageSetting, scanFlags))) {
+ sigsOk = ksms.checkUpgradeKeySetLocked(sourcePackageSetting, pkg);
} else {
sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
@@ -20938,34 +20646,11 @@
pw.println();
pw.println("Package warning messages:");
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- pw.println(line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
+ dumpCriticalInfo(pw, null);
}
if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) {
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- pw.print("msg,");
- pw.println(line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
+ dumpCriticalInfo(pw, "msg,");
}
}
@@ -21011,26 +20696,11 @@
dumpFeaturesProto(proto);
mSettings.dumpPackagesProto(proto);
mSettings.dumpSharedUsersProto(proto);
- dumpMessagesProto(proto);
+ dumpCriticalInfo(proto);
}
proto.flush();
}
- private void dumpMessagesProto(ProtoOutputStream proto) {
- BufferedReader in = null;
- String line = null;
- try {
- in = new BufferedReader(new FileReader(getSettingsProblemFile()));
- while ((line = in.readLine()) != null) {
- if (line.contains("ignored: updated version")) continue;
- proto.write(PackageServiceDumpProto.MESSAGES, line);
- }
- } catch (IOException ignored) {
- } finally {
- IoUtils.closeQuietly(in);
- }
- }
-
private void dumpFeaturesProto(ProtoOutputStream proto) {
synchronized (mAvailableFeatures) {
final int count = mAvailableFeatures.size();
@@ -22439,7 +22109,7 @@
Slog.w(TAG, "KeySet requested for filtered package: " + packageName);
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return new KeySet(ksms.getKeySetByAliasAndPackageNameLPr(packageName, alias));
}
}
@@ -22468,7 +22138,7 @@
&& Process.SYSTEM_UID != callingUid) {
throw new SecurityException("May not access signing KeySet of other apps.");
}
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return new KeySet(ksms.getSigningKeySetByPackageNameLPr(packageName));
}
}
@@ -22492,7 +22162,7 @@
}
IBinder ksh = ks.getToken();
if (ksh instanceof KeySetHandle) {
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return ksms.packageIsSignedByLPr(packageName, (KeySetHandle) ksh);
}
return false;
@@ -22518,7 +22188,7 @@
}
IBinder ksh = ks.getToken();
if (ksh instanceof KeySetHandle) {
- KeySetManagerService ksms = mSettings.mKeySetManagerService;
+ final KeySetManagerService ksms = mSettings.mKeySetManagerService;
return ksms.packageIsSignedByExactlyLPr(packageName, (KeySetHandle) ksh);
}
return false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 67e06dd..758abd7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -16,42 +16,74 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
+import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
+import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
+import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.server.EventLogTags;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.PackageDexUsage;
-import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
-import static com.android.server.pm.PackageManagerService.TAG;
-
-import com.android.internal.util.ArrayUtils;
-
import android.annotation.NonNull;
import android.app.AppGlobals;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
import android.os.Build;
import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.service.pm.PackageServiceDumpProto;
import android.system.ErrnoException;
import android.system.Os;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.jar.StrictJarFile;
-import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
+import android.util.proto.ProtoOutputStream;
+import dalvik.system.VMRuntime;
+
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.Streams;
+
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
+import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
/**
@@ -200,7 +232,7 @@
*
* If it doesn't have sufficient information about the package, it return <code>false</code>.
*/
- static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
+ public static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
@@ -263,6 +295,21 @@
return false;
}
+ public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
+ if (srcFile.isDirectory()) {
+ final File baseFile = new File(pkg.baseCodePath);
+ long maxModifiedTime = baseFile.lastModified();
+ if (pkg.splitCodePaths != null) {
+ for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+ final File splitFile = new File(pkg.splitCodePaths[i]);
+ maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+ }
+ }
+ return maxModifiedTime;
+ }
+ return srcFile.lastModified();
+ }
+
/**
* Checks that the archive located at {@code fileName} has uncompressed dex file and so
* files that can be direclty mapped.
@@ -318,6 +365,57 @@
}
}
+ private static File getSettingsProblemFile() {
+ File dataDir = Environment.getDataDirectory();
+ File systemDir = new File(dataDir, "system");
+ File fname = new File(systemDir, "uiderrors.txt");
+ return fname;
+ }
+
+ public static void dumpCriticalInfo(ProtoOutputStream proto) {
+ try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ if (line.contains("ignored: updated version")) continue;
+ proto.write(PackageServiceDumpProto.MESSAGES, line);
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ public static void dumpCriticalInfo(PrintWriter pw, String msg) {
+ try (BufferedReader in = new BufferedReader(new FileReader(getSettingsProblemFile()))) {
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ if (line.contains("ignored: updated version")) continue;
+ if (msg != null) {
+ pw.print(msg);
+ }
+ pw.println(line);
+ }
+ } catch (IOException ignored) {
+ }
+ }
+
+ public static void logCriticalInfo(int priority, String msg) {
+ Slog.println(priority, TAG, msg);
+ EventLogTags.writePmCriticalInfo(msg);
+ try {
+ File fname = getSettingsProblemFile();
+ FileOutputStream out = new FileOutputStream(fname, true);
+ PrintWriter pw = new FastPrintWriter(out);
+ SimpleDateFormat formatter = new SimpleDateFormat();
+ String dateString = formatter.format(new Date(System.currentTimeMillis()));
+ pw.println(dateString + ": " + msg);
+ pw.close();
+ FileUtils.setPermissions(
+ fname.toString(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
+ -1, -1);
+ } catch (java.io.IOException e) {
+ }
+ }
+
public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
if (callingUid == Process.SHELL_UID) {
if (userHandle >= 0
@@ -331,4 +429,240 @@
}
}
}
+
+ /**
+ * Derive the value of the {@code cpuAbiOverride} based on the provided
+ * value and an optional stored value from the package settings.
+ */
+ public static String deriveAbiOverride(String abiOverride, PackageSetting settings) {
+ String cpuAbiOverride = null;
+ if (NativeLibraryHelper.CLEAR_ABI_OVERRIDE.equals(abiOverride)) {
+ cpuAbiOverride = null;
+ } else if (abiOverride != null) {
+ cpuAbiOverride = abiOverride;
+ } else if (settings != null) {
+ cpuAbiOverride = settings.cpuAbiOverrideString;
+ }
+ return cpuAbiOverride;
+ }
+
+ /**
+ * Compares two sets of signatures. Returns:
+ * <br />
+ * {@link PackageManager#SIGNATURE_NEITHER_SIGNED}: if both signature sets are null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_FIRST_NOT_SIGNED}: if the first signature set is null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_SECOND_NOT_SIGNED}: if the second signature set is null,
+ * <br />
+ * {@link PackageManager#SIGNATURE_MATCH}: if the two signature sets are identical,
+ * <br />
+ * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
+ */
+ public static int compareSignatures(Signature[] s1, Signature[] s2) {
+ if (s1 == null) {
+ return s2 == null
+ ? PackageManager.SIGNATURE_NEITHER_SIGNED
+ : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
+ }
+
+ if (s2 == null) {
+ return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+ }
+
+ if (s1.length != s2.length) {
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ // Since both signature sets are of size 1, we can compare without HashSets.
+ if (s1.length == 1) {
+ return s1[0].equals(s2[0]) ?
+ PackageManager.SIGNATURE_MATCH :
+ PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ ArraySet<Signature> set1 = new ArraySet<Signature>();
+ for (Signature sig : s1) {
+ set1.add(sig);
+ }
+ ArraySet<Signature> set2 = new ArraySet<Signature>();
+ for (Signature sig : s2) {
+ set2.add(sig);
+ }
+ // Make sure s2 contains all signatures in s1.
+ if (set1.equals(set2)) {
+ return PackageManager.SIGNATURE_MATCH;
+ }
+ return PackageManager.SIGNATURE_NO_MATCH;
+ }
+
+ /**
+ * Used for backward compatibility to make sure any packages with
+ * certificate chains get upgraded to the new style. {@code existingSigs}
+ * will be in the old format (since they were stored on disk from before the
+ * system upgrade) and {@code scannedSigs} will be in the newer format.
+ */
+ private static boolean matchSignaturesCompat(String packageName,
+ PackageSignatures packageSignatures, Signature[] parsedSignatures) {
+ ArraySet<Signature> existingSet = new ArraySet<Signature>();
+ for (Signature sig : packageSignatures.mSignatures) {
+ existingSet.add(sig);
+ }
+ ArraySet<Signature> scannedCompatSet = new ArraySet<Signature>();
+ for (Signature sig : parsedSignatures) {
+ try {
+ Signature[] chainSignatures = sig.getChainSignatures();
+ for (Signature chainSig : chainSignatures) {
+ scannedCompatSet.add(chainSig);
+ }
+ } catch (CertificateEncodingException e) {
+ scannedCompatSet.add(sig);
+ }
+ }
+ // make sure the expanded scanned set contains all signatures in the existing one
+ if (scannedCompatSet.equals(existingSet)) {
+ // migrate the old signatures to the new scheme
+ packageSignatures.assignSignatures(parsedSignatures);
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean matchSignaturesRecover(String packageName,
+ Signature[] existingSignatures, Signature[] parsedSignatures) {
+ String msg = null;
+ try {
+ if (Signature.areEffectiveMatch(existingSignatures, parsedSignatures)) {
+ logCriticalInfo(Log.INFO,
+ "Recovered effectively matching certificates for " + packageName);
+ return true;
+ }
+ } catch (CertificateException e) {
+ msg = e.getMessage();
+ }
+ logCriticalInfo(Log.INFO,
+ "Failed to recover certificates for " + packageName + ": " + msg);
+ return false;
+ }
+
+ /**
+ * Verifies that signatures match.
+ * @returns {@code true} if the compat signatures were matched; otherwise, {@code false}.
+ * @throws PackageManagerException if the signatures did not match.
+ */
+ public static boolean verifySignatures(PackageSetting pkgSetting,
+ Signature[] parsedSignatures, boolean compareCompat, boolean compareRecover)
+ throws PackageManagerException {
+ final String packageName = pkgSetting.name;
+ boolean compatMatch = false;
+ if (pkgSetting.signatures.mSignatures != null) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.signatures.mSignatures, parsedSignatures)
+ == PackageManager.SIGNATURE_MATCH;
+ if (!match && compareCompat) {
+ match = matchSignaturesCompat(packageName, pkgSetting.signatures, parsedSignatures);
+ compatMatch = match;
+ }
+ if (!match && compareRecover) {
+ match = matchSignaturesRecover(
+ packageName, pkgSetting.signatures.mSignatures, parsedSignatures);
+ }
+ if (!match) {
+ throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+ "Package " + packageName +
+ " signatures don't match previously installed version; ignoring!");
+ }
+ }
+ // Check for shared user signatures
+ if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+ // Already existing package. Make sure signatures match
+ boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+ parsedSignatures) == PackageManager.SIGNATURE_MATCH;
+ if (!match) {
+ match = matchSignaturesCompat(
+ packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
+ }
+ if (!match && compareCompat) {
+ match = matchSignaturesRecover(
+ packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
+ compatMatch |= match;
+ }
+ if (!match && compareRecover) {
+ throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
+ "Package " + packageName
+ + " has no signatures that match those in shared user "
+ + pkgSetting.sharedUser.name + "; ignoring!");
+ }
+ }
+ return compatMatch;
+ }
+
+ public static int decompressFile(File srcFile, File dstFile) throws ErrnoException {
+ if (DEBUG_COMPRESSION) {
+ Slog.i(TAG, "Decompress file"
+ + "; src: " + srcFile.getAbsolutePath()
+ + ", dst: " + dstFile.getAbsolutePath());
+ }
+ try (
+ InputStream fileIn = new GZIPInputStream(new FileInputStream(srcFile));
+ OutputStream fileOut = new FileOutputStream(dstFile, false /*append*/);
+ ) {
+ Streams.copy(fileIn, fileOut);
+ Os.chmod(dstFile.getAbsolutePath(), 0644);
+ return PackageManager.INSTALL_SUCCEEDED;
+ } catch (IOException e) {
+ logCriticalInfo(Log.ERROR, "Failed to decompress file"
+ + "; src: " + srcFile.getAbsolutePath()
+ + ", dst: " + dstFile.getAbsolutePath());
+ }
+ return PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+ }
+
+ public static File[] getCompressedFiles(String codePath) {
+ final File stubCodePath = new File(codePath);
+ final String stubName = stubCodePath.getName();
+
+ // The layout of a compressed package on a given partition is as follows :
+ //
+ // Compressed artifacts:
+ //
+ // /partition/ModuleName/foo.gz
+ // /partation/ModuleName/bar.gz
+ //
+ // Stub artifact:
+ //
+ // /partition/ModuleName-Stub/ModuleName-Stub.apk
+ //
+ // In other words, stub is on the same partition as the compressed artifacts
+ // and in a directory that's suffixed with "-Stub".
+ int idx = stubName.lastIndexOf(STUB_SUFFIX);
+ if (idx < 0 || (stubName.length() != (idx + STUB_SUFFIX.length()))) {
+ return null;
+ }
+
+ final File stubParentDir = stubCodePath.getParentFile();
+ if (stubParentDir == null) {
+ Slog.e(TAG, "Unable to determine stub parent dir for codePath: " + codePath);
+ return null;
+ }
+
+ final File compressedPath = new File(stubParentDir, stubName.substring(0, idx));
+ final File[] files = compressedPath.listFiles(new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return name.toLowerCase().endsWith(COMPRESSED_EXTENSION);
+ }
+ });
+
+ if (DEBUG_COMPRESSION && files != null && files.length > 0) {
+ Slog.i(TAG, "getCompressedFiles[" + codePath + "]: " + Arrays.toString(files));
+ }
+
+ return files;
+ }
+
+ public static boolean compressedFileExists(String codePath) {
+ final File[] compressedFiles = getCompressedFiles(codePath);
+ return compressedFiles != null && compressedFiles.length > 0;
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index bfe09b8..96c102b 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
@@ -40,8 +42,6 @@
import java.util.Objects;
import java.util.Set;
-import static com.android.server.pm.PackageManagerService.logCriticalInfo;
-
/**
* Helper class for preparing and destroying user storage
*/
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 8014acf..c40d1fa 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -16,6 +16,8 @@
package com.android.server.pm.permission;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
+
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1110,8 +1112,8 @@
final String systemPackageName = mServiceInternal.getKnownPackageName(
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
final PackageParser.Package systemPackage = getPackage(systemPackageName);
- return PackageManagerService.compareSignatures(systemPackage.mSignatures,
- pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+ return compareSignatures(systemPackage.mSignatures, pkg.mSignatures)
+ == PackageManager.SIGNATURE_MATCH;
}
private void grantDefaultPermissionExceptions(int userId) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 6d2051f..7d8e206 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1009,10 +1009,10 @@
PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
final PackageParser.Package systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
- boolean allowed = (PackageManagerService.compareSignatures(
+ boolean allowed = (PackageManagerServiceUtils.compareSignatures(
bp.getSourceSignatures(), pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH)
- || (PackageManagerService.compareSignatures(
+ || (PackageManagerServiceUtils.compareSignatures(
systemPackage.mSignatures, pkg.mSignatures)
== PackageManager.SIGNATURE_MATCH);
if (!allowed && (privilegedPermission || oemPermission)) {
diff --git a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
index 92729dc..6886985 100644
--- a/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
+++ b/services/core/java/com/android/server/policy/BurnInProtectionHelper.java
@@ -253,7 +253,8 @@
public void onDisplayChanged(int displayId) {
if (displayId == mDisplay.getDisplayId()) {
if (mDisplay.getState() == Display.STATE_DOZE
- || mDisplay.getState() == Display.STATE_DOZE_SUSPEND) {
+ || mDisplay.getState() == Display.STATE_DOZE_SUSPEND
+ || mDisplay.getState() == Display.STATE_ON_SUSPEND) {
startBurnInProtection();
} else {
cancelBurnInProtection();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8f23cf8..416a606 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2364,9 +2364,13 @@
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) {
mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager;
- if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND
- && (mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
- mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_DOZE_SUSPEND) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_DOZE;
+ }
+ if (mDisplayPowerRequest.dozeScreenState == Display.STATE_ON_SUSPEND) {
+ mDisplayPowerRequest.dozeScreenState = Display.STATE_ON;
+ }
}
mDisplayPowerRequest.dozeScreenBrightness =
mDozeScreenBrightnessOverrideFromDreamManager;
@@ -4676,6 +4680,7 @@
case Display.STATE_OFF:
case Display.STATE_DOZE:
case Display.STATE_DOZE_SUSPEND:
+ case Display.STATE_ON_SUSPEND:
case Display.STATE_ON:
case Display.STATE_VR:
break;
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 79d46ce..cc4e0f8 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -35,6 +35,7 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import com.android.server.input.InputWindowHandle;
/**
* Managing drag and drop operations initiated by View#startDragAndDrop.
@@ -49,7 +50,12 @@
static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 2;
static final int MSG_ANIMATION_END = 3;
- DragState mDragState;
+ /**
+ * Drag state per operation.
+ * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState
+ * itself, thus the variable can be null after calling DragState's methods.
+ */
+ private DragState mDragState;
private WindowManagerService mService;
private final Handler mHandler;
@@ -58,11 +64,19 @@
return mDragState != null;
}
+ InputWindowHandle getInputWindowHandleLocked() {
+ return mDragState.getInputWindowHandle();
+ }
+
DragDropController(WindowManagerService service, Looper looper) {
mService = service;
mHandler = new DragHandler(service, looper);
}
+ void sendDragStartedIfNeededLocked(WindowState window) {
+ mDragState.sendDragStartedIfNeededLocked(window);
+ }
+
IBinder prepareDrag(SurfaceSession session, int callerPid,
int callerUid, IWindow window, int flags, int width, int height, Surface outSurface) {
if (DEBUG_DRAG) {
@@ -158,9 +172,7 @@
if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
mDragState.getInputChannel())) {
Slog.e(TAG_WM, "Unable to transfer touch focus");
- mDragState.unregister();
- mDragState.reset();
- mDragState = null;
+ mDragState.closeLocked();
return false;
}
@@ -254,6 +266,30 @@
}
}
+ /**
+ * Handles motion events.
+ * @param keepHandling Whether if the drag operation is continuing or this is the last motion
+ * event.
+ * @param newX X coordinate value in dp in the screen coordinate
+ * @param newY Y coordinate value in dp in the screen coordinate
+ */
+ void handleMotionEvent(boolean keepHandling, float newX, float newY) {
+ synchronized (mService.mWindowMap) {
+ if (!dragDropActiveLocked()) {
+ // The drag has ended but the clean-up message has not been processed by
+ // window manager. Drop events that occur after this until window manager
+ // has a chance to clean-up the input handle.
+ return;
+ }
+
+ if (keepHandling) {
+ mDragState.notifyMoveLocked(newX, newY);
+ } else {
+ mDragState.notifyDropLocked(newX, newY);
+ }
+ }
+ }
+
void dragRecipientEntered(IWindow window) {
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder());
@@ -282,6 +318,17 @@
mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS);
}
+ /**
+ * Notifies the current drag state is closed.
+ */
+ void onDragStateClosedLocked(DragState dragState) {
+ if (mDragState != dragState) {
+ Slog.wtf(TAG_WM, "Unknown drag state is closed");
+ return;
+ }
+ mDragState = null;
+ }
+
private class DragHandler extends Handler {
/**
* Lock for window manager.
@@ -304,9 +351,7 @@
synchronized (mService.mWindowMap) {
// !!! TODO: ANR the app that has failed to start the drag in time
if (mDragState != null) {
- mDragState.unregister();
- mDragState.reset();
- mDragState = null;
+ mDragState.closeLocked();
}
}
break;
@@ -346,7 +391,7 @@
"plyaing animation");
return;
}
- mDragState.onAnimationEndLocked();
+ mDragState.closeLocked();
}
break;
}
diff --git a/services/core/java/com/android/server/wm/DragInputEventReceiver.java b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
index b4bbc90..bee2bac 100644
--- a/services/core/java/com/android/server/wm/DragInputEventReceiver.java
+++ b/services/core/java/com/android/server/wm/DragInputEventReceiver.java
@@ -37,7 +37,6 @@
* Input receiver for drag and drop
*/
class DragInputEventReceiver extends InputEventReceiver {
- private final WindowManagerService mService;
private final DragDropController mDragDropController;
// Set, if stylus button was down at the start of the drag.
@@ -48,100 +47,63 @@
// are still being dispatched.
private boolean mMuteInput = false;
- public DragInputEventReceiver(InputChannel inputChannel, Looper looper,
- DragDropController controller, WindowManagerService service) {
+ DragInputEventReceiver(InputChannel inputChannel, Looper looper,
+ DragDropController controller) {
super(inputChannel, looper);
mDragDropController = controller;
- mService = service;
}
@Override
public void onInputEvent(InputEvent event, int displayId) {
boolean handled = false;
try {
- synchronized (mService.mWindowMap) {
- if (!mDragDropController.dragDropActiveLocked()) {
- // The drag has ended but the clean-up message has not been processed by
- // window manager. Drop events that occur after this until window manager
- // has a chance to clean-up the input handle.
- handled = true;
- return;
- }
- if (!(event instanceof MotionEvent)
- || (event.getSource() & SOURCE_CLASS_POINTER) == 0
- || mMuteInput) {
- return;
- }
- final MotionEvent motionEvent = (MotionEvent) event;
- boolean endDrag = false;
- final float newX = motionEvent.getRawX();
- final float newY = motionEvent.getRawY();
- final boolean isStylusButtonDown =
- (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
-
- if (mIsStartEvent) {
- if (isStylusButtonDown) {
- // First event and the button was down, check for the button being
- // lifted in the future, if that happens we'll drop the item.
- mStylusButtonDownAtStart = true;
- }
- mIsStartEvent = false;
- }
-
- switch (motionEvent.getAction()) {
- case ACTION_DOWN: {
- if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
- }
- break;
-
- case ACTION_MOVE: {
- if (mStylusButtonDownAtStart && !isStylusButtonDown) {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "Button no longer pressed; dropping at "
- + newX + "," + newY);
- }
- mMuteInput = true;
- endDrag = mDragDropController.mDragState
- .notifyDropLocked(newX, newY);
- } else {
- // move the surface and tell the involved window(s) where we are
- mDragDropController.mDragState.notifyMoveLocked(newX, newY);
- }
- }
- break;
-
- case ACTION_UP: {
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "Got UP on move channel; dropping at "
- + newX + "," + newY);
- }
- mMuteInput = true;
- endDrag = mDragDropController.mDragState
- .notifyDropLocked(newX, newY);
- }
- break;
-
- case ACTION_CANCEL: {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
- mMuteInput = true;
- endDrag = true;
- }
- break;
- }
-
- if (endDrag) {
- if (DEBUG_DRAG)
- Slog.d(TAG_WM, "Drag ended; tearing down state");
- // tell all the windows that the drag has ended
- // endDragLocked will post back to looper to dispose the receiver
- // since we still need the receiver for the last finishInputEvent.
- mDragDropController.mDragState.endDragLocked();
- mStylusButtonDownAtStart = false;
- mIsStartEvent = true;
- }
-
- handled = true;
+ if (!(event instanceof MotionEvent)
+ || (event.getSource() & SOURCE_CLASS_POINTER) == 0
+ || mMuteInput) {
+ return;
}
+ final MotionEvent motionEvent = (MotionEvent) event;
+ final float newX = motionEvent.getRawX();
+ final float newY = motionEvent.getRawY();
+ final boolean isStylusButtonDown =
+ (motionEvent.getButtonState() & BUTTON_STYLUS_PRIMARY) != 0;
+
+ if (mIsStartEvent) {
+ // First event and the button was down, check for the button being
+ // lifted in the future, if that happens we'll drop the item.
+ mStylusButtonDownAtStart = isStylusButtonDown;
+ mIsStartEvent = false;
+ }
+
+ switch (motionEvent.getAction()) {
+ case ACTION_DOWN:
+ if (DEBUG_DRAG) Slog.w(TAG_WM, "Unexpected ACTION_DOWN in drag layer");
+ return;
+ case ACTION_MOVE:
+ if (mStylusButtonDownAtStart && !isStylusButtonDown) {
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Button no longer pressed; dropping at " + newX + ","
+ + newY);
+ }
+ mMuteInput = true;
+ }
+ break;
+ case ACTION_UP:
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "Got UP on move channel; dropping at " + newX + "," + newY);
+ }
+ mMuteInput = true;
+ break;
+ case ACTION_CANCEL:
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "Drag cancelled!");
+ mMuteInput = true;
+ break;
+ default:
+ return;
+ }
+
+ mDragDropController.handleMotionEvent(!mMuteInput /* keepHandling */, newX, newY);
+ handled = true;
} catch (Exception e) {
Slog.e(TAG_WM, "Exception caught by drag handleMotion", e);
} finally {
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 861fb44..e81d366 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -105,6 +105,11 @@
WindowState mTargetWindow;
ArrayList<WindowState> mNotifiedWindows;
boolean mDragInProgress;
+ /**
+ * Whether if animation is completed. Needs to be volatile to update from the animation thread
+ * without having a WM lock.
+ */
+ volatile boolean mAnimationCompleted = false;
DisplayContent mDisplayContent;
@Nullable private ValueAnimator mAnimator;
@@ -122,21 +127,79 @@
mNotifiedWindows = new ArrayList<WindowState>();
}
- void reset() {
- if (mAnimator != null) {
+ /**
+ * After calling this, DragDropController#onDragStateClosedLocked is invoked, which causes
+ * DragDropController#mDragState becomes null.
+ */
+ void closeLocked() {
+ // Unregister the input interceptor.
+ if (mInputInterceptor != null) {
+ if (DEBUG_DRAG)
+ Slog.d(TAG_WM, "unregistering drag input channel");
+
+ // Input channel should be disposed on the thread where the input is being handled.
+ mDragDropController.sendHandlerMessage(
+ MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
+ mInputInterceptor = null;
+ mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+ }
+
+ // Send drag end broadcast if drag start has been sent.
+ if (mDragInProgress) {
+ final int myPid = Process.myPid();
+
+ if (DEBUG_DRAG) {
+ Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
+ }
+ for (WindowState ws : mNotifiedWindows) {
+ float x = 0;
+ float y = 0;
+ if (!mDragResult && (ws.mSession.mPid == mPid)) {
+ // Report unconsumed drop location back to the app that started the drag.
+ x = mCurrentX;
+ y = mCurrentY;
+ }
+ DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+ x, y, null, null, null, null, mDragResult);
+ try {
+ ws.mClient.dispatchDragEvent(evt);
+ } catch (RemoteException e) {
+ Slog.w(TAG_WM, "Unable to drag-end window " + ws);
+ }
+ // if the current window is in the same process,
+ // the dispatch has already recycled the event
+ if (myPid != ws.mSession.mPid) {
+ evt.recycle();
+ }
+ }
+ mNotifiedWindows.clear();
+ mDragInProgress = false;
+ }
+
+ // Take the cursor back if it has been changed.
+ if (isFromSource(InputDevice.SOURCE_MOUSE)) {
+ mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
+ mTouchSource = 0;
+ }
+
+ // Clear the internal variables.
+ if (mSurfaceControl != null) {
+ mSurfaceControl.destroy();
+ mSurfaceControl = null;
+ }
+ if (mAnimator != null && !mAnimationCompleted) {
Slog.wtf(TAG_WM,
"Unexpectedly destroying mSurfaceControl while animation is running");
}
- if (mSurfaceControl != null) {
- mSurfaceControl.destroy();
- }
- mSurfaceControl = null;
mFlags = 0;
mLocalWin = null;
mToken = null;
mData = null;
mThumbOffsetX = mThumbOffsetY = 0;
mNotifiedWindows = null;
+
+ // Notifies the controller that the drag state is closed.
+ mDragDropController.onDragStateClosedLocked(this);
}
class InputInterceptor {
@@ -151,7 +214,7 @@
mClientChannel = channels[1];
mService.mInputManager.registerInputChannel(mServerChannel, null);
mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
- mService.mH.getLooper(), mDragDropController, mService);
+ mService.mH.getLooper(), mDragDropController);
mDragApplicationHandle = new InputApplicationHandle(null);
mDragApplicationHandle.name = "drag";
@@ -235,19 +298,6 @@
}
}
- void unregister() {
- if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
- if (mInputInterceptor == null) {
- Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
- } else {
- // Input channel should be disposed on the thread where the input is being handled.
- mDragDropController.sendHandlerMessage(
- MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor);
- mInputInterceptor = null;
- mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
- }
- }
-
int getDragLayerLocked() {
return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
* WindowManagerService.TYPE_LAYER_MULTIPLIER
@@ -366,46 +416,15 @@
return false;
}
- private void broadcastDragEndedLocked() {
- final int myPid = Process.myPid();
-
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
- }
- for (WindowState ws : mNotifiedWindows) {
- float x = 0;
- float y = 0;
- if (!mDragResult && (ws.mSession.mPid == mPid)) {
- // Report unconsumed drop location back to the app that started the drag.
- x = mCurrentX;
- y = mCurrentY;
- }
- DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
- x, y, null, null, null, null, mDragResult);
- try {
- ws.mClient.dispatchDragEvent(evt);
- } catch (RemoteException e) {
- Slog.w(TAG_WM, "Unable to drag-end window " + ws);
- }
- // if the current window is in the same process,
- // the dispatch has already recycled the event
- if (myPid != ws.mSession.mPid) {
- evt.recycle();
- }
- }
- mNotifiedWindows.clear();
- mDragInProgress = false;
- }
-
void endDragLocked() {
if (mAnimator != null) {
return;
}
if (!mDragResult) {
mAnimator = createReturnAnimationLocked();
- return; // Will call cleanUpDragLw when the animation is done.
+ return; // Will call closeLocked() when the animation is done.
}
- cleanUpDragLocked();
+ closeLocked();
}
void cancelDragLocked() {
@@ -414,33 +433,15 @@
}
if (!mDragInProgress) {
// This can happen if an app invokes Session#cancelDragAndDrop before
- // Session#performDrag. Reset the drag state:
- // 1. without sending the end broadcast because the start broadcast has not been sent,
- // and
- // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to
- // WindowManagerService, which will cause DragState#reset() while playing the
- // cancel animation.
- reset();
- mDragDropController.mDragState = null;
+ // Session#performDrag. Reset the drag state without playing the cancel animation
+ // because H.DRAG_START_TIMEOUT may be sent to WindowManagerService, which will cause
+ // DragState#reset() while playing the cancel animation.
+ closeLocked();
return;
}
mAnimator = createCancelAnimationLocked();
}
- private void cleanUpDragLocked() {
- broadcastDragEndedLocked();
- if (isFromSource(InputDevice.SOURCE_MOUSE)) {
- mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
- }
-
- // stop intercepting input
- unregister();
-
- // free our resources and drop all the object references
- reset();
- mDragDropController.mDragState = null;
- }
-
void notifyMoveLocked(float x, float y) {
if (mAnimator != null) {
return;
@@ -507,34 +508,33 @@
mTargetWindow = touchedWin;
}
- // Find the drop target and tell it about the data. Returns 'true' if we can immediately
- // dispatch the global drag-ended message, 'false' if we need to wait for a
- // result from the recipient.
- boolean notifyDropLocked(float x, float y) {
+ /**
+ * Finds the drop target and tells it about the data. If the drop event is not sent to the
+ * target, invokes {@code endDragLocked} immediately.
+ */
+ void notifyDropLocked(float x, float y) {
if (mAnimator != null) {
- return false;
+ return;
}
mCurrentX = x;
mCurrentY = y;
- WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
+ final WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
if (!isWindowNotified(touchedWin)) {
// "drop" outside a valid window -- no recipient to apply a
// timeout to, and we can send the drag-ended message immediately.
mDragResult = false;
- return true;
+ endDragLocked();
+ return;
}
- if (DEBUG_DRAG) {
- Slog.d(TAG_WM, "sending DROP to " + touchedWin);
- }
+ if (DEBUG_DRAG) Slog.d(TAG_WM, "sending DROP to " + touchedWin);
final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
- DragAndDropPermissionsHandler dragAndDropPermissions = null;
- if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
- (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
+ final DragAndDropPermissionsHandler dragAndDropPermissions;
+ if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
dragAndDropPermissions = new DragAndDropPermissionsHandler(
mData,
mUid,
@@ -542,13 +542,15 @@
mFlags & DRAG_FLAGS_URI_PERMISSIONS,
mSourceUserId,
targetUserId);
+ } else {
+ dragAndDropPermissions = null;
}
if (mSourceUserId != targetUserId){
mData.fixUris(mSourceUserId);
}
final int myPid = Process.myPid();
final IBinder token = touchedWin.mClient.asBinder();
- DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+ final DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
null, null, mData, dragAndDropPermissions, false);
try {
touchedWin.mClient.dispatchDragEvent(evt);
@@ -557,23 +559,13 @@
mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token);
} catch (RemoteException e) {
Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
- return true;
+ endDragLocked();
} finally {
if (myPid != touchedWin.mSession.mPid) {
evt.recycle();
}
}
mToken = token;
- return false;
- }
-
- void onAnimationEndLocked() {
- if (mAnimator == null) {
- Slog.wtf(TAG_WM, "Unexpected null mAnimator");
- return;
- }
- mAnimator = null;
- cleanUpDragLocked();
}
private static DragEvent obtainDragEvent(WindowState win, int action,
@@ -677,6 +669,7 @@
@Override
public void onAnimationEnd(Animator animator) {
+ mAnimationCompleted = true;
// Updating mDragState requires the WM lock so continues it on the out of
// AnimationThread.
mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null);
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 40eab45..a766097 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -381,7 +381,7 @@
Log.d(TAG_WM, "Inserting drag window");
}
final InputWindowHandle dragWindowHandle =
- mService.mDragDropController.mDragState.getInputWindowHandle();
+ mService.mDragDropController.getInputWindowHandleLocked();
if (dragWindowHandle != null) {
addInputWindowHandle(dragWindowHandle);
} else {
@@ -698,7 +698,7 @@
// If there's a drag in progress and 'child' is a potential drop target,
// make sure it's been told about the drag
if (inDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
- mService.mDragDropController.mDragState.sendDragStartedIfNeededLocked(w);
+ mService.mDragDropController.sendDragStartedIfNeededLocked(w);
}
addInputWindowHandle(
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f71520e..f313f31 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7087,7 +7087,7 @@
}
synchronized (mWindowMap) {
- if (mDragDropController.mDragState != null) {
+ if (mDragDropController.dragDropActiveLocked()) {
// Drag cursor overrides the app cursor.
return;
}
diff --git a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
index daaad7a8..106f9e8 100644
--- a/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BatteryServiceTest.java
@@ -36,12 +36,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
public class BatteryServiceTest extends AndroidTestCase {
@Mock IServiceManager mMockedManager;
@Mock IHealth mMockedHal;
+ @Mock IHealth mMockedHal2;
@Mock BatteryService.HealthServiceWrapper.Callback mCallback;
@Mock BatteryService.HealthServiceWrapper.IServiceManagerSupplier mManagerSupplier;
@@ -56,6 +58,12 @@
MockitoAnnotations.initMocks(this);
}
+ @Override
+ public void tearDown() {
+ if (mWrapper != null)
+ mWrapper.getHandlerThread().quitSafely();
+ }
+
public static <T> ArgumentMatcher<T> isOneOf(Collection<T> collection) {
return new ArgumentMatcher<T>() {
@Override public boolean matches(T e) {
@@ -70,42 +78,64 @@
private void initForInstances(String... instanceNamesArr) throws Exception {
final Collection<String> instanceNames = Arrays.asList(instanceNamesArr);
doAnswer((invocation) -> {
- Slog.e("BatteryServiceTest", "health: onRegistration " + invocation.getArguments()[2]);
- ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
- IHealth.kInterfaceName,
- (String)invocation.getArguments()[1],
- true /* preexisting */);
+ // technically, preexisting is ignored by
+ // BatteryService.HealthServiceWrapper.Notification, but still call it correctly.
+ sendNotification(invocation, true);
+ sendNotification(invocation, true);
+ sendNotification(invocation, false);
return null;
}).when(mMockedManager).registerForNotifications(
eq(IHealth.kInterfaceName),
argThat(isOneOf(instanceNames)),
any(IServiceNotification.class));
- doReturn(mMockedHal).when(mMockedManager)
- .get(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
-
- doReturn(IServiceManager.Transport.HWBINDER).when(mMockedManager)
- .getTransport(eq(IHealth.kInterfaceName), argThat(isOneOf(instanceNames)));
-
doReturn(mMockedManager).when(mManagerSupplier).get();
- doReturn(mMockedHal).when(mHealthServiceSupplier)
- .get(argThat(isOneOf(instanceNames)));
+ doReturn(mMockedHal) // init calls this
+ .doReturn(mMockedHal) // notification 1
+ .doReturn(mMockedHal) // notification 2
+ .doReturn(mMockedHal2) // notification 3
+ .doThrow(new RuntimeException("Should not call getService for more than 4 times"))
+ .when(mHealthServiceSupplier).get(argThat(isOneOf(instanceNames)));
mWrapper = new BatteryService.HealthServiceWrapper();
}
+ private void waitHandlerThreadFinish() throws Exception {
+ for (int i = 0; i < 5; i++) {
+ if (!mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks()) {
+ return;
+ }
+ Thread.sleep(300);
+ }
+ assertFalse(mWrapper.getHandlerThread().getThreadHandler().hasMessagesOrCallbacks());
+ }
+
+ private static void sendNotification(InvocationOnMock invocation, boolean preexisting)
+ throws Exception {
+ ((IServiceNotification)invocation.getArguments()[2]).onRegistration(
+ IHealth.kInterfaceName,
+ (String)invocation.getArguments()[1],
+ preexisting);
+ }
+
@SmallTest
public void testWrapPreferVendor() throws Exception {
initForInstances(VENDOR, HEALTHD);
mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
- verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+ waitHandlerThreadFinish();
+ verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(VENDOR));
+ verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
+ verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(VENDOR));
}
@SmallTest
public void testUseHealthd() throws Exception {
initForInstances(HEALTHD);
mWrapper.init(mCallback, mManagerSupplier, mHealthServiceSupplier);
- verify(mCallback).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
+ waitHandlerThreadFinish();
+ verify(mCallback, times(1)).onRegistration(same(null), same(mMockedHal), eq(HEALTHD));
+ verify(mCallback, never()).onRegistration(same(mMockedHal), same(mMockedHal), anyString());
+ verify(mCallback, times(1)).onRegistration(same(mMockedHal), same(mMockedHal2), eq(HEALTHD));
}
@SmallTest
diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
index 209fdf1..9c80544 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java
@@ -67,7 +67,8 @@
// TODO: Test non-Activity windows.
// TODO: Test secondary display.
@SmallTest
-@Presubmit
+// TODO(b/68957554)
+//@Presubmit
@RunWith(AndroidJUnit4.class)
public class ScreenDecorWindowTests {
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxy.java b/telephony/java/android/telephony/ims/ImsServiceProxy.java
index 038e295..31d3db4 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxy.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxy.java
@@ -305,19 +305,6 @@
mStatusCallback = c;
}
- /**
- * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
- * method returns false, it doesn't mean that the Binder connection is not available (use
- * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
- * at this time.
- *
- * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
- * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
- */
- public boolean isBinderReady() {
- return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
- }
-
@Override
public boolean isBinderAlive() {
return mIsAvailable && mBinder != null && mBinder.isBinderAlive();
diff --git a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
index bbd5f02..7ec9229 100644
--- a/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
+++ b/telephony/java/android/telephony/ims/ImsServiceProxyCompat.java
@@ -171,6 +171,19 @@
return mBinder != null && mBinder.isBinderAlive();
}
+ /**
+ * @return Returns true if the ImsService is ready to take commands, false otherwise. If this
+ * method returns false, it doesn't mean that the Binder connection is not available (use
+ * {@link #isBinderReady()} to check that), but that the ImsService is not accepting commands
+ * at this time.
+ *
+ * For example, for DSDS devices, only one slot can be {@link ImsFeature#STATE_READY} to take
+ * commands at a time, so the other slot must stay at {@link ImsFeature#STATE_NOT_AVAILABLE}.
+ */
+ public boolean isBinderReady() {
+ return isBinderAlive() && getFeatureStatus() == ImsFeature.STATE_READY;
+ }
+
private IImsService getServiceInterface(IBinder b) {
return IImsService.Stub.asInterface(b);
}
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index 29a95e6..3367aba 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -50,6 +50,9 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
# Generate the stub source files for android.test.runner.stubs
# ============================================================
include $(CLEAR_VARS)
@@ -149,6 +152,8 @@
@echo Copying removed.txt
$(hide) $(ACP) $(ANDROID_TEST_RUNNER_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_RUNNER_REMOVED_API_FILE)
+endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
# Build the android.test.mock library
# ===================================
include $(CLEAR_VARS)
@@ -161,6 +166,9 @@
include $(BUILD_JAVA_LIBRARY)
+# For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
+ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
+
# Generate the stub source files for android.test.mock.stubs
# ==========================================================
include $(CLEAR_VARS)
@@ -257,3 +265,5 @@
# additionally, build unit tests in a separate .apk
include $(call all-makefiles-under,$(LOCAL_PATH))
+
+endif # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
diff --git a/tests/UiBench/AndroidManifest.xml b/tests/UiBench/AndroidManifest.xml
index f2de7db..c6b4a54 100644
--- a/tests/UiBench/AndroidManifest.xml
+++ b/tests/UiBench/AndroidManifest.xml
@@ -92,6 +92,15 @@
</intent-filter>
</activity>
<activity
+ android:name=".TrivialAnimationActivityWideGamut"
+ android:label="General/Trivial Animation (Wide Gamut)"
+ android:colorMode="wideColorGamut">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.android.test.uibench.TEST" />
+ </intent-filter>
+ </activity>
+ <activity
android:name=".TrivialListActivity"
android:label="General/Trivial ListView" >
<intent-filter>
diff --git a/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
new file mode 100644
index 0000000..c492753
--- /dev/null
+++ b/tests/UiBench/src/com/android/test/uibench/TrivialAnimationActivityWideGamut.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.uibench;
+
+public class TrivialAnimationActivityWideGamut extends TrivialAnimationActivity { }
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 058504d..ae67f61 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -16,6 +16,7 @@
toolSources = [
"cmd/Compile.cpp",
+ "cmd/Convert.cpp",
"cmd/Diff.cpp",
"cmd/Dump.cpp",
"cmd/Link.cpp",
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index ae32ee9..921d853 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -21,43 +21,139 @@
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"
+using ::aapt::io::IFile;
+using ::aapt::io::IFileCollection;
+using ::aapt::xml::XmlResource;
+using ::android::StringPiece;
+using ::std::unique_ptr;
+
namespace aapt {
-using xml::XmlResource;
-
-std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
- const android::StringPiece& path) {
+std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
Source source(path);
std::string error;
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
- if (!apk) {
- context->GetDiagnostics()->Error(DiagMessage(source) << error);
+ if (apk == nullptr) {
+ diag->Error(DiagMessage(path) << "failed opening zip: " << error);
return {};
}
- io::IFile* file = apk->FindFile("resources.arsc");
- if (!file) {
- context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
+ if (apk->FindFile("resources.arsc") != nullptr) {
+ return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
+ } else if (apk->FindFile("resources.pb") != nullptr) {
+ return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
+ }
+ diag->Error(DiagMessage(path) << "no resource table found");
+ return {};
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
+ const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+ io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
+ if (table_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kProtoResourceTablePath);
return {};
}
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
+ std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
+ if (in == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
+ return {};
+ }
+
+ pb::ResourceTable pb_table;
+ io::ZeroCopyInputAdaptor adaptor(in.get());
+ if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
+ diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
+ return {};
+ }
+
+ std::string error;
+ std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+ if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
+ diag->Error(DiagMessage(source)
+ << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
+ return {};
+ }
+
+ io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
+ if (manifest_in == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+ return {};
+ }
+
+ pb::XmlNode pb_node;
+ io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
+ if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
+ diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage(source)
+ << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
+ return {};
+ }
+ return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+ std::move(manifest));
+}
+
+std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
+ const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
+ io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
+ if (table_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kApkResourceTablePath);
+
+ return {};
+ }
+
+ std::unique_ptr<io::IData> data = table_file->OpenAsData();
+ if (data == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
return {};
}
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get());
+ BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
+ collection.get());
if (!parser.Parse()) {
return {};
}
- return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
+ io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
+ if (manifest_file == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
+ if (manifest_data == nullptr) {
+ diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
+ return {};
+ }
+
+ std::string error;
+ std::unique_ptr<xml::XmlResource> manifest =
+ xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
+ if (manifest == nullptr) {
+ diag->Error(DiagMessage(source)
+ << "failed to parse binary " << kAndroidManifestPath << ": " << error);
+ return {};
+ }
+ return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
+ std::move(manifest));
}
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -148,26 +244,4 @@
return true;
}
-std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
- IDiagnostics* diag = context->GetDiagnostics();
-
- io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
- if (manifest_file == nullptr) {
- diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
- return {};
- }
-
- std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
- if (manifest_data == nullptr) {
- diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
- return {};
- }
-
- std::unique_ptr<xml::XmlResource> manifest =
- xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
- if (manifest == nullptr) {
- diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
- }
- return manifest;
-}
} // namespace aapt
diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h
index d2dd5cf..ef97de3 100644
--- a/tools/aapt2/LoadedApk.h
+++ b/tools/aapt2/LoadedApk.h
@@ -29,20 +29,41 @@
namespace aapt {
+constexpr static const char kApkResourceTablePath[] = "resources.arsc";
+constexpr static const char kProtoResourceTablePath[] = "resources.pb";
+constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml";
+
// Info about an APK loaded in memory.
class LoadedApk {
public:
- LoadedApk(
- const Source& source,
- std::unique_ptr<io::IFileCollection> apk,
- std::unique_ptr<ResourceTable> table)
- : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
- virtual ~LoadedApk() = default;
+ // Loads both binary and proto APKs from disk.
+ static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
+ IDiagnostics* diag);
+
+ // Loads a proto APK from the given file collection.
+ static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection(
+ const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+ // Loads a binary APK from the given file collection.
+ static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection(
+ const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);
+
+ LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
+ std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
+ : source_(source),
+ apk_(std::move(apk)),
+ table_(std::move(table)),
+ manifest_(std::move(manifest)) {
+ }
io::IFileCollection* GetFileCollection() {
return apk_.get();
}
+ const ResourceTable* GetResourceTable() const {
+ return table_.get();
+ }
+
ResourceTable* GetResourceTable() {
return table_.get();
}
@@ -51,6 +72,10 @@
return source_;
}
+ const xml::XmlResource* GetManifest() const {
+ return manifest_.get();
+ }
+
/**
* Writes the APK on disk at the given path, while also removing the resource
* files that are not referenced in the resource table.
@@ -71,11 +96,6 @@
const TableFlattenerOptions& options, FilterChain* filters,
IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);
- /** Inflates the AndroidManifest.xml file from the APK. */
- std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);
-
- static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
- const android::StringPiece& path);
private:
DISALLOW_COPY_AND_ASSIGN(LoadedApk);
@@ -83,6 +103,7 @@
Source source_;
std::unique_ptr<io::IFileCollection> apk_;
std::unique_ptr<ResourceTable> table_;
+ std::unique_ptr<xml::XmlResource> manifest_;
};
} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 36ab30c..808b29c 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -50,7 +50,7 @@
}
static void PrintUsage() {
- std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;
+ std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
}
extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
@@ -58,6 +58,7 @@
extern int Dump(const std::vector<StringPiece>& args);
extern int Diff(const std::vector<StringPiece>& args);
extern int Optimize(const std::vector<StringPiece>& args);
+extern int Convert(const std::vector<StringPiece>& args);
static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args,
IDiagnostics* diagnostics) {
@@ -71,6 +72,8 @@
return Diff(args);
} else if (command == "optimize") {
return Optimize(args);
+ } else if (command == "convert") {
+ return Convert(args);
} else if (command == "version") {
PrintVersion();
return 0;
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
new file mode 100644
index 0000000..89ae9e8
--- /dev/null
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -0,0 +1,228 @@
+/*
+ * 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 <vector>
+
+#include "android-base/macros.h"
+#include "androidfw/StringPiece.h"
+
+#include "Flags.h"
+#include "LoadedApk.h"
+#include "ValueVisitor.h"
+#include "cmd/Util.h"
+#include "format/binary/TableFlattener.h"
+#include "format/binary/XmlFlattener.h"
+#include "format/proto/ProtoDeserialize.h"
+#include "io/BigBufferStream.h"
+#include "io/Util.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+
+using ::android::StringPiece;
+using ::std::unique_ptr;
+using ::std::vector;
+
+namespace aapt {
+
+static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
+ const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
+ BigBuffer buffer(4096);
+ XmlFlattenerOptions options = {};
+ options.use_utf16 = utf16;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context, &xml)) {
+ return false;
+ }
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
+ writer);
+}
+
+bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
+ const TableFlattenerOptions& options, IArchiveWriter* writer) {
+ if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+ return false;
+ }
+
+ BigBuffer buffer(4096);
+ TableFlattener table_flattener(options, &buffer);
+ if (!table_flattener.Consume(context, apk->GetResourceTable())) {
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
+ ArchiveEntry::kAlign, writer)) {
+ return false;
+ }
+
+ for (const auto& package : apk->GetResourceTable()->packages) {
+ for (const auto& type : package->types) {
+ for (const auto& entry : type->entries) {
+ for (const auto& config_value : entry->values) {
+ const FileReference* file = ValueCast<FileReference>(config_value->value.get());
+ if (file != nullptr) {
+ if (file->file == nullptr) {
+ context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+ << "no file associated with " << *file);
+ return false;
+ }
+
+ if (file->type == ResourceFile::Type::kProtoXml) {
+ unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+ if (in == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to open file " << *file->path);
+ return false;
+ }
+
+ pb::XmlNode pb_node;
+ io::ZeroCopyInputAdaptor adaptor(in.get());
+ if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to parse proto XML " << *file->path);
+ return false;
+ }
+
+ std::string error;
+ unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+ if (xml == nullptr) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to deserialize proto XML "
+ << *file->path << ": " << error);
+ return false;
+ }
+
+ if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize XML " << *file->path);
+ return false;
+ }
+ } else {
+ if (!io::CopyFileToArchive(context, file->file, *file->path,
+ file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
+ writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to copy file " << *file->path);
+ return false;
+ }
+ }
+
+ } // file
+ } // config_value
+ } // entry
+ } // type
+ } // package
+ return true;
+}
+
+class Context : public IAaptContext {
+ public:
+ Context() : mangler_({}), symbols_(&mangler_) {
+ }
+
+ PackageType GetPackageType() override {
+ return PackageType::kApp;
+ }
+
+ SymbolTable* GetExternalSymbols() override {
+ return &symbols_;
+ }
+
+ IDiagnostics* GetDiagnostics() override {
+ return &diag_;
+ }
+
+ const std::string& GetCompilationPackage() override {
+ return package_;
+ }
+
+ uint8_t GetPackageId() override {
+ // Nothing should call this.
+ UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
+ return 0;
+ }
+
+ NameMangler* GetNameMangler() override {
+ UNIMPLEMENTED(FATAL);
+ return nullptr;
+ }
+
+ bool IsVerbose() override {
+ return verbose_;
+ }
+
+ int GetMinSdkVersion() override {
+ return 0u;
+ }
+
+ bool verbose_ = false;
+ std::string package_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Context);
+
+ NameMangler mangler_;
+ SymbolTable symbols_;
+ StdErrDiagnostics diag_;
+};
+
+int Convert(const vector<StringPiece>& args) {
+ Context context;
+ std::string output_path;
+ TableFlattenerOptions options;
+ Flags flags =
+ Flags()
+ .RequiredFlag("-o", "Output path", &output_path)
+ .OptionalSwitch("--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.",
+ &options.use_sparse_entries)
+ .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
+ if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
+ return 1;
+ }
+
+ if (flags.GetArgs().size() != 1) {
+ std::cerr << "must supply a single proto APK\n";
+ flags.Usage("aapt2 convert", &std::cerr);
+ return 1;
+ }
+
+ const StringPiece& path = flags.GetArgs()[0];
+ unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
+ if (apk == nullptr) {
+ context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
+ return 1;
+ }
+
+ Maybe<AppInfo> app_info =
+ ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
+ if (!app_info) {
+ return 1;
+ }
+
+ context.package_ = app_info.value().package;
+
+ unique_ptr<IArchiveWriter> writer =
+ CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
+ if (writer == nullptr) {
+ return 1;
+ }
+ return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index 625c47c..fc1f1d6 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -357,8 +357,9 @@
return 1;
}
- std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
- std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[1]);
+ IDiagnostics* diag = context.GetDiagnostics();
+ std::unique_ptr<LoadedApk> apk_a = LoadedApk::LoadApkFromPath(flags.GetArgs()[0], diag);
+ std::unique_ptr<LoadedApk> apk_b = LoadedApk::LoadApkFromPath(flags.GetArgs()[1], diag);
if (!apk_a || !apk_b) {
return 1;
}
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 090c3fb..bc8f1dc 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -78,7 +78,7 @@
return false;
}
- if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+ if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to parse table: " << err);
return false;
@@ -90,7 +90,8 @@
return false;
}
- BinaryResourceParser parser(context, &table, Source(file_path), data->data(), data->size());
+ BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
+ data->data(), data->size());
if (!parser.Parse()) {
return false;
}
@@ -129,7 +130,7 @@
ResourceTable table;
err.clear();
- if (!DeserializeTableFromPb(pb_table, &table, &err)) {
+ if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
context->GetDiagnostics()->Error(DiagMessage(file_path)
<< "failed to parse table: " << err);
continue;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index e0dae1b..b372923 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -29,6 +29,7 @@
#include "AppInfo.h"
#include "Debug.h"
#include "Flags.h"
+#include "LoadedApk.h"
#include "Locale.h"
#include "NameMangler.h"
#include "ResourceUtils.h"
@@ -39,7 +40,6 @@
#include "filter/ConfigFilter.h"
#include "format/Archive.h"
#include "format/Container.h"
-#include "format/binary/BinaryResourceParser.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
@@ -71,9 +71,6 @@
namespace aapt {
-constexpr static const char kApkResourceTablePath[] = "resources.arsc";
-constexpr static const char kProtoResourceTablePath[] = "resources.pb";
-
enum class OutputFormat {
kApk,
kProto,
@@ -298,23 +295,6 @@
return false;
}
-static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data,
- size_t len, IDiagnostics* diag) {
- pb::ResourceTable pb_table;
- if (!pb_table.ParseFromArray(data, len)) {
- diag->Error(DiagMessage(source) << "invalid compiled table");
- return {};
- }
-
- std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- std::string error;
- if (!DeserializeTableFromPb(pb_table, table.get(), &error)) {
- diag->Error(DiagMessage(source) << "invalid compiled table: " << error);
- return {};
- }
- return table;
-}
-
// Inflates an XML file from the source path.
static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) {
FileInputStream fin(path);
@@ -587,7 +567,7 @@
pb::XmlNode pb_xml_node;
if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to parse proto xml");
+ << "failed to parse proto XML");
return false;
}
@@ -595,13 +575,15 @@
file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error);
if (file_op.xml_to_flatten == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
- << "failed to deserialize proto xml: " << error);
+ << "failed to deserialize proto XML: " << error);
return false;
}
} else {
- file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(),
- context_->GetDiagnostics(), file->GetSource());
+ std::string error_str;
+ file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), &error_str);
if (file_op.xml_to_flatten == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(file->GetSource())
+ << "failed to parse binary XML: " << error_str);
return false;
}
}
@@ -748,22 +730,29 @@
file_collection_(util::make_unique<io::FileCollection>()) {
}
- /**
- * Creates a SymbolTable that loads symbols from the various APKs and caches
- * the results for faster lookup.
- */
+ // Creates a SymbolTable that loads symbols from the various APKs.
bool LoadSymbolsFromIncludePaths() {
- std::unique_ptr<AssetManagerSymbolSource> asset_source =
- util::make_unique<AssetManagerSymbolSource>();
+ auto asset_source = util::make_unique<AssetManagerSymbolSource>();
for (const std::string& path : options_.include_paths) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "including " << path);
}
- // First try to load the file as a static lib.
- std::string error_str;
- std::unique_ptr<ResourceTable> include_static = LoadStaticLibrary(path, &error_str);
- if (include_static) {
+ std::string error;
+ auto zip_collection = io::ZipFileCollection::Create(path, &error);
+ if (zip_collection == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to open APK: " << error);
+ return false;
+ }
+
+ if (zip_collection->FindFile(kProtoResourceTablePath) != nullptr) {
+ // Load this as a static library include.
+ std::unique_ptr<LoadedApk> static_apk = LoadedApk::LoadProtoApkFromFileCollection(
+ Source(path), std::move(zip_collection), context_->GetDiagnostics());
+ if (static_apk == nullptr) {
+ return false;
+ }
+
if (context_->GetPackageType() != PackageType::kStaticLib) {
// Can't include static libraries when not building a static library (they have no IDs
// assigned).
@@ -772,13 +761,15 @@
return false;
}
- // If we are using --no-static-lib-packages, we need to rename the
- // package of this table to our compilation package.
+ ResourceTable* table = static_apk->GetResourceTable();
+
+ // If we are using --no-static-lib-packages, we need to rename the package of this table to
+ // our compilation package.
if (options_.no_static_lib_packages) {
// Since package names can differ, and multiple packages can exist in a ResourceTable,
// we place the requirement that all static libraries are built with the package
// ID 0x7f. So if one is not found, this is an error.
- if (ResourceTablePackage* pkg = include_static->FindPackageById(kAppPackageId)) {
+ if (ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId)) {
pkg->name = context_->GetCompilationPackage();
} else {
context_->GetDiagnostics()->Error(DiagMessage(path)
@@ -788,19 +779,14 @@
}
context_->GetExternalSymbols()->AppendSource(
- util::make_unique<ResourceTableSymbolSource>(include_static.get()));
-
- static_table_includes_.push_back(std::move(include_static));
-
- } else if (!error_str.empty()) {
- // We had an error with reading, so fail.
- context_->GetDiagnostics()->Error(DiagMessage(path) << error_str);
- return false;
- }
-
- if (!asset_source->AddAssetPath(path)) {
- context_->GetDiagnostics()->Error(DiagMessage(path) << "failed to load include path");
- return false;
+ util::make_unique<ResourceTableSymbolSource>(table));
+ static_library_includes_.push_back(std::move(static_apk));
+ } else {
+ if (!asset_source->AddAssetPath(path)) {
+ context_->GetDiagnostics()->Error(DiagMessage()
+ << "failed to load include path " << path);
+ return false;
+ }
}
}
@@ -1194,46 +1180,18 @@
return true;
}
- std::unique_ptr<ResourceTable> LoadStaticLibrary(const std::string& input,
- std::string* out_error) {
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::Create(input, out_error);
- if (!collection) {
- return {};
- }
- return LoadTablePbFromCollection(collection.get());
- }
-
- std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) {
- io::IFile* file = collection->FindFile(kProtoResourceTablePath);
- if (!file) {
- return {};
- }
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- return LoadTableFromPb(file->GetSource(), data->data(), data->size(),
- context_->GetDiagnostics());
- }
-
bool MergeStaticLibrary(const std::string& input, bool override) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage() << "merging static library " << input);
}
- std::string error_str;
- std::unique_ptr<io::ZipFileCollection> collection =
- io::ZipFileCollection::Create(input, &error_str);
- if (!collection) {
- context_->GetDiagnostics()->Error(DiagMessage(input) << error_str);
- return false;
- }
-
- std::unique_ptr<ResourceTable> table = LoadTablePbFromCollection(collection.get());
- if (!table) {
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(input, context_->GetDiagnostics());
+ if (apk == nullptr) {
context_->GetDiagnostics()->Error(DiagMessage(input) << "invalid static library");
return false;
}
+ ResourceTable* table = apk->GetResourceTable();
ResourceTablePackage* pkg = table->FindPackageById(kAppPackageId);
if (!pkg) {
context_->GetDiagnostics()->Error(DiagMessage(input) << "static library has no package");
@@ -1254,13 +1212,12 @@
// Clear the package name, so as to make the resources look like they are coming from the
// local package.
pkg->name = "";
- result = table_merger_->Merge(Source(input), table.get(), override, collection.get());
+ result = table_merger_->Merge(Source(input), table, override);
} else {
// This is the proper way to merge libraries, where the package name is
// preserved and resource names are mangled.
- result =
- table_merger_->MergeAndMangle(Source(input), pkg->name, table.get(), collection.get());
+ result = table_merger_->MergeAndMangle(Source(input), pkg->name, table);
}
if (!result) {
@@ -1268,31 +1225,10 @@
}
// Make sure to move the collection into the set of IFileCollections.
- collections_.push_back(std::move(collection));
+ merged_apks_.push_back(std::move(apk));
return true;
}
- bool MergeResourceTable(io::IFile* file, bool override) {
- if (context_->IsVerbose()) {
- context_->GetDiagnostics()->Note(DiagMessage() << "merging resource table "
- << file->GetSource());
- }
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) << "failed to open file");
- return false;
- }
-
- std::unique_ptr<ResourceTable> table =
- LoadTableFromPb(file->GetSource(), data->data(), data->size(), context_->GetDiagnostics());
- if (!table) {
- return false;
- }
-
- return table_merger_->Merge(file->GetSource(), table.get(), override);
- }
-
bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) {
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage()
@@ -1423,7 +1359,7 @@
ResourceTable table;
std::string error;
- if (!DeserializeTableFromPb(pb_table, &table, &error)) {
+ if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
context_->GetDiagnostics()->Error(DiagMessage(src)
<< "failed to deserialize resource table: " << error);
return false;
@@ -1868,13 +1804,15 @@
// A pointer to the FileCollection representing the filesystem (not archives).
std::unique_ptr<io::FileCollection> file_collection_;
- // A vector of IFileCollections. This is mainly here to keep ownership of the
+ // A vector of IFileCollections. This is mainly here to retain ownership of the
// collections.
std::vector<std::unique_ptr<io::IFileCollection>> collections_;
- // A vector of ResourceTables. This is here to retain ownership, so that the
- // SymbolTable can use these.
- std::vector<std::unique_ptr<ResourceTable>> static_table_includes_;
+ // The set of merged APKs. This is mainly here to retain ownership of the APKs.
+ std::vector<std::unique_ptr<LoadedApk>> merged_apks_;
+
+ // The set of included APKs (not merged). This is mainly here to retain ownership of the APKs.
+ std::vector<std::unique_ptr<LoadedApk>> static_library_includes_;
// The set of shared libraries being used, mapping their assigned package ID to package name.
std::map<size_t, std::string> shared_libs_;
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 44e148e..688b6a7 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -281,15 +281,14 @@
OptimizeContext* context_;
};
-bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
+bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
OptimizeOptions* out_options) {
- std::unique_ptr<xml::XmlResource> manifest = apk->InflateManifest(context);
+ const xml::XmlResource* manifest = apk->GetManifest();
if (manifest == nullptr) {
return false;
}
- Maybe<AppInfo> app_info =
- ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
+ Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
if (!app_info) {
context->GetDiagnostics()->Error(DiagMessage()
<< "failed to extract data from AndroidManifest.xml");
@@ -355,7 +354,7 @@
}
const std::string& apk_path = flags.GetArgs()[0];
- std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
+ std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
if (!apk) {
return 1;
}
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 708bed8..d39f43e 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -214,9 +214,10 @@
return doc;
}
-static Maybe<std::string> ExtractCompiledString(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+static Maybe<std::string> ExtractCompiledString(const xml::Attribute& attr,
+ std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
if (compiled_str != nullptr) {
if (!compiled_str->value->empty()) {
return *compiled_str->value;
@@ -230,16 +231,16 @@
}
// Fallback to the plain text value if there is one.
- if (!attr->value.empty()) {
- return attr->value;
+ if (!attr.value.empty()) {
+ return attr.value;
}
*out_error = "value is an empty string";
return {};
}
-static Maybe<uint32_t> ExtractCompiledInt(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<uint32_t> ExtractCompiledInt(const xml::Attribute& attr, std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -251,19 +252,19 @@
}
// Fallback to the plain text value if there is one.
- Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr->value);
+ Maybe<uint32_t> integer = ResourceUtils::ParseInt(attr.value);
if (integer) {
return integer;
}
std::stringstream error_msg;
- error_msg << "'" << attr->value << "' is not a valid integer";
+ error_msg << "'" << attr.value << "' is not a valid integer";
*out_error = error_msg.str();
return {};
}
-static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error) {
- if (attr->compiled_value != nullptr) {
- BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr->compiled_value.get());
+static Maybe<int> ExtractSdkVersion(const xml::Attribute& attr, std::string* out_error) {
+ if (attr.compiled_value != nullptr) {
+ const BinaryPrimitive* compiled_prim = ValueCast<BinaryPrimitive>(attr.compiled_value.get());
if (compiled_prim != nullptr) {
if (compiled_prim->value.dataType >= android::Res_value::TYPE_FIRST_INT &&
compiled_prim->value.dataType <= android::Res_value::TYPE_LAST_INT) {
@@ -273,7 +274,7 @@
return {};
}
- String* compiled_str = ValueCast<String>(attr->compiled_value.get());
+ const String* compiled_str = ValueCast<String>(attr.compiled_value.get());
if (compiled_str != nullptr) {
Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(*compiled_str->value);
if (sdk_version) {
@@ -288,19 +289,20 @@
}
// Fallback to the plain text value if there is one.
- Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr->value);
+ Maybe<int> sdk_version = ResourceUtils::ParseSdkVersion(attr.value);
if (sdk_version) {
return sdk_version;
}
std::stringstream error_msg;
- error_msg << "'" << attr->value << "' is not a valid SDK version";
+ error_msg << "'" << attr.value << "' is not a valid SDK version";
*out_error = error_msg.str();
return {};
}
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) {
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+ IDiagnostics* diag) {
// Make sure the first element is <manifest> with package attribute.
- xml::Element* manifest_el = xml_res->root.get();
+ const xml::Element* manifest_el = xml_res.root.get();
if (manifest_el == nullptr) {
return {};
}
@@ -308,63 +310,63 @@
AppInfo app_info;
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
- diag->Error(DiagMessage(xml_res->file.source) << "root tag must be <manifest>");
+ diag->Error(DiagMessage(xml_res.file.source) << "root tag must be <manifest>");
return {};
}
- xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
+ const xml::Attribute* package_attr = manifest_el->FindAttribute({}, "package");
if (!package_attr) {
- diag->Error(DiagMessage(xml_res->file.source) << "<manifest> must have a 'package' attribute");
+ diag->Error(DiagMessage(xml_res.file.source) << "<manifest> must have a 'package' attribute");
return {};
}
std::string error_msg;
- Maybe<std::string> maybe_package = ExtractCompiledString(package_attr, &error_msg);
+ Maybe<std::string> maybe_package = ExtractCompiledString(*package_attr, &error_msg);
if (!maybe_package) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid package name: " << error_msg);
return {};
}
app_info.package = maybe_package.value();
- if (xml::Attribute* version_code_attr =
+ if (const xml::Attribute* version_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "versionCode")) {
- Maybe<uint32_t> maybe_code = ExtractCompiledInt(version_code_attr, &error_msg);
+ Maybe<uint32_t> maybe_code = ExtractCompiledInt(*version_code_attr, &error_msg);
if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid android:versionCode: " << error_msg);
return {};
}
app_info.version_code = maybe_code.value();
}
- if (xml::Attribute* revision_code_attr =
+ if (const xml::Attribute* revision_code_attr =
manifest_el->FindAttribute(xml::kSchemaAndroid, "revisionCode")) {
- Maybe<uint32_t> maybe_code = ExtractCompiledInt(revision_code_attr, &error_msg);
+ Maybe<uint32_t> maybe_code = ExtractCompiledInt(*revision_code_attr, &error_msg);
if (!maybe_code) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid android:revisionCode: " << error_msg);
return {};
}
app_info.revision_code = maybe_code.value();
}
- if (xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
- Maybe<std::string> maybe_split_name = ExtractCompiledString(split_name_attr, &error_msg);
+ if (const xml::Attribute* split_name_attr = manifest_el->FindAttribute({}, "split")) {
+ Maybe<std::string> maybe_split_name = ExtractCompiledString(*split_name_attr, &error_msg);
if (!maybe_split_name) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(manifest_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(manifest_el->line_number))
<< "invalid split name: " << error_msg);
return {};
}
app_info.split_name = maybe_split_name.value();
}
- if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
- if (xml::Attribute* min_sdk =
+ if (const xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
+ if (const xml::Attribute* min_sdk =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
- Maybe<int> maybe_sdk = ExtractSdkVersion(min_sdk, &error_msg);
+ Maybe<int> maybe_sdk = ExtractSdkVersion(*min_sdk, &error_msg);
if (!maybe_sdk) {
- diag->Error(DiagMessage(xml_res->file.source.WithLine(uses_sdk_el->line_number))
+ diag->Error(DiagMessage(xml_res.file.source.WithLine(uses_sdk_el->line_number))
<< "invalid android:minSdkVersion: " << error_msg);
return {};
}
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index fd9b39c..7611c15 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -57,7 +57,8 @@
const SplitConstraints& constraints);
// Extracts relevant info from the AndroidManifest.xml.
-Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag);
+Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(const xml::XmlResource& xml_res,
+ IDiagnostics* diag);
} // namespace aapt
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 95eec4a..66510b0 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -73,29 +73,22 @@
} // namespace
-BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
+BinaryResourceParser::BinaryResourceParser(IDiagnostics* diag, ResourceTable* table,
const Source& source, const void* data, size_t len,
io::IFileCollection* files)
- : context_(context),
- table_(table),
- source_(source),
- data_(data),
- data_len_(len),
- files_(files) {
+ : diag_(diag), table_(table), source_(source), data_(data), data_len_(len), files_(files) {
}
bool BinaryResourceParser::Parse() {
ResChunkPullParser parser(data_, data_len_);
if (!ResChunkPullParser::IsGoodEvent(parser.Next())) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt resources.arsc: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt resources.arsc: " << parser.error());
return false;
}
if (parser.chunk()->type != android::RES_TABLE_TYPE) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << StringPrintf("unknown chunk of type 0x%02x",
+ diag_->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x",
static_cast<int>(parser.chunk()->type)));
return false;
}
@@ -106,13 +99,12 @@
if (parser.Next() != ResChunkPullParser::Event::kEndDocument) {
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
+ diag_->Warn(DiagMessage(source_)
+ << "invalid chunk trailing RES_TABLE_TYPE: " << parser.error());
} else {
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << StringPrintf(
- "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
- static_cast<int>(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << StringPrintf("unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE",
+ static_cast<int>(parser.chunk()->type)));
}
}
return true;
@@ -122,7 +114,7 @@
bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) {
const ResTable_header* table_header = ConvertTo<ResTable_header>(chunk);
if (!table_header) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_header chunk");
return false;
}
@@ -135,17 +127,15 @@
status_t err =
value_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt string pool in ResTable: "
- << value_pool_.getError());
+ diag_->Error(DiagMessage(source_)
+ << "corrupt string pool in ResTable: " << value_pool_.getError());
return false;
}
// Reserve some space for the strings we are going to add.
table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount());
} else {
- context_->GetDiagnostics()->Warn(DiagMessage(source_)
- << "unexpected string pool in ResTable");
+ diag_->Warn(DiagMessage(source_) << "unexpected string pool in ResTable");
}
break;
@@ -156,16 +146,15 @@
break;
default:
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "unexpected chunk type "
- << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt resource table: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt resource table: " << parser.error());
return false;
}
return true;
@@ -176,14 +165,13 @@
sizeof(ResTable_package) - sizeof(ResTable_package::typeIdOffset);
const ResTable_package* package_header = ConvertTo<ResTable_package, kMinPackageSize>(chunk);
if (!package_header) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_package chunk");
return false;
}
uint32_t package_id = util::DeviceToHost32(package_header->id);
if (package_id > std::numeric_limits<uint8_t>::max()) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "package ID is too big (" << package_id << ")");
+ diag_->Error(DiagMessage(source_) << "package ID is too big (" << package_id << ")");
return false;
}
@@ -198,9 +186,8 @@
ResourceTablePackage* package =
table_->CreatePackage(util::Utf16ToUtf8(package_name), static_cast<uint8_t>(package_id));
if (!package) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "incompatible package '" << package_name << "' with ID "
- << package_id);
+ diag_->Error(DiagMessage(source_)
+ << "incompatible package '" << package_name << "' with ID " << package_id);
return false;
}
@@ -218,8 +205,7 @@
status_t err =
type_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt type string pool in "
+ diag_->Error(DiagMessage(source_) << "corrupt type string pool in "
<< "ResTable_package: " << type_pool_.getError());
return false;
}
@@ -227,13 +213,12 @@
status_t err =
key_pool_.setTo(parser.chunk(), util::DeviceToHost32(parser.chunk()->size));
if (err != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt key string pool in "
+ diag_->Error(DiagMessage(source_) << "corrupt key string pool in "
<< "ResTable_package: " << key_pool_.getError());
return false;
}
} else {
- context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool");
+ diag_->Warn(DiagMessage(source_) << "unexpected string pool");
}
break;
@@ -256,16 +241,15 @@
break;
default:
- context_->GetDiagnostics()->Warn(
- DiagMessage(source_) << "unexpected chunk type "
- << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
+ diag_->Warn(DiagMessage(source_)
+ << "unexpected chunk type "
+ << static_cast<int>(util::DeviceToHost16(parser.chunk()->type)));
break;
}
}
if (parser.event() == ResChunkPullParser::Event::kBadDocument) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "corrupt ResTable_package: " << parser.error());
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_package: " << parser.error());
return false;
}
@@ -278,19 +262,18 @@
bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+ diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
}
const ResTable_typeSpec* type_spec = ConvertTo<ResTable_typeSpec>(chunk);
if (!type_spec) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk");
return false;
}
if (type_spec->id == 0) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "ResTable_typeSpec has invalid id: " << type_spec->id);
+ diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
return false;
}
return true;
@@ -299,12 +282,12 @@
bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
const ResChunk_header* chunk) {
if (type_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool");
+ diag_->Error(DiagMessage(source_) << "missing type string pool");
return false;
}
if (key_pool_.getError() != NO_ERROR) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool");
+ diag_->Error(DiagMessage(source_) << "missing key string pool");
return false;
}
@@ -312,13 +295,12 @@
// a lot and has its own code to handle variable size.
const ResTable_type* type = ConvertTo<ResTable_type, kResTableTypeMinSize>(chunk);
if (!type) {
- context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
+ diag_->Error(DiagMessage(source_) << "corrupt ResTable_type chunk");
return false;
}
if (type->id == 0) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "ResTable_type has invalid id: " << (int)type->id);
+ diag_->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id);
return false;
}
@@ -329,9 +311,8 @@
const ResourceType* parsed_type = ParseResourceType(type_str);
if (!parsed_type) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "invalid type name '" << type_str << "' for type with ID "
- << (int)type->id);
+ diag_->Error(DiagMessage(source_)
+ << "invalid type name '" << type_str << "' for type with ID " << (int)type->id);
return false;
}
@@ -360,14 +341,13 @@
}
if (!resource_value) {
- context_->GetDiagnostics()->Error(DiagMessage(source_)
- << "failed to parse value for resource " << name << " ("
+ diag_->Error(DiagMessage(source_) << "failed to parse value for resource " << name << " ("
<< res_id << ") with configuration '" << config << "'");
return false;
}
if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
- context_->GetDiagnostics())) {
+ diag_)) {
return false;
}
@@ -375,7 +355,7 @@
Symbol symbol;
symbol.state = SymbolState::kPublic;
symbol.source = source_.WithLine(0);
- if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) {
+ if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
return false;
}
}
@@ -410,15 +390,14 @@
const android::Res_value& value) {
std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(name.type, config, value_pool_,
value, &table_->string_pool);
- if (files_ != nullptr && item != nullptr) {
+ if (files_ != nullptr) {
FileReference* file_ref = ValueCast<FileReference>(item.get());
if (file_ref != nullptr) {
file_ref->file = files_->FindFile(*file_ref->path);
if (file_ref->file == nullptr) {
- context_->GetDiagnostics()->Warn(DiagMessage()
- << "resource " << name << " for config '" << config
- << "' is a file reference to '" << *file_ref->path
- << "' but no such path exists");
+ diag_->Warn(DiagMessage() << "resource " << name << " for config '" << config
+ << "' is a file reference to '" << *file_ref->path
+ << "' but no such path exists");
}
}
}
@@ -432,7 +411,7 @@
case ResourceType::kStyle:
return ParseStyle(name, config, map);
case ResourceType::kAttrPrivate:
- // fallthrough
+ // fallthrough
case ResourceType::kAttr:
return ParseAttr(name, config, map);
case ResourceType::kArray:
@@ -445,8 +424,8 @@
// We can ignore the value here.
return util::make_unique<Id>();
default:
- context_->GetDiagnostics()->Error(DiagMessage() << "illegal map type '" << ToString(name.type)
- << "' (" << (int)name.type << ")");
+ diag_->Error(DiagMessage() << "illegal map type '" << ToString(name.type) << "' ("
+ << (int)name.type << ")");
break;
}
return {};
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index dc9a384..052f806 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -39,7 +39,7 @@
public:
// Creates a parser, which will read `len` bytes from `data`, and add any resources parsed to
// `table`. `source` is for logging purposes.
- BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source,
+ BinaryResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source,
const void* data, size_t data_len, io::IFileCollection* files = nullptr);
// Parses the binary resource table and returns true if successful.
@@ -80,7 +80,7 @@
*/
bool CollectMetaData(const android::ResTable_map& map_entry, Value* value);
- IAaptContext* context_;
+ IDiagnostics* diag_;
ResourceTable* table_;
const Source source_;
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 57565a5..259f2e9 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -568,14 +568,6 @@
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
table_header->packageCount = util::HostToDevice32(table->packages.size());
- // Write a self mapping entry for this package if the ID is non-standard (0x7f).
- if (context->GetPackageType() == PackageType::kApp) {
- const uint8_t package_id = context->GetPackageId();
- if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
- table->included_packages_[package_id] = context->GetCompilationPackage();
- }
- }
-
// Flatten the values string pool.
StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool);
@@ -583,6 +575,14 @@
// Flatten each package.
for (auto& package : table->packages) {
+ if (context->GetPackageType() == PackageType::kApp) {
+ // Write a self mapping entry for this package if the ID is non-standard (0x7f).
+ const uint8_t package_id = package->id.value();
+ if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
+ table->included_packages_[package_id] = package->name;
+ }
+ }
+
PackageFlattener flattener(context, package.get(), &table->included_packages_,
options_.use_sparse_entries);
if (!flattener.FlattenPackage(&package_buffer)) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 6d75973..e11890b 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -71,7 +71,8 @@
return result;
}
- BinaryResourceParser parser(context, out_table, {}, content.data(), content.size());
+ BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
+ content.size());
if (!parser.Parse()) {
return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
}
@@ -278,7 +279,7 @@
// Attempt to parse the sparse contents.
ResourceTable sparse_table;
- BinaryResourceParser parser(context.get(), &sparse_table, Source("test.arsc"),
+ BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
sparse_contents.data(), sparse_contents.size());
ASSERT_TRUE(parser.Parse());
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 86bd865..0f0bce8 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -371,7 +371,8 @@
}
static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
- ResourceTable* out_table, std::string* out_error) {
+ io::IFileCollection* files, ResourceTable* out_table,
+ std::string* out_error) {
Maybe<uint8_t> id;
if (pb_package.has_package_id()) {
id = static_cast<uint8_t>(pb_package.package_id().id());
@@ -444,7 +445,7 @@
}
config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
- &out_table->string_pool, out_error);
+ &out_table->string_pool, files, out_error);
if (config_value->value == nullptr) {
return false;
}
@@ -457,8 +458,8 @@
return true;
}
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
- std::string* out_error) {
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+ ResourceTable* out_table, std::string* out_error) {
// We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which
// causes errors when qualifying it with android::
using namespace android;
@@ -474,7 +475,7 @@
}
for (const pb::Package& pb_package : pb_table.package()) {
- if (!DeserializePackageFromPb(pb_package, source_pool, out_table, out_error)) {
+ if (!DeserializePackageFromPb(pb_package, source_pool, files, out_table, out_error)) {
return false;
}
}
@@ -600,10 +601,11 @@
std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
const android::ResStringPool& src_pool,
const ConfigDescription& config,
- StringPool* value_pool, std::string* out_error) {
+ StringPool* value_pool, io::IFileCollection* files,
+ std::string* out_error) {
std::unique_ptr<Value> value;
if (pb_value.has_item()) {
- value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, out_error);
+ value = DeserializeItemFromPb(pb_value.item(), src_pool, config, value_pool, files, out_error);
if (value == nullptr) {
return {};
}
@@ -651,8 +653,8 @@
return {};
}
DeserializeItemMetaDataFromPb(pb_entry, src_pool, &entry.key);
- entry.value =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ entry.value = DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, files,
+ out_error);
if (entry.value == nullptr) {
return {};
}
@@ -680,8 +682,8 @@
const pb::Array& pb_array = pb_compound_value.array();
std::unique_ptr<Array> array = util::make_unique<Array>();
for (const pb::Array_Element& pb_entry : pb_array.element()) {
- std::unique_ptr<Item> item =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+ value_pool, files, out_error);
if (item == nullptr) {
return {};
}
@@ -697,8 +699,8 @@
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) {
size_t plural_idx = DeserializePluralEnumFromPb(pb_entry.arity());
- plural->values[plural_idx] =
- DeserializeItemFromPb(pb_entry.item(), src_pool, config, value_pool, out_error);
+ plural->values[plural_idx] = DeserializeItemFromPb(pb_entry.item(), src_pool, config,
+ value_pool, files, out_error);
if (!plural->values[plural_idx]) {
return {};
}
@@ -727,7 +729,7 @@
std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
const android::ResStringPool& src_pool,
const ConfigDescription& config, StringPool* value_pool,
- std::string* out_error) {
+ io::IFileCollection* files, std::string* out_error) {
switch (pb_item.value_case()) {
case pb::Item::kRef: {
const pb::Reference& pb_ref = pb_item.ref();
@@ -774,6 +776,9 @@
util::make_unique<FileReference>(value_pool->MakeRef(
pb_file.path(), StringPool::Context(StringPool::Context::kHighPriority, config)));
file_ref->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ if (files != nullptr) {
+ file_ref->file = files->FindFile(*file_ref->path);
+ }
return std::move(file_ref);
} break;
@@ -825,7 +830,7 @@
}
if (pb_attr.has_compiled_item()) {
attr.compiled_value =
- DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, out_error);
+ DeserializeItemFromPb(pb_attr.compiled_item(), {}, {}, value_pool, nullptr, out_error);
if (attr.compiled_value == nullptr) {
return {};
}
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.h b/tools/aapt2/format/proto/ProtoDeserialize.h
index 7dc54f2..0c581a1 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.h
+++ b/tools/aapt2/format/proto/ProtoDeserialize.h
@@ -27,6 +27,7 @@
#include "Resources.pb.h"
#include "ResourcesInternal.pb.h"
#include "StringPool.h"
+#include "io/File.h"
#include "xml/XmlDom.h"
namespace aapt {
@@ -34,12 +35,13 @@
std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value,
const android::ResStringPool& src_pool,
const ConfigDescription& config,
- StringPool* value_pool, std::string* out_error);
+ StringPool* value_pool, io::IFileCollection* files,
+ std::string* out_error);
std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
const android::ResStringPool& src_pool,
const ConfigDescription& config, StringPool* value_pool,
- std::string* out_error);
+ io::IFileCollection* files, std::string* out_error);
std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
std::string* out_error);
@@ -50,8 +52,9 @@
bool DeserializeConfigFromPb(const pb::Configuration& pb_config, ConfigDescription* out_config,
std::string* out_error);
-bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, ResourceTable* out_table,
- std::string* out_error);
+// Optional io::IFileCollection used to lookup references to files in the ResourceTable.
+bool DeserializeTableFromPb(const pb::ResourceTable& pb_table, io::IFileCollection* files,
+ ResourceTable* out_table, std::string* out_error);
bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
ResourceFile* out_file, std::string* out_error);
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 8efac8a..9649a4d 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -29,6 +29,12 @@
namespace aapt {
+class MockFileCollection : public io::IFileCollection {
+ public:
+ MOCK_METHOD1(FindFile, io::IFile*(const StringPiece& path));
+ MOCK_METHOD0(Iterator, std::unique_ptr<io::IFileCollectionIterator>());
+};
+
TEST(ProtoSerializeTest, SerializeSinglePackage) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
@@ -86,9 +92,14 @@
pb::ResourceTable pb_table;
SerializeTableToPb(*table, &pb_table);
+ test::TestFile file_a("res/layout/main.xml");
+ MockFileCollection files;
+ EXPECT_CALL(files, FindFile(Eq("res/layout/main.xml")))
+ .WillRepeatedly(::testing::Return(&file_a));
+
ResourceTable new_table;
std::string error;
- ASSERT_TRUE(DeserializeTableFromPb(pb_table, &new_table, &error));
+ ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
EXPECT_THAT(error, IsEmpty());
Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
@@ -124,6 +135,11 @@
ASSERT_TRUE(actual_ref->id);
EXPECT_THAT(*actual_ref, Eq(expected_ref));
+ FileReference* actual_file_ref =
+ test::GetValue<FileReference>(&new_table, "com.app.a:layout/main");
+ ASSERT_THAT(actual_file_ref, NotNull());
+ EXPECT_THAT(actual_file_ref->file, Eq(&file_a));
+
StyledString* actual_styled_str =
test::GetValue<StyledString>(&new_table, "com.app.a:string/styled");
ASSERT_THAT(actual_styled_str, NotNull());
diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
index 6ed07b0..94686c0 100644
--- a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
+++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk
@@ -25,5 +25,4 @@
LOCAL_STATIC_ANDROID_LIBRARIES := \
AaptTestNamespace_LibOne \
AaptTestNamespace_LibTwo
-LOCAL_AAPT_FLAGS := -v
include $(BUILD_PACKAGE)
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 21c6b11..58d0607 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -37,17 +37,14 @@
CHECK(master_package_ != nullptr) << "package name or ID already taken";
}
-bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay,
- io::IFileCollection* collection) {
+bool TableMerger::Merge(const Source& src, ResourceTable* table, bool overlay) {
// We allow adding new resources if this is not an overlay, or if the options allow overlays
// to add new resources.
- return MergeImpl(src, table, collection, overlay,
- options_.auto_add_overlay || !overlay /*allow_new*/);
+ return MergeImpl(src, table, overlay, options_.auto_add_overlay || !overlay /*allow_new*/);
}
// This will merge packages with the same package name (or no package name).
-bool TableMerger::MergeImpl(const Source& src, ResourceTable* table,
- io::IFileCollection* collection, bool overlay, bool allow_new) {
+bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, bool overlay, bool allow_new) {
bool error = false;
for (auto& package : table->packages) {
// Only merge an empty package or the package we're building.
@@ -55,37 +52,20 @@
// This is because at compile time it is unknown if the attributes are
// simply uses of the attribute or definitions.
if (package->name.empty() || context_->GetCompilationPackage() == package->name) {
- FileMergeCallback callback;
- if (collection) {
- callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* new_file, FileReference* old_file) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->FindFile(*old_file->path);
- if (!f) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "file '" << *old_file->path << "' not found");
- return false;
- }
-
- new_file->file = f;
- return true;
- };
- }
-
// Merge here. Once the entries are merged and mangled, any references to them are still
// valid. This is because un-mangled references are mangled, then looked up at resolution
// time. Also, when linking, we convert references with no package name to use the compilation
// package name.
- error |=
- !DoMerge(src, table, package.get(), false /* mangle */, overlay, allow_new, callback);
+ error |= !DoMerge(src, table, package.get(), false /*mangle*/, overlay, allow_new);
}
}
return !error;
}
-// This will merge and mangle resources from a static library.
+// This will merge and mangle resources from a static library. It is assumed that all FileReferences
+// have correctly set their io::IFile*.
bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name,
- ResourceTable* table, io::IFileCollection* collection) {
+ ResourceTable* table) {
bool error = false;
for (auto& package : table->packages) {
// Warn of packages with an unrelated ID.
@@ -96,23 +76,7 @@
bool mangle = package_name != context_->GetCompilationPackage();
merged_packages_.insert(package->name);
-
- auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
- FileReference* new_file, FileReference* old_file) -> bool {
- // The old file's path points inside the APK, so we can use it as is.
- io::IFile* f = collection->FindFile(*old_file->path);
- if (!f) {
- context_->GetDiagnostics()->Error(DiagMessage(src)
- << "file '" << *old_file->path << "' not found");
- return false;
- }
-
- new_file->file = f;
- return true;
- };
-
- error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/,
- callback);
+ error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/);
}
return !error;
}
@@ -187,7 +151,7 @@
static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context,
const ResourceNameRef& res_name,
- const bool overlay,
+ bool overlay,
ResourceConfigValue* dst_config_value,
ResourceConfigValue* src_config_value,
StringPool* pool) {
@@ -220,10 +184,8 @@
}
bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
- ResourceTablePackage* src_package,
- const bool mangle_package, const bool overlay,
- const bool allow_new_resources,
- const FileMergeCallback& callback) {
+ ResourceTablePackage* src_package, bool mangle_package, bool overlay,
+ bool allow_new_resources) {
bool error = false;
for (auto& src_type : src_package->types) {
@@ -292,13 +254,6 @@
} else {
new_file_ref = std::unique_ptr<FileReference>(f->Clone(&master_table_->string_pool));
}
-
- if (callback) {
- if (!callback(res_name, src_config_value->config, new_file_ref.get(), f)) {
- error = true;
- continue;
- }
- }
dst_config_value->value = std::move(new_file_ref);
} else {
@@ -343,8 +298,8 @@
->FindOrCreateValue(file_desc.config, {})
->value = std::move(file_ref);
- return DoMerge(file->GetSource(), &table, pkg, false /* mangle */, overlay /* overlay */,
- true /* allow_new */, {});
+ return DoMerge(file->GetSource(), &table, pkg, false /*mangle*/, overlay /*overlay*/,
+ true /*allow_new*/);
}
} // namespace aapt
diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h
index d024aa4..47e23dd 100644
--- a/tools/aapt2/link/TableMerger.h
+++ b/tools/aapt2/link/TableMerger.h
@@ -40,6 +40,9 @@
// TableMerger takes resource tables and merges all packages within the tables that have the same
// package ID.
//
+// It is assumed that any FileReference values have their io::IFile pointer set to point to the
+// file they represent.
+//
// If a package has a different name, all the entries in that table have their names mangled
// to include the package name. This way there are no collisions. In order to do this correctly,
// the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the
@@ -60,14 +63,11 @@
// Merges resources from the same or empty package. This is for local sources.
// If overlay is true, the resources are treated as overlays.
- // An io::IFileCollection is optional and used to find the referenced Files and process them.
- bool Merge(const Source& src, ResourceTable* table, bool overlay,
- io::IFileCollection* collection = nullptr);
+ bool Merge(const Source& src, ResourceTable* table, bool overlay);
// Merges resources from the given package, mangling the name. This is for static libraries.
- // An io::IFileCollection is needed in order to find the referenced Files and process them.
- bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table,
- io::IFileCollection* collection);
+ // All FileReference values must have their io::IFile set.
+ bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table);
// Merges a compiled file that belongs to this same or empty package.
bool MergeFile(const ResourceFile& fileDesc, bool overlay, io::IFile* file);
@@ -75,23 +75,16 @@
private:
DISALLOW_COPY_AND_ASSIGN(TableMerger);
- using FileMergeCallback = std::function<bool(const ResourceNameRef&,
- const ConfigDescription& config,
- FileReference*, FileReference*)>;
-
IAaptContext* context_;
ResourceTable* master_table_;
TableMergerOptions options_;
ResourceTablePackage* master_package_;
std::set<std::string> merged_packages_;
- bool MergeImpl(const Source& src, ResourceTable* src_table,
- io::IFileCollection* collection, bool overlay, bool allow_new);
+ bool MergeImpl(const Source& src, ResourceTable* src_table, bool overlay, bool allow_new);
- bool DoMerge(const Source& src, ResourceTable* src_table,
- ResourceTablePackage* src_package, const bool mangle_package,
- const bool overlay, const bool allow_new_resources,
- const FileMergeCallback& callback);
+ bool DoMerge(const Source& src, ResourceTable* src_table, ResourceTablePackage* src_package,
+ bool mangle_package, bool overlay, bool allow_new_resources);
std::unique_ptr<FileReference> CloneAndMangleFile(const std::string& package,
const FileReference& value);
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 3499809..6aab8de 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -22,11 +22,12 @@
using ::aapt::test::ValueEq;
using ::testing::Contains;
-using ::testing::NotNull;
-using ::testing::UnorderedElementsAreArray;
-using ::testing::Pointee;
-using ::testing::Field;
using ::testing::Eq;
+using ::testing::Field;
+using ::testing::NotNull;
+using ::testing::Pointee;
+using ::testing::StrEq;
+using ::testing::UnorderedElementsAreArray;
namespace aapt {
@@ -67,10 +68,9 @@
ResourceTable final_table;
TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
- io::FileCollection collection;
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
- ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+ ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
EXPECT_TRUE(merger.merged_packages().count("com.app.b") != 0);
@@ -122,32 +122,35 @@
}
TEST_F(TableMergerTest, MergeFileReferences) {
+ test::TestFile file_a("res/xml/file.xml");
+ test::TestFile file_b("res/xml/file.xml");
+
std::unique_ptr<ResourceTable> table_a =
test::ResourceTableBuilder()
.SetPackageId("com.app.a", 0x7f)
- .AddFileReference("com.app.a:xml/file", "res/xml/file.xml")
+ .AddFileReference("com.app.a:xml/file", "res/xml/file.xml", &file_a)
.Build();
std::unique_ptr<ResourceTable> table_b =
test::ResourceTableBuilder()
.SetPackageId("com.app.b", 0x7f)
- .AddFileReference("com.app.b:xml/file", "res/xml/file.xml")
+ .AddFileReference("com.app.b:xml/file", "res/xml/file.xml", &file_b)
.Build();
ResourceTable final_table;
TableMerger merger(context_.get(), &final_table, TableMergerOptions{});
- io::FileCollection collection;
- collection.InsertFile("res/xml/file.xml");
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
- ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get(), &collection));
+ ASSERT_TRUE(merger.MergeAndMangle({}, "com.app.b", table_b.get()));
FileReference* f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/file");
ASSERT_THAT(f, NotNull());
- EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
+ EXPECT_THAT(*f->path, StrEq("res/xml/file.xml"));
+ EXPECT_THAT(f->file, Eq(&file_a));
f = test::GetValue<FileReference>(&final_table, "com.app.a:xml/com.app.b$file");
ASSERT_THAT(f, NotNull());
- EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
+ EXPECT_THAT(*f->path, StrEq("res/xml/com.app.b$file.xml"));
+ EXPECT_THAT(f->file, Eq(&file_b));
}
TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 6803088..473693c 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -265,12 +265,14 @@
const PostProcessingConfiguration& config,
std::unique_ptr<XmlResource>* updated_manifest,
IDiagnostics* diag) {
- *updated_manifest = apk_->InflateManifest(context_);
- XmlResource* manifest = updated_manifest->get();
- if (manifest == nullptr) {
+ const xml::XmlResource* apk_manifest = apk_->GetManifest();
+ if (apk_manifest == nullptr) {
return false;
}
+ *updated_manifest = apk_manifest->Clone();
+ XmlResource* manifest = updated_manifest->get();
+
// Make sure the first element is <manifest> with package attribute.
xml::Element* manifest_el = manifest->root.get();
if (manifest_el == nullptr) {
diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
index c8f3524..30c9146 100644
--- a/tools/aapt2/optimize/MultiApkGenerator_test.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp
@@ -106,7 +106,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -147,7 +147,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
@@ -186,7 +186,7 @@
TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) {
std::unique_ptr<ResourceTable> table = BuildTable();
- LoadedApk apk = {{"test.apk"}, {}, std::move(table)};
+ LoadedApk apk = {{"test.apk"}, {}, std::move(table), {}};
std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build();
PostProcessingConfiguration empty_config;
TableFlattenerOptions table_flattener_options;
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 5a62e97..ecec63f 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -78,21 +78,27 @@
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
- const StringPiece& path) {
- return AddFileReference(name, {}, path);
+ const StringPiece& path,
+ io::IFile* file) {
+ return AddFileReference(name, {}, path, file);
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const ResourceId& id,
- const StringPiece& path) {
- return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+ const StringPiece& path,
+ io::IFile* file) {
+ auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+ file_ref->file = file;
+ return AddValue(name, id, std::move(file_ref));
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const StringPiece& path,
- const ConfigDescription& config) {
- return AddValue(name, config, {},
- util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
+ const ConfigDescription& config,
+ io::IFile* file) {
+ auto file_ref = util::make_unique<FileReference>(table_->string_pool.MakeRef(path));
+ file_ref->file = file;
+ return AddValue(name, config, {}, std::move(file_ref));
}
ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 263fb55..4cdfc33 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -52,12 +52,15 @@
ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
const ConfigDescription& config, const android::StringPiece& str);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
- const android::StringPiece& path);
+ const android::StringPiece& path,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
- const android::StringPiece& path);
+ const android::StringPiece& path,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
const android::StringPiece& path,
- const ConfigDescription& config);
+ const ConfigDescription& config,
+ io::IFile* file = nullptr);
ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
std::unique_ptr<Value> value);
diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index 3522506..b0cf44a 100644
--- a/tools/aapt2/xml/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -258,8 +258,7 @@
}
}
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
- const Source& source) {
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len, std::string* out_error) {
// We import the android namespace because on Windows NO_ERROR is a macro, not
// an enum, which causes errors when qualifying it with android::
using namespace android;
@@ -270,7 +269,10 @@
std::unique_ptr<Element> pending_element;
ResXMLTree tree;
- if (tree.setTo(data, data_len) != NO_ERROR) {
+ if (tree.setTo(data, len) != NO_ERROR) {
+ if (out_error != nullptr) {
+ *out_error = "failed to initialize ResXMLTree";
+ }
return {};
}
@@ -361,6 +363,27 @@
return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root));
}
+std::unique_ptr<XmlResource> XmlResource::Clone() const {
+ std::unique_ptr<XmlResource> cloned = util::make_unique<XmlResource>(file);
+ if (root != nullptr) {
+ cloned->root = root->CloneElement([&](const xml::Element& src, xml::Element* dst) {
+ dst->attributes.reserve(src.attributes.size());
+ for (const xml::Attribute& attr : src.attributes) {
+ xml::Attribute cloned_attr;
+ cloned_attr.name = attr.name;
+ cloned_attr.namespace_uri = attr.namespace_uri;
+ cloned_attr.value = attr.value;
+ cloned_attr.compiled_attribute = attr.compiled_attribute;
+ if (attr.compiled_value != nullptr) {
+ cloned_attr.compiled_value.reset(attr.compiled_value->Clone(&cloned->string_pool));
+ }
+ dst->attributes.push_back(std::move(cloned_attr));
+ }
+ });
+ }
+ return cloned;
+}
+
Element* FindRootElement(Node* node) {
if (node == nullptr) {
return nullptr;
@@ -383,12 +406,7 @@
}
Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) {
- for (auto& attr : attributes) {
- if (ns == attr.namespace_uri && name == attr.name) {
- return &attr;
- }
- }
- return nullptr;
+ return const_cast<Attribute*>(static_cast<const Element*>(this)->FindAttribute(ns, name));
}
const Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) const {
@@ -404,17 +422,29 @@
return FindChildWithAttribute(ns, name, {}, {}, {});
}
+const Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) const {
+ return FindChildWithAttribute(ns, name, {}, {}, {});
+}
+
Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
const StringPiece& attr_ns, const StringPiece& attr_name,
const StringPiece& attr_value) {
- for (auto& child : children) {
- if (Element* el = NodeCast<Element>(child.get())) {
+ return const_cast<Element*>(static_cast<const Element*>(this)->FindChildWithAttribute(
+ ns, name, attr_ns, attr_name, attr_value));
+}
+
+const Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name,
+ const StringPiece& attr_ns,
+ const StringPiece& attr_name,
+ const StringPiece& attr_value) const {
+ for (const auto& child : children) {
+ if (const Element* el = NodeCast<Element>(child.get())) {
if (ns == el->namespace_uri && name == el->name) {
if (attr_ns.empty() && attr_name.empty()) {
return el;
}
- Attribute* attr = el->FindAttribute(attr_ns, attr_name);
+ const Attribute* attr = el->FindAttribute(attr_ns, attr_name);
if (attr && attr_value == attr->value) {
return el;
}
diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 063d7b9..cf06ba5 100644
--- a/tools/aapt2/xml/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -100,11 +100,21 @@
Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name);
const Attribute* FindAttribute(const android::StringPiece& ns,
const android::StringPiece& name) const;
+
Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name);
+ const Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name) const;
+
Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name,
const android::StringPiece& attr_ns,
const android::StringPiece& attr_name,
const android::StringPiece& attr_value);
+
+ const Element* FindChildWithAttribute(const android::StringPiece& ns,
+ const android::StringPiece& name,
+ const android::StringPiece& attr_ns,
+ const android::StringPiece& attr_name,
+ const android::StringPiece& attr_value) const;
+
std::vector<Element*> GetChildElements();
// Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method
@@ -139,16 +149,16 @@
StringPool string_pool;
std::unique_ptr<xml::Element> root;
+
+ std::unique_ptr<XmlResource> Clone() const;
};
// Inflates an XML DOM from an InputStream, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source);
-// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger.
-// Returns the root node on success, or nullptr on failure.
-std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag,
- const Source& source);
+// Inflates an XML DOM from a binary ResXMLTree.
+std::unique_ptr<XmlResource> Inflate(const void* data, size_t len,
+ std::string* out_error = nullptr);
Element* FindRootElement(Node* node);
diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index 34e6d3f..e5012d6 100644
--- a/tools/aapt2/xml/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -70,8 +70,7 @@
ASSERT_TRUE(flattener.Consume(context.get(), doc.get()));
auto block = util::Copy(buffer);
- std::unique_ptr<XmlResource> new_doc =
- Inflate(block.get(), buffer.size(), context->GetDiagnostics(), Source("test.xml"));
+ std::unique_ptr<XmlResource> new_doc = Inflate(block.get(), buffer.size(), nullptr);
ASSERT_THAT(new_doc, NotNull());
EXPECT_THAT(new_doc->root->name, StrEq("Layout"));