Merge "Fix text encoding in some macrobench tests"
diff --git a/Android.mk b/Android.mk
index 4d4baa9..06f64f1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -219,6 +219,8 @@
core/java/android/hardware/location/IGeofenceHardwareCallback.aidl \
core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
core/java/android/hardware/location/IContextHubCallback.aidl \
+ core/java/android/hardware/location/IContextHubClient.aidl \
+ core/java/android/hardware/location/IContextHubClientCallback.aidl \
core/java/android/hardware/location/IContextHubService.aidl \
core/java/android/hardware/location/IContextHubTransactionCallback.aidl \
core/java/android/hardware/radio/IRadioService.aidl \
@@ -559,6 +561,7 @@
wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
wifi/java/android/net/wifi/rtt/IRttCallback.aidl \
wifi/java/android/net/wifi/rtt/IWifiRttManager.aidl \
+ wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl \
wifi/java/android/net/wifi/IWifiScanner.aidl \
wifi/java/android/net/wifi/IRttManager.aidl \
packages/services/PacProcessor/com/android/net/IProxyService.aidl \
diff --git a/api/current.txt b/api/current.txt
index f6f74af..2f3fc26 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -343,6 +343,7 @@
field public static final int buttonBarNeutralButtonStyle = 16843914; // 0x101048a
field public static final int buttonBarPositiveButtonStyle = 16843913; // 0x1010489
field public static final int buttonBarStyle = 16843566; // 0x101032e
+ field public static final int buttonCornerRadius = 16844149; // 0x1010575
field public static final int buttonGravity = 16844030; // 0x10104fe
field public static final int buttonStyle = 16842824; // 0x1010048
field public static final int buttonStyleInset = 16842826; // 0x101004a
@@ -2314,7 +2315,9 @@
field public static final int Widget_DeviceDefault_AutoCompleteTextView = 16974151; // 0x1030147
field public static final int Widget_DeviceDefault_Button = 16974145; // 0x1030141
field public static final int Widget_DeviceDefault_Button_Borderless = 16974188; // 0x103016c
+ field public static final int Widget_DeviceDefault_Button_Borderless_Colored = 16974561; // 0x10302e1
field public static final int Widget_DeviceDefault_Button_Borderless_Small = 16974149; // 0x1030145
+ field public static final int Widget_DeviceDefault_Button_Colored = 16974560; // 0x10302e0
field public static final int Widget_DeviceDefault_Button_Inset = 16974147; // 0x1030143
field public static final int Widget_DeviceDefault_Button_Small = 16974146; // 0x1030142
field public static final int Widget_DeviceDefault_Button_Toggle = 16974148; // 0x1030144
@@ -23254,16 +23257,20 @@
field public static final int MEDIA_TRACK_TYPE_VIDEO = 1; // 0x1
}
- public class MediaRecorder {
+ public class MediaRecorder implements android.media.AudioRouting {
ctor public MediaRecorder();
+ method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
method protected void finalize();
method public static final int getAudioSourceMax();
method public int getMaxAmplitude() throws java.lang.IllegalStateException;
method public android.os.PersistableBundle getMetrics();
+ method public android.media.AudioDeviceInfo getPreferredDevice();
+ method public android.media.AudioDeviceInfo getRoutedDevice();
method public android.view.Surface getSurface();
method public void pause() throws java.lang.IllegalStateException;
method public void prepare() throws java.io.IOException, java.lang.IllegalStateException;
method public void release();
+ method public void removeOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener);
method public void reset();
method public void resume() throws java.lang.IllegalStateException;
method public void setAudioChannels(int);
@@ -23286,6 +23293,7 @@
method public void setOutputFile(java.io.File);
method public void setOutputFile(java.lang.String) throws java.lang.IllegalStateException;
method public void setOutputFormat(int) throws java.lang.IllegalStateException;
+ method public boolean setPreferredDevice(android.media.AudioDeviceInfo);
method public void setPreviewDisplay(android.view.Surface);
method public void setProfile(android.media.CamcorderProfile);
method public void setVideoEncoder(int) throws java.lang.IllegalStateException;
@@ -47898,6 +47906,7 @@
method public void interrupt();
method public static boolean isAccessibilityButtonSupported();
method public boolean isEnabled();
+ method public boolean isObservedEventType(int);
method public boolean isTouchExplorationEnabled();
method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
method public boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
diff --git a/api/test-current.txt b/api/test-current.txt
index 8647ed3..b181538 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -958,6 +958,15 @@
package android.view.accessibility {
+ public final class AccessibilityManager {
+ method public void addAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, android.os.Handler);
+ method public void removeAccessibilityServicesStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
+ }
+
+ public static abstract interface AccessibilityManager.AccessibilityServicesStateChangeListener {
+ method public abstract void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager);
+ }
+
public class AccessibilityNodeInfo implements android.os.Parcelable {
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
}
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 618e03d..c019d5d 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define DEBUG true
+#define DEBUG true // STOPSHIP if true
#include "Log.h"
#include "StatsService.h"
@@ -80,10 +80,10 @@
sp<IStatsCompanionService> sc = getStatsCompanionService();
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
- ALOGD("Could not find StatsCompanionService");
+ VLOG("Could not find StatsCompanionService");
} else if (receiver.first.size() == 0) {
- ALOGD("Statscompanion could not find a broadcast receiver for %s",
- key.ToString().c_str());
+ VLOG("Statscompanion could not find a broadcast receiver for %s",
+ key.ToString().c_str());
} else {
sc->sendBroadcast(String16(receiver.first.c_str()),
String16(receiver.second.c_str()));
@@ -226,10 +226,6 @@
return cmd_print_stats(out, args);
}
- if (!args[0].compare(String8("clear-config"))) {
- return cmd_remove_config_files(out);
- }
-
if (!args[0].compare(String8("meminfo"))) {
return cmd_dump_memory_info(out);
}
@@ -263,11 +259,6 @@
fprintf(out, " Prints the UID, app name, version mapping.\n");
fprintf(out, "\n");
fprintf(out, "\n");
- fprintf(out, "usage: adb shell cmd stats clear-config \n");
- fprintf(out, "\n");
- fprintf(out, " Removes all configs from disk.\n");
- fprintf(out, "\n");
- fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats pull-source [int] \n");
fprintf(out, "\n");
fprintf(out, " Prints the output of a pulled metrics source (int indicates source)\n");
@@ -278,17 +269,20 @@
fprintf(out, " Flushes all data on memory to disk.\n");
fprintf(out, "\n");
fprintf(out, "\n");
- fprintf(out, "usage: adb shell cmd stats config remove [UID] NAME\n");
+ fprintf(out, "usage: adb shell cmd stats config remove [UID] [NAME]\n");
fprintf(out, "usage: adb shell cmd stats config update [UID] NAME\n");
fprintf(out, "\n");
fprintf(out, " Adds, updates or removes a configuration. The proto should be in\n");
- fprintf(out, " wire-encoded protobuf format and passed via stdin.\n");
+ fprintf(out, " wire-encoded protobuf format and passed via stdin. If no UID and name is\n");
+ fprintf(out, " provided, then all configs will be removed from memory and disk.\n");
fprintf(out, "\n");
fprintf(out, " UID The uid to use. It is only possible to pass the UID\n");
- fprintf(out, " parameter on eng builds. If UID is omitted the calling\n");
+ fprintf(out, " parameter on eng builds. If UID is omitted the calling\n");
fprintf(out, " uid is used.\n");
fprintf(out, " NAME The per-uid name to use\n");
fprintf(out, "\n");
+ fprintf(out, "\n *Note: If both UID and NAME are omitted then all configs will\n");
+ fprintf(out, "\n be removed from memory and disk!\n");
fprintf(out, "\n");
fprintf(out, "usage: adb shell cmd stats dump-report [UID] NAME\n");
fprintf(out, " Dump all metric data for a configuration.\n");
@@ -349,10 +343,10 @@
sp<IStatsCompanionService> sc = getStatsCompanionService();
if (sc != nullptr) {
sc->sendBroadcast(String16(receiver.first.c_str()), String16(receiver.second.c_str()));
- ALOGD("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
- args[2].c_str());
+ VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
+ args[2].c_str());
} else {
- ALOGD("Could not access statsCompanion");
+ VLOG("Could not access statsCompanion");
}
return NO_ERROR;
@@ -416,8 +410,12 @@
// Add / update the config.
mConfigManager->UpdateConfig(ConfigKey(uid, name), config);
} else {
- // Remove the config.
- mConfigManager->RemoveConfig(ConfigKey(uid, name));
+ if (argCount == 2) {
+ cmd_remove_all_configs(out);
+ } else {
+ // Remove the config.
+ mConfigManager->RemoveConfig(ConfigKey(uid, name));
+ }
}
return NO_ERROR;
@@ -526,8 +524,10 @@
return UNKNOWN_ERROR;
}
-status_t StatsService::cmd_remove_config_files(FILE* out) {
- fprintf(out, "Trying to remove config files...\n");
+status_t StatsService::cmd_remove_all_configs(FILE* out) {
+ fprintf(out, "Removing all configs...\n");
+ VLOG("StatsService::cmd_remove_all_configs was called");
+ mConfigManager->RemoveAllConfigs();
StorageManager::deleteAllFiles(STATS_SERVICE_DIR);
return NO_ERROR;
}
@@ -541,7 +541,7 @@
Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
const vector<String16>& app) {
- if (DEBUG) ALOGD("StatsService::informAllUidData was called");
+ VLOG("StatsService::informAllUidData was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -549,13 +549,13 @@
}
mUidMap->updateMap(uid, version, app);
- if (DEBUG) ALOGD("StatsService::informAllUidData succeeded");
+ VLOG("StatsService::informAllUidData succeeded");
return Status::ok();
}
Status StatsService::informOnePackage(const String16& app, int32_t uid, int32_t version) {
- if (DEBUG) ALOGD("StatsService::informOnePackage was called");
+ VLOG("StatsService::informOnePackage was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -566,7 +566,7 @@
}
Status StatsService::informOnePackageRemoved(const String16& app, int32_t uid) {
- if (DEBUG) ALOGD("StatsService::informOnePackageRemoved was called");
+ VLOG("StatsService::informOnePackageRemoved was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -577,14 +577,14 @@
}
Status StatsService::informAnomalyAlarmFired() {
- if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired was called");
+ VLOG("StatsService::informAnomalyAlarmFired was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
"Only system uid can call informAnomalyAlarmFired");
}
- if (DEBUG) ALOGD("StatsService::informAnomalyAlarmFired succeeded");
+ VLOG("StatsService::informAnomalyAlarmFired succeeded");
uint64_t currentTimeSec = time(nullptr);
std::unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> anomalySet =
mAnomalyMonitor->popSoonerThan(static_cast<uint32_t>(currentTimeSec));
@@ -593,7 +593,7 @@
}
Status StatsService::informPollAlarmFired() {
- if (DEBUG) ALOGD("StatsService::informPollAlarmFired was called");
+ VLOG("StatsService::informPollAlarmFired was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -602,7 +602,7 @@
mStatsPullerManager.OnAlarmFired();
- if (DEBUG) ALOGD("StatsService::informPollAlarmFired succeeded");
+ VLOG("StatsService::informPollAlarmFired succeeded");
return Status::ok();
}
@@ -614,7 +614,7 @@
}
// When system_server is up and running, schedule the dropbox task to run.
- ALOGD("StatsService::systemRunning");
+ VLOG("StatsService::systemRunning");
sayHiToStatsCompanion();
@@ -627,7 +627,7 @@
"Only system uid can call systemRunning");
}
- ALOGD("StatsService::writeDataToDisk");
+ VLOG("StatsService::writeDataToDisk");
mProcessor->WriteDataToDisk();
@@ -639,10 +639,10 @@
// purposes.
sp<IStatsCompanionService> statsCompanion = getStatsCompanionService();
if (statsCompanion != nullptr) {
- if (DEBUG) ALOGD("Telling statsCompanion that statsd is ready");
+ VLOG("Telling statsCompanion that statsd is ready");
statsCompanion->statsdReady();
} else {
- if (DEBUG) ALOGD("Could not access statsCompanion");
+ VLOG("Could not access statsCompanion");
}
}
@@ -662,7 +662,7 @@
}
Status StatsService::statsCompanionReady() {
- if (DEBUG) ALOGD("StatsService::statsCompanionReady was called");
+ VLOG("StatsService::statsCompanionReady was called");
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -675,7 +675,7 @@
Status::EX_NULL_POINTER,
"statscompanion unavailable despite it contacting statsd!");
}
- if (DEBUG) ALOGD("StatsService::statsCompanionReady linking to statsCompanion.");
+ VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
mAnomalyMonitor->setStatsCompanionService(statsCompanion);
@@ -692,8 +692,7 @@
Status StatsService::getData(const String16& key, vector<uint8_t>* output) {
IPCThreadState* ipc = IPCThreadState::self();
- ALOGD("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(),
- ipc->getCallingUid());
+ VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
if (checkCallingPermission(String16(kPermissionDump))) {
string keyStr = string(String8(key).string());
ConfigKey configKey(ipc->getCallingUid(), keyStr);
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index a32595a..007227e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -162,9 +162,9 @@
status_t cmd_print_pulled_metrics(FILE* out, const Vector<String8>& args);
/**
- * Removes all configs stored on disk.
+ * Removes all configs stored on disk and on memory.
*/
- status_t cmd_remove_config_files(FILE* out);
+ status_t cmd_remove_all_configs(FILE* out);
/*
* Dump memory usage by statsd.
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 445ee59..4b7e49a 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -102,8 +102,8 @@
// Remove from map
if (it->first.GetUid() == uid) {
removed.push_back(it->first);
- it = mConfigs.erase(it);
mConfigReceivers.erase(it->first);
+ it = mConfigs.erase(it);
} else {
it++;
}
@@ -118,6 +118,28 @@
}
}
+void ConfigManager::RemoveAllConfigs() {
+ vector<ConfigKey> removed;
+
+ for (auto it = mConfigs.begin(); it != mConfigs.end();) {
+ // Remove from map
+ removed.push_back(it->first);
+ auto receiverIt = mConfigReceivers.find(it->first);
+ if (receiverIt != mConfigReceivers.end()) {
+ mConfigReceivers.erase(it->first);
+ }
+ it = mConfigs.erase(it);
+ }
+
+ // Remove separately so if they do anything in the callback they can't mess up our iteration.
+ for (auto& key : removed) {
+ // Tell everyone
+ for (auto& listener : mListeners) {
+ listener->OnConfigRemoved(key);
+ }
+ }
+}
+
vector<ConfigKey> ConfigManager::GetAllConfigKeys() const {
vector<ConfigKey> ret;
for (auto it = mConfigs.cbegin(); it != mConfigs.cend(); ++it) {
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index 578e5ac..90ea6c0 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -98,6 +98,11 @@
void RemoveConfigs(int uid);
/**
+ * Remove all of the configs from memory.
+ */
+ void RemoveAllConfigs();
+
+ /**
* Text dump of our state for debugging.
*/
void Dump(FILE* out);
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 2ab1146..2bd3612 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -117,36 +117,60 @@
}
void StatsdStats::noteBroadcastSent(const ConfigKey& key) {
+ noteBroadcastSent(key, time(nullptr));
+}
+
+void StatsdStats::noteBroadcastSent(const ConfigKey& key, int32_t timeSec) {
lock_guard<std::mutex> lock(mLock);
auto it = mConfigStats.find(key);
if (it == mConfigStats.end()) {
ALOGE("Config key %s not found!", key.ToString().c_str());
return;
}
-
- it->second.add_broadcast_sent_time_sec(time(nullptr));
+ if (it->second.broadcast_sent_time_sec_size() >= kMaxTimestampCount) {
+ auto timestampList = it->second.mutable_broadcast_sent_time_sec();
+ // This is O(N) operation. It shouldn't happen often, and N is only 20.
+ timestampList->erase(timestampList->begin());
+ }
+ it->second.add_broadcast_sent_time_sec(timeSec);
}
void StatsdStats::noteDataDropped(const ConfigKey& key) {
+ noteDataDropped(key, time(nullptr));
+}
+
+void StatsdStats::noteDataDropped(const ConfigKey& key, int32_t timeSec) {
lock_guard<std::mutex> lock(mLock);
auto it = mConfigStats.find(key);
if (it == mConfigStats.end()) {
ALOGE("Config key %s not found!", key.ToString().c_str());
return;
}
-
- it->second.add_data_drop_time_sec(time(nullptr));
+ if (it->second.data_drop_time_sec_size() >= kMaxTimestampCount) {
+ auto timestampList = it->second.mutable_data_drop_time_sec();
+ // This is O(N) operation. It shouldn't happen often, and N is only 20.
+ timestampList->erase(timestampList->begin());
+ }
+ it->second.add_data_drop_time_sec(timeSec);
}
void StatsdStats::noteMetricsReportSent(const ConfigKey& key) {
+ noteMetricsReportSent(key, time(nullptr));
+}
+
+void StatsdStats::noteMetricsReportSent(const ConfigKey& key, int32_t timeSec) {
lock_guard<std::mutex> lock(mLock);
auto it = mConfigStats.find(key);
if (it == mConfigStats.end()) {
ALOGE("Config key %s not found!", key.ToString().c_str());
return;
}
-
- it->second.add_dump_report_time_sec(time(nullptr));
+ if (it->second.dump_report_time_sec_size() >= kMaxTimestampCount) {
+ auto timestampList = it->second.mutable_dump_report_time_sec();
+ // This is O(N) operation. It shouldn't happen often, and N is only 20.
+ timestampList->erase(timestampList->begin());
+ }
+ it->second.add_dump_report_time_sec(timeSec);
}
void StatsdStats::noteConditionDimensionSize(const ConfigKey& key, const string& name, int size) {
@@ -201,6 +225,14 @@
mMetricsStats.clear();
std::fill(mPushedAtomStats.begin(), mPushedAtomStats.end(), 0);
mMatcherStats.clear();
+ for (auto& config : mConfigStats) {
+ config.second.clear_broadcast_sent_time_sec();
+ config.second.clear_data_drop_time_sec();
+ config.second.clear_dump_report_time_sec();
+ config.second.clear_matcher_stats();
+ config.second.clear_condition_stats();
+ config.second.clear_metric_stats();
+ }
}
void StatsdStats::addSubStatsToConfig(const ConfigKey& key,
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 6fd9e4b..451144f 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -43,6 +43,8 @@
const static int kMaxMetricCountPerConfig = 300;
const static int kMaxMatcherCountPerConfig = 500;
+ const static int kMaxTimestampCount = 20;
+
/**
* Report a new config has been received and report the static stats about the config.
*
@@ -162,11 +164,18 @@
void addSubStatsToConfig(const ConfigKey& key, StatsdStatsReport_ConfigStats& configStats);
+ void noteDataDropped(const ConfigKey& key, int32_t timeSec);
+
+ void noteMetricsReportSent(const ConfigKey& key, int32_t timeSec);
+
+ void noteBroadcastSent(const ConfigKey& key, int32_t timeSec);
+
FRIEND_TEST(StatsdStatsTest, TestValidConfigAdd);
FRIEND_TEST(StatsdStatsTest, TestInvalidConfigAdd);
FRIEND_TEST(StatsdStatsTest, TestConfigRemove);
FRIEND_TEST(StatsdStatsTest, TestSubStats);
FRIEND_TEST(StatsdStatsTest, TestAtomLog);
+ FRIEND_TEST(StatsdStatsTest, TestTimestampThreshold);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 4338399..0064240 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -94,8 +94,6 @@
}
void CountMetricProducer::startNewProtoOutputStream(long long startTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
-
mProto = std::make_unique<ProtoOutputStream>();
mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name());
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
@@ -109,8 +107,13 @@
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
}
-void CountMetricProducer::serializeBuckets() {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
+std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
+ long long endTime = time(nullptr) * NS_PER_SEC;
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushIfNeeded(endTime);
VLOG("metric %s dump report now...", mMetric.name().c_str());
for (const auto& counter : mPastBuckets) {
@@ -156,40 +159,28 @@
}
mProto->end(wrapperToken);
}
+
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
- mPastBuckets.clear();
- // TODO: Clear mDimensionKeyMap once the report is dumped.
-}
-
-std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
- long long endTime = time(nullptr) * NS_PER_SEC;
VLOG("metric %s dump report now...", mMetric.name().c_str());
- // Dump current bucket if it's stale.
- // If current bucket is still on-going, don't force dump current bucket.
- // In finish(), We can force dump current bucket.
- flushIfNeeded(endTime);
-
- // TODO(yanglu): merge these three functions to one to avoid three locks.
- serializeBuckets();
-
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
+ mPastBuckets.clear();
return buffer;
+
+ // TODO: Clear mDimensionKeyMap once the report is dumped.
}
void CountMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mCondition = conditionMet;
}
bool CountMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
if (mCurrentSlicedCounter->find(newKey) != mCurrentSlicedCounter->end()) {
return false;
}
@@ -217,40 +208,38 @@
flushIfNeeded(eventTimeNs);
- // ===========GuardRail==============
- if (hitGuardRail(eventKey)) {
+ if (condition == false) {
return;
}
- // TODO(yanglu): move the following logic to a seperate function to make it lockable.
- {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
- if (condition == false) {
+ auto it = mCurrentSlicedCounter->find(eventKey);
+
+ if (it == mCurrentSlicedCounter->end()) {
+ // ===========GuardRail==============
+ if (hitGuardRail(eventKey)) {
return;
}
- auto it = mCurrentSlicedCounter->find(eventKey);
- if (it == mCurrentSlicedCounter->end()) {
- // create a counter for the new key
- mCurrentSlicedCounter->insert({eventKey, 1});
- } else {
- // increment the existing value
- auto& count = it->second;
- count++;
- }
- const int64_t& count = mCurrentSlicedCounter->find(eventKey)->second;
- for (auto& tracker : mAnomalyTrackers) {
- tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, count);
- }
- VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(), (long long)(count));
+ // create a counter for the new key
+ (*mCurrentSlicedCounter)[eventKey] = 1;
+ } else {
+ // increment the existing value
+ auto& count = it->second;
+ count++;
}
+
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey,
+ mCurrentSlicedCounter->find(eventKey)->second);
+ }
+
+ VLOG("metric %s %s->%lld", mMetric.name().c_str(), eventKey.c_str(),
+ (long long)(*mCurrentSlicedCounter)[eventKey]);
}
// When a new matched event comes in, we check if event falls into the current
// bucket. If not, flush the old counter to past buckets and initialize the new bucket.
void CountMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
-
if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
return;
}
@@ -284,7 +273,6 @@
// greater than actual data size as it contains each dimension of
// CountMetricData is duplicated.
size_t CountMetricProducer::byteSize() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
totalSize += pair.second.size() * kBucketSize;
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index 164dfb2..f78a199 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -75,8 +75,6 @@
void startNewProtoOutputStream(long long timestamp) override;
private:
- void serializeBuckets();
-
const CountMetric mMetric;
// TODO: Add a lock to mPastBuckets.
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 3b49b9a..a0374c0 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -104,7 +104,6 @@
}
void DurationMetricProducer::startNewProtoOutputStream(long long startTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mProto = std::make_unique<ProtoOutputStream>();
mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name());
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
@@ -112,7 +111,7 @@
}
unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
- const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) const {
+ const HashableDimensionKey& eventKey, vector<DurationBucket>& bucket) {
switch (mMetric.aggregation_type()) {
case DurationMetric_AggregationType_SUM:
return make_unique<OringDurationTracker>(
@@ -131,7 +130,6 @@
}
void DurationMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
flushIfNeeded(eventTime);
// Now for each of the on-going event, check if the condition has changed for them.
@@ -141,7 +139,6 @@
}
void DurationMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
mCondition = conditionMet;
flushIfNeeded(eventTime);
@@ -152,8 +149,15 @@
}
}
-void DurationMetricProducer::SerializeBuckets() {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
+std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
+ long long endTime = time(nullptr) * NS_PER_SEC;
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushIfNeeded(endTime);
+ VLOG("metric %s dump report now...", mMetric.name().c_str());
+
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
VLOG(" dimension key %s", hashableKey.c_str());
@@ -210,29 +214,13 @@
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
-}
-
-std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
- VLOG("metric %s dump report now...", mMetric.name().c_str());
-
- long long endTime = time(nullptr) * NS_PER_SEC;
-
- // Dump current bucket if it's stale.
- // If current bucket is still on-going, don't force dump current bucket.
- // In finish(), We can force dump current bucket.
- flushIfNeeded(endTime);
-
- SerializeBuckets();
-
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
-
startNewProtoOutputStream(endTime);
// TODO: Properly clear the old buckets.
return buffer;
}
void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTime) {
return;
}
@@ -252,7 +240,6 @@
}
bool DurationMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
// the key is not new, we are good.
if (mCurrentSlicedDuration.find(newKey) != mCurrentSlicedDuration.end()) {
return false;
@@ -278,37 +265,32 @@
const LogEvent& event, bool scheduledPull) {
flushIfNeeded(event.GetTimestampNs());
- // TODO(yanglu): move the following logic to a seperate function to make it lockable.
- {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
- if (matcherIndex == mStopAllIndex) {
- for (auto& pair : mCurrentSlicedDuration) {
- pair.second->noteStopAll(event.GetTimestampNs());
- }
+ if (matcherIndex == mStopAllIndex) {
+ for (auto& pair : mCurrentSlicedDuration) {
+ pair.second->noteStopAll(event.GetTimestampNs());
+ }
+ return;
+ }
+
+ HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+
+ if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
+ if (hitGuardRail(eventKey)) {
return;
}
+ mCurrentSlicedDuration[eventKey] = createDurationTracker(eventKey, mPastBuckets[eventKey]);
+ }
- HashableDimensionKey atomKey = getHashableKey(getDimensionKey(event, mInternalDimension));
+ auto it = mCurrentSlicedDuration.find(eventKey);
- if (mCurrentSlicedDuration.find(eventKey) == mCurrentSlicedDuration.end()) {
- if (hitGuardRail(eventKey)) {
- return;
- }
- mCurrentSlicedDuration[eventKey] =
- createDurationTracker(eventKey, mPastBuckets[eventKey]);
- }
- auto it = mCurrentSlicedDuration.find(eventKey);
-
- if (matcherIndex == mStartIndex) {
- it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
- } else if (matcherIndex == mStopIndex) {
- it->second->noteStop(atomKey, event.GetTimestampNs(), false);
- }
+ if (matcherIndex == mStartIndex) {
+ it->second->noteStart(atomKey, condition, event.GetTimestampNs(), conditionKeys);
+ } else if (matcherIndex == mStopIndex) {
+ it->second->noteStop(atomKey, event.GetTimestampNs(), false);
}
}
size_t DurationMetricProducer::byteSize() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
totalSize += pair.second.size() * kBucketSize;
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 68fff48..5b5373e 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -71,8 +71,6 @@
void startNewProtoOutputStream(long long timestamp) override;
private:
- void SerializeBuckets();
-
const DurationMetric mMetric;
// Index of the SimpleLogEntryMatcher which defines the start.
@@ -98,8 +96,8 @@
std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
mCurrentSlicedDuration;
- std::unique_ptr<DurationTracker> createDurationTracker(
- const HashableDimensionKey& eventKey, std::vector<DurationBucket>& bucket) const;
+ std::unique_ptr<DurationTracker> createDurationTracker(const HashableDimensionKey& eventKey,
+ std::vector<DurationBucket>& bucket);
bool hitGuardRail(const HashableDimensionKey& newKey);
static const size_t kBucketSize = sizeof(DurationBucket{});
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 53f112a..95a18f7 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -73,7 +73,6 @@
}
void EventMetricProducer::startNewProtoOutputStream(long long startTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mProto = std::make_unique<ProtoOutputStream>();
// TODO: We need to auto-generate the field IDs for StatsLogReport, EventMetricData,
// and StatsEvent.
@@ -90,16 +89,11 @@
std::unique_ptr<std::vector<uint8_t>> EventMetricProducer::onDumpReport() {
long long endTime = time(nullptr) * NS_PER_SEC;
- // TODO(yanglu): make this section to an util function.
- {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
- mProto->end(mProtoToken);
- mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
+ mProto->end(mProtoToken);
+ mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
- size_t bufferSize = mProto->size();
- VLOG("metric %s dump report now... proto size: %zu ", mMetric.name().c_str(), bufferSize);
- }
-
+ size_t bufferSize = mProto->size();
+ VLOG("metric %s dump report now... proto size: %zu ", mMetric.name().c_str(), bufferSize);
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
@@ -109,7 +103,6 @@
void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mCondition = conditionMet;
}
@@ -117,7 +110,6 @@
const size_t matcherIndex, const HashableDimensionKey& eventKey,
const std::map<std::string, HashableDimensionKey>& conditionKey, bool condition,
const LogEvent& event, bool scheduledPull) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
if (!condition) {
return;
}
@@ -132,7 +124,6 @@
}
size_t EventMetricProducer::byteSize() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
return mProto->bytesWritten();
}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index ed4c760..1791654 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -102,7 +102,6 @@
}
void GaugeMetricProducer::startNewProtoOutputStream(long long startTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mProto = std::make_unique<ProtoOutputStream>();
mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name());
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
@@ -112,8 +111,14 @@
void GaugeMetricProducer::finish() {
}
-void GaugeMetricProducer::SerializeBuckets() {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
+std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
+ VLOG("gauge metric %s dump report now...", mMetric.name().c_str());
+
+ // Dump current bucket if it's stale.
+ // If current bucket is still on-going, don't force dump current bucket.
+ // In finish(), We can force dump current bucket.
+ flushIfNeeded(time(nullptr) * NS_PER_SEC);
+
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
auto it = mDimensionKeyMap.find(hashableKey);
@@ -161,69 +166,50 @@
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
- mPastBuckets.clear();
-}
-
-std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
- VLOG("gauge metric %s dump report now...", mMetric.name().c_str());
-
- // Dump current bucket if it's stale.
- // If current bucket is still on-going, don't force dump current bucket.
- // In finish(), We can force dump current bucket.
- flushIfNeeded(time(nullptr) * NS_PER_SEC);
-
- SerializeBuckets();
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
+ mPastBuckets.clear();
+
return buffer;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
void GaugeMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
+ AutoMutex _l(mLock);
VLOG("Metric %s onConditionChanged", mMetric.name().c_str());
-
- // flushIfNeeded holds the write lock and is thread-safe.
flushIfNeeded(eventTime);
+ mCondition = conditionMet;
- vector<std::shared_ptr<LogEvent>> allData;
- // The following section is to update the condition and re-pull the gauge.
- // TODO(yanglu): make it a seperate lockable function.
- {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
-
- mCondition = conditionMet;
-
- // Push mode. No need to proactively pull the gauge data.
- if (mPullTagId == -1) {
- return;
- }
- if (!mCondition) {
- return;
- }
- // Already have gauge metric for the current bucket, do not do it again.
- if (mCurrentSlicedBucket->size() > 0) {
- return;
- }
- if (!mStatsPullerManager.Pull(mPullTagId, &allData)) {
- ALOGE("Stats puller failed for tag: %d", mPullTagId);
- return;
- }
+ // Push mode. No need to proactively pull the gauge data.
+ if (mPullTagId == -1) {
+ return;
}
-
- // onMatchedLogEventInternal holds the write lock and is thread-safe.
+ if (!mCondition) {
+ return;
+ }
+ // Already have gauge metric for the current bucket, do not do it again.
+ if (mCurrentSlicedBucket->size() > 0) {
+ return;
+ }
+ vector<std::shared_ptr<LogEvent>> allData;
+ if (!mStatsPullerManager.Pull(mPullTagId, &allData)) {
+ ALOGE("Stats puller failed for tag: %d", mPullTagId);
+ return;
+ }
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, false /*scheduledPull*/);
}
+ flushIfNeeded(eventTime);
}
void GaugeMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
}
-int64_t GaugeMetricProducer::getGauge(const LogEvent& event) const {
+int64_t GaugeMetricProducer::getGauge(const LogEvent& event) {
status_t err = NO_ERROR;
int64_t val = event.GetLong(mMetric.gauge_field(), &err);
if (err == NO_ERROR) {
@@ -235,14 +221,13 @@
}
void GaugeMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
- // onMatchedLogEventInternal holds the write lock and is thread-safe.
+ AutoMutex mutex(mLock);
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, true /*scheduledPull*/);
}
}
bool GaugeMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
if (mCurrentSlicedBucket->find(newKey) != mCurrentSlicedBucket->end()) {
return false;
}
@@ -266,32 +251,32 @@
const size_t matcherIndex, const HashableDimensionKey& eventKey,
const map<string, HashableDimensionKey>& conditionKey, bool condition,
const LogEvent& event, bool scheduledPull) {
- uint64_t eventTimeNs = event.GetTimestampNs();
- flushIfNeeded(eventTimeNs);
-
if (condition == false) {
return;
}
- const long gauge = getGauge(event);
- if (gauge < 0) {
- return;
- }
- if (hitGuardRail(eventKey)) {
- return;
- }
-
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
+ 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;
}
+ // When the event happens in a new bucket, flush the old buckets.
+ if (eventTimeNs >= mCurrentBucketStartTimeNs + mBucketSizeNs) {
+ flushIfNeeded(eventTimeNs);
+ }
+
// For gauge metric, we just simply use the first gauge in the given bucket.
if (!mCurrentSlicedBucket->empty()) {
return;
}
+ const long gauge = getGauge(event);
+ if (gauge >= 0) {
+ if (hitGuardRail(eventKey)) {
+ return;
+ }
(*mCurrentSlicedBucket)[eventKey] = gauge;
+ }
for (auto& tracker : mAnomalyTrackers) {
tracker->detectAndDeclareAnomaly(eventTimeNs, mCurrentBucketNum, eventKey, gauge);
}
@@ -303,7 +288,6 @@
// if data is pushed, onMatchedLogEvent will only be called through onConditionChanged() inside
// the GaugeMetricProducer while holding the lock.
void GaugeMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
if (eventTimeNs < mCurrentBucketStartTimeNs + mBucketSizeNs) {
return;
}
@@ -337,7 +321,6 @@
}
size_t GaugeMetricProducer::byteSize() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
totalSize += pair.second.size() * kBucketSize;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 8df6111..f344303 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -81,8 +81,6 @@
void startNewProtoOutputStream(long long timestamp) override;
private:
- void SerializeBuckets();
-
// The default bucket size for gauge metric is 1 second.
static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
const GaugeMetric mMetric;
@@ -91,6 +89,8 @@
// tagId for pulled data. -1 if this is not pulled
const int mPullTagId;
+ Mutex mLock;
+
// Save the past buckets and we can clear when the StatsLogReport is dumped.
// TODO: Add a lock to mPastBuckets.
std::unordered_map<HashableDimensionKey, std::vector<GaugeBucket>> mPastBuckets;
@@ -98,7 +98,7 @@
// The current bucket.
std::shared_ptr<DimToValMap> mCurrentSlicedBucket = std::make_shared<DimToValMap>();
- int64_t getGauge(const LogEvent& event) const;
+ int64_t getGauge(const LogEvent& event);
bool hitGuardRail(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 7542a94..62fb632 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -23,7 +23,6 @@
void MetricProducer::onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event,
bool scheduledPull) {
- std::unique_lock<std::shared_timed_mutex> writeLock(mRWMutex);
uint64_t eventTimeNs = event.GetTimestampNs();
// this is old event, maybe statsd restarted?
if (eventTimeNs < mStartTimeNs) {
@@ -60,16 +59,12 @@
} else {
condition = mCondition;
}
- // Unlock as onMatchedLogEventInternal is threadsafe.
- writeLock.unlock();
onMatchedLogEventInternal(matcherIndex, eventKey, conditionKeys, condition, event,
scheduledPull);
}
std::unique_ptr<std::vector<uint8_t>> MetricProducer::serializeProto() {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
-
size_t bufferSize = mProto->size();
std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize));
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 27343ad..b22ff6f 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,8 +17,6 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
-#include <shared_mutex>
-
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "config/ConfigKey.h"
@@ -139,10 +137,6 @@
long long mProtoToken;
- // Read/Write mutex to make the producer thread-safe.
- // TODO(yanglu): replace with std::shared_mutex when available in libc++.
- mutable std::shared_timed_mutex mRWMutex;
-
virtual void startNewProtoOutputStream(long long timestamp) = 0;
std::unique_ptr<std::vector<uint8_t>> serializeProto();
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index eed7841..66c8419 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -121,7 +121,6 @@
}
void ValueMetricProducer::startNewProtoOutputStream(long long startTime) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
mProto = std::make_unique<ProtoOutputStream>();
mProto->write(FIELD_TYPE_STRING | FIELD_ID_NAME, mMetric.name());
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_REPORT_NANOS, startTime);
@@ -137,8 +136,9 @@
VLOG("Metric %s onSlicedConditionMayChange", mMetric.name().c_str());
}
-void ValueMetricProducer::SerializeBuckets() {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
+std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
+ VLOG("metric %s dump report now...", mMetric.name().c_str());
+
for (const auto& pair : mPastBuckets) {
const HashableDimensionKey& hashableKey = pair.first;
VLOG(" dimension key %s", hashableKey.c_str());
@@ -185,63 +185,47 @@
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
- mPastBuckets.clear();
-}
-std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
VLOG("metric %s dump report now...", mMetric.name().c_str());
-
- SerializeBuckets();
std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
+ mPastBuckets.clear();
return buffer;
+
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
- vector<shared_ptr<LogEvent>> allData;
+ AutoMutex _l(mLock);
+ mCondition = condition;
- // TODO(yanglu): move the following logic to a seperate function to make it lockable.
- {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
- mCondition = condition;
-
- if (mPullTagId == -1) {
- return;
- }
-
+ if (mPullTagId != -1) {
if (mCondition == true) {
mStatsPullerManager->RegisterReceiver(mPullTagId, this,
mMetric.bucket().bucket_size_millis());
} else if (mCondition == false) {
mStatsPullerManager->UnRegisterReceiver(mPullTagId, this);
}
- if (!mStatsPullerManager->Pull(mPullTagId, &allData)) {
- return;
- }
- }
- if (allData.size() == 0) {
+ vector<shared_ptr<LogEvent>> allData;
+ if (mStatsPullerManager->Pull(mPullTagId, &allData)) {
+ if (allData.size() == 0) {
+ return;
+ }
+ for (const auto& data : allData) {
+ onMatchedLogEvent(0, *data, false);
+ }
+ flushIfNeeded(eventTime);
+ }
return;
}
-
- // onMatchedLogEventInternal holds the write lock and is thread-safe.
- for (const auto& data : allData) {
- onMatchedLogEvent(0, *data, false);
- }
- // flushIfNeeded holds the write lock and is thread-safe.
- flushIfNeeded(eventTime);
-}
-
-bool ValueMetricProducer::IsConditionMet() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
- return mCondition == true || !mMetric.has_condition();
}
void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
- if (IsConditionMet()) {
+ AutoMutex _l(mLock);
+ if (mCondition == true || !mMetric.has_condition()) {
if (allData.size() == 0) {
return;
}
@@ -258,7 +242,6 @@
}
bool ValueMetricProducer::hitGuardRail(const HashableDimensionKey& newKey) {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
// ===========GuardRail==============
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedBucket.find(newKey) != mCurrentSlicedBucket.end()) {
@@ -279,75 +262,58 @@
return false;
}
-void ValueMetricProducer::onMatchedLogEventInternal_pull(const uint64_t& eventTimeNs,
- const HashableDimensionKey& eventKey,
- const long& value, bool scheduledPull) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
-
- if (eventTimeNs < mCurrentBucketStartTimeNs) {
- VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
- (long long)mCurrentBucketStartTimeNs);
- return;
- }
- Interval& interval = mCurrentSlicedBucket[eventKey];
- if (scheduledPull) {
- // scheduled pull always sets beginning of current bucket and end
- // of next bucket
- if (interval.raw.size() > 0) {
- interval.raw.back().second = value;
- } else {
- interval.raw.push_back(make_pair(value, value));
- }
- Interval& nextInterval = mNextSlicedBucket[eventKey];
- if (nextInterval.raw.size() == 0) {
- nextInterval.raw.push_back(make_pair(value, 0));
- } else {
- nextInterval.raw.front().first = value;
- }
- } else {
- if (mCondition == true) {
- interval.raw.push_back(make_pair(value, 0));
- } else {
- if (interval.raw.size() != 0) {
- interval.raw.back().second = value;
- } else {
- interval.tainted = true;
- VLOG("Data on condition true missing!");
- }
- }
- }
-}
-
-void ValueMetricProducer::onMatchedLogEventInternal_push(const uint64_t& eventTimeNs,
- const HashableDimensionKey& eventKey,
- const long& value) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
- if (eventTimeNs < mCurrentBucketStartTimeNs) {
- VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
- (long long)mCurrentBucketStartTimeNs);
- return;
- }
- mCurrentSlicedBucket[eventKey].raw.push_back(make_pair(value, 0));
-}
-
void ValueMetricProducer::onMatchedLogEventInternal(
const size_t matcherIndex, const HashableDimensionKey& eventKey,
const map<string, HashableDimensionKey>& conditionKey, bool condition,
const LogEvent& event, bool scheduledPull) {
uint64_t eventTimeNs = event.GetTimestampNs();
- long value = get_value(event);
+ if (eventTimeNs < mCurrentBucketStartTimeNs) {
+ VLOG("Skip event due to late arrival: %lld vs %lld", (long long)eventTimeNs,
+ (long long)mCurrentBucketStartTimeNs);
+ return;
+ }
+
if (hitGuardRail(eventKey)) {
return;
}
+ Interval& interval = mCurrentSlicedBucket[eventKey];
+
+ long value = get_value(event);
+
if (mPullTagId != -1) {
- onMatchedLogEventInternal_pull(eventTimeNs, eventKey, value, scheduledPull);
+ if (scheduledPull) {
+ // scheduled pull always sets beginning of current bucket and end
+ // of next bucket
+ if (interval.raw.size() > 0) {
+ interval.raw.back().second = value;
+ } else {
+ interval.raw.push_back(make_pair(value, value));
+ }
+ Interval& nextInterval = mNextSlicedBucket[eventKey];
+ if (nextInterval.raw.size() == 0) {
+ nextInterval.raw.push_back(make_pair(value, 0));
+ } else {
+ nextInterval.raw.front().first = value;
+ }
+ } else {
+ if (mCondition == true) {
+ interval.raw.push_back(make_pair(value, 0));
+ } else {
+ if (interval.raw.size() != 0) {
+ interval.raw.back().second = value;
+ } else {
+ interval.tainted = true;
+ VLOG("Data on condition true missing!");
+ }
+ }
+ }
} else {
flushIfNeeded(eventTimeNs);
- onMatchedLogEventInternal_push(eventTimeNs, eventKey, value);
+ interval.raw.push_back(make_pair(value, 0));
}
}
-long ValueMetricProducer::get_value(const LogEvent& event) const {
+long ValueMetricProducer::get_value(const LogEvent& event) {
status_t err = NO_ERROR;
long val = event.GetLong(mMetric.value_field(), &err);
if (err == NO_ERROR) {
@@ -359,7 +325,6 @@
}
void ValueMetricProducer::flushIfNeeded(const uint64_t eventTimeNs) {
- std::lock_guard<std::shared_timed_mutex> writeLock(mRWMutex);
if (mCurrentBucketStartTimeNs + mBucketSizeNs > eventTimeNs) {
VLOG("eventTime is %lld, less than next bucket start time %lld", (long long)eventTimeNs,
(long long)(mCurrentBucketStartTimeNs + mBucketSizeNs));
@@ -408,7 +373,6 @@
}
size_t ValueMetricProducer::byteSize() const {
- std::shared_lock<std::shared_timed_mutex> readLock(mRWMutex);
size_t totalSize = 0;
for (const auto& pair : mPastBuckets) {
totalSize += pair.second.size() * kBucketSize;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index e87e9da..a024bd8 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -72,16 +72,6 @@
void startNewProtoOutputStream(long long timestamp) override;
private:
- void onMatchedLogEventInternal_pull(const uint64_t& eventTimeNs,
- const HashableDimensionKey& eventKey, const long& value,
- bool scheduledPull);
- void onMatchedLogEventInternal_push(const uint64_t& eventTimeNs,
- const HashableDimensionKey& eventKey, const long& value);
-
- void SerializeBuckets();
-
- bool IsConditionMet() const;
-
const ValueMetric mMetric;
std::shared_ptr<StatsPullerManager> mStatsPullerManager;
@@ -92,6 +82,8 @@
const int pullTagId, const uint64_t startTimeNs,
std::shared_ptr<StatsPullerManager> statsPullerManager);
+ Mutex mLock;
+
// tagId for pulled data. -1 if this is not pulled
const int mPullTagId;
@@ -110,7 +102,7 @@
// TODO: Add a lock to mPastBuckets.
std::unordered_map<HashableDimensionKey, std::vector<ValueBucket>> mPastBuckets;
- long get_value(const LogEvent& event) const;
+ long get_value(const LogEvent& event);
bool hitGuardRail(const HashableDimensionKey& newKey);
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index b14b52c..a8193dd 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -224,6 +224,48 @@
EXPECT_TRUE(sensorAtomGood);
}
+TEST(StatsdStatsTest, TestTimestampThreshold) {
+ StatsdStats stats;
+ vector<int32_t> timestamps;
+ for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
+ timestamps.push_back(i);
+ }
+ ConfigKey key(0, "test");
+ stats.noteConfigReceived(key, 2, 3, 4, 5, true);
+
+ for (int i = 0; i < StatsdStats::kMaxTimestampCount; i++) {
+ stats.noteDataDropped(key, timestamps[i]);
+ stats.noteBroadcastSent(key, timestamps[i]);
+ stats.noteMetricsReportSent(key, timestamps[i]);
+ }
+
+ int32_t newTimestamp = 10000;
+
+ // now it should trigger removing oldest timestamp
+ stats.noteDataDropped(key, 10000);
+ stats.noteBroadcastSent(key, 10000);
+ stats.noteMetricsReportSent(key, 10000);
+
+ EXPECT_TRUE(stats.mConfigStats.find(key) != stats.mConfigStats.end());
+ const auto& configStats = stats.mConfigStats[key];
+
+ int maxCount = StatsdStats::kMaxTimestampCount;
+ EXPECT_EQ(maxCount, configStats.broadcast_sent_time_sec_size());
+ EXPECT_EQ(maxCount, configStats.data_drop_time_sec_size());
+ EXPECT_EQ(maxCount, configStats.dump_report_time_sec_size());
+
+ // the oldest timestamp is the second timestamp in history
+ EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+ EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+ EXPECT_EQ(1, configStats.broadcast_sent_time_sec(0));
+
+ // the last timestamp is the newest timestamp.
+ EXPECT_EQ(newTimestamp,
+ configStats.broadcast_sent_time_sec(StatsdStats::kMaxTimestampCount - 1));
+ EXPECT_EQ(newTimestamp, configStats.data_drop_time_sec(StatsdStats::kMaxTimestampCount - 1));
+ EXPECT_EQ(newTimestamp, configStats.dump_report_time_sec(StatsdStats::kMaxTimestampCount - 1));
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 06a9b06..e0d60cd 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -16,6 +16,8 @@
package android.accessibilityservice;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -47,8 +49,6 @@
import java.util.Collections;
import java.util.List;
-import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
-
/**
* This class describes an {@link AccessibilityService}. The system notifies an
* {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
@@ -554,7 +554,7 @@
}
/**
- * Updates the properties that an AccessibilitySerivice can change dynamically.
+ * Updates the properties that an AccessibilityService can change dynamically.
*
* @param other The info from which to update the properties.
*
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 95c5fb5..ffd012d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -23,6 +23,8 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
@@ -174,7 +176,7 @@
*
* {@hide}
*/
-public final class ActivityThread {
+public final class ActivityThread extends ClientTransactionHandler {
/** @hide */
public static final String TAG = "ActivityThread";
private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
@@ -401,8 +403,8 @@
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- activity.mMainThread.handleActivityConfigurationChanged(
- new ActivityConfigChangeData(token, overrideConfig), newDisplayId);
+ activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+ newDisplayId);
};
}
@@ -469,16 +471,6 @@
}
}
- static final class NewIntentData {
- List<ReferrerIntent> intents;
- IBinder token;
- boolean andPause;
- public String toString() {
- return "NewIntentData{intents=" + intents + " token=" + token
- + " andPause=" + andPause +"}";
- }
- }
-
static final class ReceiverData extends BroadcastReceiver.PendingResult {
public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
boolean ordered, boolean sticky, IBinder token, int sendingUser) {
@@ -644,14 +636,6 @@
String[] args;
}
- static final class ResultData {
- IBinder token;
- List<ResultInfo> results;
- public String toString() {
- return "ResultData{token=" + token + " results" + results + "}";
- }
- }
-
static final class ContextCleanupInfo {
ContextImpl context;
String what;
@@ -679,15 +663,6 @@
int flags;
}
- static final class ActivityConfigChangeData {
- final IBinder activityToken;
- final Configuration overrideConfig;
- public ActivityConfigChangeData(IBinder token, Configuration config) {
- activityToken = token;
- overrideConfig = config;
- }
- }
-
private class ApplicationThread extends IApplicationThread.Stub {
private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";
@@ -702,93 +677,10 @@
}
}
- public final void schedulePauseActivity(IBinder token, boolean finished,
- boolean userLeaving, int configChanges, boolean dontReport) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "pauseActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
- token,
- (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),
- configChanges,
- seq);
- }
-
- public final void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "stopActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- sendMessage(
- showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
- token, 0, configChanges, seq);
- }
-
- public final void scheduleWindowVisibility(IBinder token, boolean showWindow) {
- sendMessage(
- showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW,
- token);
- }
-
public final void scheduleSleeping(IBinder token, boolean sleeping) {
sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
}
- public final void scheduleResumeActivity(IBinder token, int processState,
- boolean isForward, Bundle resumeArgs) {
- int seq = getLifecycleSeq();
- if (DEBUG_ORDER) Slog.d(TAG, "resumeActivity " + ActivityThread.this
- + " operation received seq: " + seq);
- updateProcessState(processState, false);
- sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
- }
-
- public final void scheduleSendResult(IBinder token, List<ResultInfo> results) {
- ResultData res = new ResultData();
- res.token = token;
- res.results = results;
- sendMessage(H.SEND_RESULT, res);
- }
-
- // we use token to identify this activity without having to send the
- // activity itself back to the activity manager. (matters more with ipc)
- @Override
- public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
- CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
- int procState, Bundle state, PersistableBundle persistentState,
- List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
-
- updateProcessState(procState, false);
-
- ActivityClientRecord r = new ActivityClientRecord();
-
- r.token = token;
- r.ident = ident;
- r.intent = intent;
- r.referrer = referrer;
- r.voiceInteractor = voiceInteractor;
- r.activityInfo = info;
- r.compatInfo = compatInfo;
- r.state = state;
- r.persistentState = persistentState;
-
- r.pendingResults = pendingResults;
- r.pendingIntents = pendingNewIntents;
-
- r.startsNotResumed = notResumed;
- r.isForward = isForward;
-
- r.profilerInfo = profilerInfo;
-
- r.overrideConfig = overrideConfig;
- updatePendingConfiguration(curConfig);
-
- sendMessage(H.LAUNCH_ACTIVITY, r);
- }
-
@Override
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -798,22 +690,6 @@
configChanges, notResumed, config, overrideConfig, true, preserveWindow);
}
- public final void scheduleNewIntent(
- List<ReferrerIntent> intents, IBinder token, boolean andPause) {
- NewIntentData data = new NewIntentData();
- data.intents = intents;
- data.token = token;
- data.andPause = andPause;
-
- sendMessage(H.NEW_INTENT, data);
- }
-
- public final void scheduleDestroyActivity(IBinder token, boolean finishing,
- int configChanges) {
- sendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0,
- configChanges);
- }
-
public final void scheduleReceiver(Intent intent, ActivityInfo info,
CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
boolean sync, int sendingUser, int processState) {
@@ -949,11 +825,6 @@
sendMessage(H.SUICIDE, null);
}
- public void scheduleConfigurationChanged(Configuration config) {
- updatePendingConfiguration(config);
- sendMessage(H.CONFIGURATION_CHANGED, config);
- }
-
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
@@ -1016,20 +887,6 @@
}
@Override
- public void scheduleActivityConfigurationChanged(
- IBinder token, Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
- new ActivityConfigChangeData(token, overrideConfig));
- }
-
- @Override
- public void scheduleActivityMovedToDisplay(IBinder token, int displayId,
- Configuration overrideConfig) {
- sendMessage(H.ACTIVITY_MOVED_TO_DISPLAY,
- new ActivityConfigChangeData(token, overrideConfig), displayId);
- }
-
- @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
}
@@ -1427,26 +1284,6 @@
}
@Override
- public void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInMultiWindowMode ? 1 : 0;
- sendMessage(H.MULTI_WINDOW_MODE_CHANGED, args);
- }
-
- @Override
- public void schedulePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
- Configuration overrideConfig) throws RemoteException {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = token;
- args.arg2 = overrideConfig;
- args.argi1 = isInPipMode ? 1 : 0;
- sendMessage(H.PICTURE_IN_PICTURE_MODE_CHANGED, args);
- }
-
- @Override
public void scheduleLocalVoiceInteractionStarted(IBinder token,
IVoiceInteractor voiceInteractor) throws RemoteException {
SomeArgs args = SomeArgs.obtain();
@@ -1459,28 +1296,33 @@
public void handleTrustStorageUpdate() {
NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
}
+
+ @Override
+ public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ ActivityThread.this.scheduleTransaction(transaction);
+ }
}
- private int getLifecycleSeq() {
+ @Override
+ public void updatePendingConfiguration(Configuration config) {
+ mAppThread.updatePendingConfiguration(config);
+ }
+
+ @Override
+ public void updateProcessState(int processState, boolean fromIpc) {
+ mAppThread.updateProcessState(processState, fromIpc);
+ }
+
+ @Override
+ public int getLifecycleSeq() {
synchronized (mResourcesManager) {
return mLifecycleSeq++;
}
}
- private class H extends Handler {
- public static final int LAUNCH_ACTIVITY = 100;
- public static final int PAUSE_ACTIVITY = 101;
- public static final int PAUSE_ACTIVITY_FINISHING= 102;
- public static final int STOP_ACTIVITY_SHOW = 103;
- public static final int STOP_ACTIVITY_HIDE = 104;
- public static final int SHOW_WINDOW = 105;
- public static final int HIDE_WINDOW = 106;
- public static final int RESUME_ACTIVITY = 107;
- public static final int SEND_RESULT = 108;
- public static final int DESTROY_ACTIVITY = 109;
+ class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
- public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
@@ -1493,7 +1335,6 @@
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
- public static final int ACTIVITY_CONFIGURATION_CHANGED = 125;
public static final int RELAUNCH_ACTIVITY = 126;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
@@ -1518,30 +1359,17 @@
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
- public static final int MULTI_WINDOW_MODE_CHANGED = 152;
- public static final int PICTURE_IN_PICTURE_MODE_CHANGED = 153;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
- public static final int ACTIVITY_MOVED_TO_DISPLAY = 157;
public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+ public static final int EXECUTE_TRANSACTION = 159;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
- case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
- case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
- case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
- case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
- case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
- case SHOW_WINDOW: return "SHOW_WINDOW";
- case HIDE_WINDOW: return "HIDE_WINDOW";
- case RESUME_ACTIVITY: return "RESUME_ACTIVITY";
- case SEND_RESULT: return "SEND_RESULT";
- case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY";
case BIND_APPLICATION: return "BIND_APPLICATION";
case EXIT_APPLICATION: return "EXIT_APPLICATION";
- case NEW_INTENT: return "NEW_INTENT";
case RECEIVER: return "RECEIVER";
case CREATE_SERVICE: return "CREATE_SERVICE";
case SERVICE_ARGS: return "SERVICE_ARGS";
@@ -1553,8 +1381,6 @@
case UNBIND_SERVICE: return "UNBIND_SERVICE";
case DUMP_SERVICE: return "DUMP_SERVICE";
case LOW_MEMORY: return "LOW_MEMORY";
- case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED";
- case ACTIVITY_MOVED_TO_DISPLAY: return "ACTIVITY_MOVED_TO_DISPLAY";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PROFILER_CONTROL: return "PROFILER_CONTROL";
case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
@@ -1577,12 +1403,11 @@
case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
- case MULTI_WINDOW_MODE_CHANGED: return "MULTI_WINDOW_MODE_CHANGED";
- case PICTURE_IN_PICTURE_MODE_CHANGED: return "PICTURE_IN_PICTURE_MODE_CHANGED";
case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
case ATTACH_AGENT: return "ATTACH_AGENT";
case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+ case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
}
}
return Integer.toString(code);
@@ -1590,76 +1415,12 @@
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
- case LAUNCH_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
- final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
-
- r.packageInfo = getPackageInfoNoCheck(
- r.activityInfo.applicationInfo, r.compatInfo);
- handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
- case PAUSE_ACTIVITY: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, false,
- (args.argi1 & USER_LEAVING) != 0, args.argi2,
- (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case PAUSE_ACTIVITY_FINISHING: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
- SomeArgs args = (SomeArgs) msg.obj;
- handlePauseActivity((IBinder) args.arg1, true, (args.argi1 & USER_LEAVING) != 0,
- args.argi2, (args.argi1 & DONT_REPORT) != 0, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_SHOW: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, true, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case STOP_ACTIVITY_HIDE: {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- SomeArgs args = (SomeArgs) msg.obj;
- handleStopActivity((IBinder) args.arg1, false, args.argi2, args.argi3);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- } break;
- case SHOW_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
- handleWindowVisibility((IBinder)msg.obj, true);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case HIDE_WINDOW:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityHideWindow");
- handleWindowVisibility((IBinder)msg.obj, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case RESUME_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
- SomeArgs args = (SomeArgs) msg.obj;
- handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
- args.argi3, "RESUME_ACTIVITY");
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case SEND_RESULT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
- handleSendResult((ResultData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case DESTROY_ACTIVITY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
- handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
- msg.arg2, false);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
@@ -1672,11 +1433,6 @@
}
Looper.myLooper().quit();
break;
- case NEW_INTENT:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
- handleNewIntent((NewIntentData)msg.obj);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
@@ -1708,15 +1464,7 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
- mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi;
- mUpdatingSystemConfig = true;
- try {
- handleConfigurationChanged((Configuration) msg.obj, null);
- } finally {
- mUpdatingSystemConfig = false;
- }
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ handleConfigurationChanged((Configuration) msg.obj);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
@@ -1733,18 +1481,6 @@
handleLowMemory();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
- case ACTIVITY_CONFIGURATION_CHANGED:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- INVALID_DISPLAY);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
- case ACTIVITY_MOVED_TO_DISPLAY:
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
- handleActivityConfigurationChanged((ActivityConfigChangeData) msg.obj,
- msg.arg1 /* displayId */);
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
- break;
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
break;
@@ -1828,16 +1564,6 @@
case STOP_BINDER_TRACKING_AND_DUMP:
handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
break;
- case MULTI_WINDOW_MODE_CHANGED:
- handleMultiWindowModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
- case PICTURE_IN_PICTURE_MODE_CHANGED:
- handlePictureInPictureModeChanged((IBinder) ((SomeArgs) msg.obj).arg1,
- ((SomeArgs) msg.obj).argi1 == 1,
- (Configuration) ((SomeArgs) msg.obj).arg2);
- break;
case LOCAL_VOICE_INTERACTION_STARTED:
handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
(IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
@@ -1857,6 +1583,9 @@
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
+ case EXECUTE_TRANSACTION:
+ ((ClientTransaction) msg.obj).execute(ActivityThread.this);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -2601,10 +2330,16 @@
+ " req=" + requestCode + " res=" + resultCode + " data=" + data);
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(id, requestCode, resultCode, data));
- mAppThread.scheduleSendResult(token, list);
+ final ClientTransaction clientTransaction = new ClientTransaction(mAppThread, token);
+ clientTransaction.addCallback(new ActivityResultItem(list));
+ try {
+ mAppThread.scheduleTransaction(clientTransaction);
+ } catch (RemoteException e) {
+ // Local scheduling
+ }
}
- private void sendMessage(int what, Object obj) {
+ void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
@@ -2844,6 +2579,37 @@
return appContext;
}
+ @Override
+ public void handleLaunchActivity(IBinder token, Intent intent, int ident, ActivityInfo info,
+ Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer,
+ IVoiceInteractor voiceInteractor, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
+ ActivityClientRecord r = new ActivityClientRecord();
+
+ r.token = token;
+ r.ident = ident;
+ r.intent = intent;
+ r.referrer = referrer;
+ r.voiceInteractor = voiceInteractor;
+ r.activityInfo = info;
+ r.compatInfo = compatInfo;
+ r.state = state;
+ r.persistentState = persistentState;
+
+ r.pendingResults = pendingResults;
+ r.pendingIntents = pendingNewIntents;
+
+ r.startsNotResumed = notResumed;
+ r.isForward = isForward;
+
+ r.profilerInfo = profilerInfo;
+
+ r.overrideConfig = overrideConfig;
+ r.packageInfo = getPackageInfoNoCheck(r.activityInfo.applicationInfo, r.compatInfo);
+ handleLaunchActivity(r, null /* customIntent */, "LAUNCH_ACTIVITY");
+ }
+
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
@@ -2974,8 +2740,9 @@
}
}
- private void handleNewIntent(NewIntentData data) {
- performNewIntents(data.token, data.intents, data.andPause);
+ @Override
+ public void handleNewIntent(IBinder token, List<ReferrerIntent> intents, boolean andPause) {
+ performNewIntents(token, intents, andPause);
}
public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
@@ -3096,7 +2863,8 @@
}
}
- private void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ @Override
+ public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3108,7 +2876,8 @@
}
}
- private void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ @Override
+ public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
Configuration overrideConfig) {
final ActivityClientRecord r = mActivities.get(token);
if (r != null) {
@@ -3619,8 +3388,9 @@
r.mPendingRemoveWindowManager = null;
}
- final void handleResumeActivity(IBinder token,
- boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
+ @Override
+ public void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
@@ -3823,7 +3593,8 @@
return thumbnail;
}
- private void handlePauseActivity(IBinder token, boolean finished,
+ @Override
+ public void handlePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
@@ -4087,7 +3858,8 @@
}
}
- private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
+ @Override
+ public void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "stopActivity")) {
return;
@@ -4142,7 +3914,8 @@
}
}
- private void handleWindowVisibility(IBinder token, boolean show) {
+ @Override
+ public void handleWindowVisibility(IBinder token, boolean show) {
ActivityClientRecord r = mActivities.get(token);
if (r == null) {
@@ -4288,8 +4061,9 @@
}
}
- private void handleSendResult(ResultData res) {
- ActivityClientRecord r = mActivities.get(res.token);
+ @Override
+ public void handleSendResult(IBinder token, List<ResultInfo> results) {
+ ActivityClientRecord r = mActivities.get(token);
if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
if (r != null) {
final boolean resumed = !r.paused;
@@ -4323,7 +4097,7 @@
}
}
checkAndBlockForNetworkAccess();
- deliverResults(r, res.results);
+ deliverResults(r, results);
if (resumed) {
r.activity.performResume();
r.activity.mTemporaryPause = false;
@@ -4410,8 +4184,9 @@
return component == null ? "[Unknown]" : component.toShortString();
}
- private void handleDestroyActivity(IBinder token, boolean finishing,
- int configChanges, boolean getNonConfigInstance) {
+ @Override
+ public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
configChanges, getNonConfigInstance);
if (r != null) {
@@ -4982,7 +4757,20 @@
return config;
}
- final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
+ @Override
+ public void handleConfigurationChanged(Configuration config) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+ mCurDefaultDisplayDpi = config.densityDpi;
+ mUpdatingSystemConfig = true;
+ try {
+ handleConfigurationChanged(config, null /* compat */);
+ } finally {
+ mUpdatingSystemConfig = false;
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
int configDiff = 0;
@@ -5113,12 +4901,15 @@
/**
* Handle new activity configuration and/or move to a different display.
- * @param data Configuration update data.
+ * @param activityToken Target activity token.
+ * @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
*/
- void handleActivityConfigurationChanged(ActivityConfigChangeData data, int displayId) {
- ActivityClientRecord r = mActivities.get(data.activityToken);
+ @Override
+ public void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId) {
+ ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
@@ -5128,14 +4919,14 @@
&& displayId != r.activity.getDisplay().getDisplayId();
// Perform updates.
- r.overrideConfig = data.overrideConfig;
+ r.overrideConfig = overrideConfig;
final ViewRootImpl viewRoot = r.activity.mDecor != null
? r.activity.mDecor.getViewRootImpl() : null;
if (movedToDifferentDisplay) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+ r.activityInfo.name + ", displayId=" + displayId
- + ", config=" + data.overrideConfig);
+ + ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
@@ -5144,7 +4935,7 @@
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
- + r.activityInfo.name + ", config=" + data.overrideConfig);
+ + r.activityInfo.name + ", config=" + overrideConfig);
performConfigurationChangedForActivity(r, mCompatConfiguration);
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 7a4c00f..005b7c3 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2462,7 +2462,8 @@
if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
Bitmap bitmap = getUserManager().getUserIcon(itemInfo.showUserIcon);
if (bitmap == null) {
- return UserIcons.getDefaultUserIcon(itemInfo.showUserIcon, /* light= */ false);
+ return UserIcons.getDefaultUserIcon(
+ mContext.getResources(), itemInfo.showUserIcon, /* light= */ false);
}
return new BitmapDrawable(bitmap);
}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
new file mode 100644
index 0000000..f7f4c71
--- /dev/null
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app;
+
+import android.app.servertransaction.ClientTransaction;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.PersistableBundle;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
+ * can perform on client.
+ * @hide
+ */
+public abstract class ClientTransactionHandler {
+
+ // Schedule phase related logic and handlers.
+
+ /** Prepare and schedule transaction for execution. */
+ void scheduleTransaction(ClientTransaction transaction) {
+ transaction.prepare(this);
+ sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
+ }
+
+ abstract void sendMessage(int what, Object obj);
+
+
+ // Prepare phase related logic and handlers. Methods that inform about about pending changes or
+ // do other internal bookkeeping.
+
+ /** Get current lifecycle request number to maintain correct ordering. */
+ public abstract int getLifecycleSeq();
+
+ /** Set pending config in case it will be updated by other transaction item. */
+ public abstract void updatePendingConfiguration(Configuration config);
+
+ /** Set current process state. */
+ public abstract void updateProcessState(int processState, boolean fromIpc);
+
+
+ // Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
+ // and deliver callbacks.
+
+ /** Destroy the activity. */
+ public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+ boolean getNonConfigInstance);
+
+ /** Pause the activity. */
+ public abstract void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+ int configChanges, boolean dontReport, int seq);
+
+ /** Resume the activity. */
+ public abstract void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
+ boolean reallyResume, int seq, String reason);
+
+ /** Stop the activity. */
+ public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+ int seq);
+
+ /** Deliver activity (override) configuration change. */
+ public abstract void handleActivityConfigurationChanged(IBinder activityToken,
+ Configuration overrideConfig, int displayId);
+
+ /** Deliver result from another activity. */
+ public abstract void handleSendResult(IBinder token, List<ResultInfo> results);
+
+ /** Deliver multi-window mode change notification. */
+ public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+ Configuration overrideConfig);
+
+ /** Deliver new intent. */
+ public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents,
+ boolean andPause);
+
+ /** Deliver picture-in-picture mode change notification. */
+ public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+ Configuration overrideConfig);
+
+ /** Update window visibility. */
+ public abstract void handleWindowVisibility(IBinder token, boolean show);
+
+ /** Perform activity launch. */
+ public abstract void handleLaunchActivity(IBinder token, Intent intent, int ident,
+ ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo);
+
+ /** Deliver app configuration change notification. */
+ public abstract void handleConfigurationChanged(Configuration config);
+}
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 487a94a..b25d778 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@
import android.app.IUiAutomationConnection;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
+import android.app.servertransaction.ClientTransaction;
import android.content.ComponentName;
import android.content.IIntentReceiver;
import android.content.Intent;
@@ -52,24 +53,6 @@
* {@hide}
*/
oneway interface IApplicationThread {
- void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving,
- int configChanges, boolean dontReport);
- void scheduleStopActivity(IBinder token, boolean showWindow,
- int configChanges);
- void scheduleWindowVisibility(IBinder token, boolean showWindow);
- void scheduleResumeActivity(IBinder token, int procState, boolean isForward,
- in Bundle resumeArgs);
- void scheduleSendResult(IBinder token, in List<ResultInfo> results);
- void scheduleLaunchActivity(in Intent intent, IBinder token, int ident,
- in ActivityInfo info, in Configuration curConfig, in Configuration overrideConfig,
- in CompatibilityInfo compatInfo, in String referrer, IVoiceInteractor voiceInteractor,
- int procState, in Bundle state, in PersistableBundle persistentState,
- in List<ResultInfo> pendingResults, in List<ReferrerIntent> pendingNewIntents,
- boolean notResumed, boolean isForward, in ProfilerInfo profilerInfo);
- void scheduleNewIntent(
- in List<ReferrerIntent> intent, IBinder token, boolean andPause);
- void scheduleDestroyActivity(IBinder token, boolean finished,
- int configChanges);
void scheduleReceiver(in Intent intent, in ActivityInfo info,
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean sync,
@@ -87,7 +70,6 @@
in Bundle coreSettings, in String buildSerial);
void runIsolatedEntryPoint(in String entryPoint, in String[] entryPointArgs);
void scheduleExit();
- void scheduleConfigurationChanged(in Configuration config);
void scheduleServiceArgs(IBinder token, in ParceledListSlice args);
void updateTimeZone();
void processInBackground();
@@ -101,9 +83,6 @@
int resultCode, in String data, in Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState);
void scheduleLowMemory();
- void scheduleActivityConfigurationChanged(IBinder token, in Configuration overrideConfig);
- void scheduleActivityMovedToDisplay(IBinder token, int displayId,
- in Configuration overrideConfig);
void scheduleRelaunchActivity(IBinder token, in List<ResultInfo> pendingResults,
in List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
in Configuration config, in Configuration overrideConfig, boolean preserveWindow);
@@ -146,14 +125,11 @@
void notifyCleartextNetwork(in byte[] firstPacket);
void startBinderTracking();
void stopBinderTrackingAndDump(in ParcelFileDescriptor fd);
- void scheduleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
- in Configuration newConfig);
- void schedulePictureInPictureModeChanged(IBinder token, boolean isInPictureInPictureMode,
- in Configuration newConfig);
void scheduleLocalVoiceInteractionStarted(IBinder token,
IVoiceInteractor voiceInteractor);
void handleTrustStorageUpdate();
void attachAgent(String path);
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
void setNetworkBlockSeq(long procStateSeq);
+ void scheduleTransaction(in ClientTransaction transaction);
}
diff --git a/core/java/android/app/ProfilerInfo.java b/core/java/android/app/ProfilerInfo.java
index fad4798..d523427 100644
--- a/core/java/android/app/ProfilerInfo.java
+++ b/core/java/android/app/ProfilerInfo.java
@@ -22,6 +22,7 @@
import android.util.Slog;
import java.io.IOException;
+import java.util.Objects;
/**
* System private API for passing profiler settings.
@@ -132,4 +133,32 @@
streamingOutput = in.readInt() != 0;
agent = in.readString();
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ProfilerInfo other = (ProfilerInfo) o;
+ // TODO: Also check #profileFd for equality.
+ return Objects.equals(profileFile, other.profileFile)
+ && autoStopProfiler == other.autoStopProfiler
+ && samplingInterval == other.samplingInterval
+ && streamingOutput == other.streamingOutput
+ && Objects.equals(agent, other.agent);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(profileFile);
+ result = 31 * result + samplingInterval;
+ result = 31 * result + (autoStopProfiler ? 1 : 0);
+ result = 31 * result + (streamingOutput ? 1 : 0);
+ result = 31 * result + Objects.hashCode(agent);
+ return result;
+ }
}
diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java
index 5e0867c..d5af08a 100644
--- a/core/java/android/app/ResultInfo.java
+++ b/core/java/android/app/ResultInfo.java
@@ -20,6 +20,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.Objects;
+
/**
* {@hide}
*/
@@ -79,4 +81,29 @@
mData = null;
}
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ResultInfo)) {
+ return false;
+ }
+ final ResultInfo other = (ResultInfo) obj;
+ final boolean intentsEqual = mData == null ? (other.mData == null)
+ : mData.filterEquals(other.mData);
+ return intentsEqual && Objects.equals(mResultWho, other.mResultWho)
+ && mResultCode == other.mResultCode
+ && mRequestCode == other.mRequestCode;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mRequestCode;
+ result = 31 * result + mResultCode;
+ result = 31 * result + Objects.hashCode(mResultWho);
+ if (mData != null) {
+ result = 31 * result + mData.filterHashCode();
+ }
+ return result;
+ }
}
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 6ea0825..8c47598 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -34,8 +34,6 @@
import libcore.io.IoUtils;
-import com.google.android.collect.Maps;
-
import org.xmlpull.v1.XmlPullParserException;
import java.io.BufferedInputStream;
@@ -139,7 +137,7 @@
Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
}
- Map map = null;
+ Map<String, Object> map = null;
StructStat stat = null;
try {
stat = Os.stat(mFile.getPath());
@@ -148,7 +146,7 @@
try {
str = new BufferedInputStream(
new FileInputStream(mFile), 16*1024);
- map = XmlUtils.readMapXml(str);
+ map = (Map<String, Object>) XmlUtils.readMapXml(str);
} catch (Exception e) {
Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
} finally {
@@ -214,12 +212,14 @@
}
}
+ @Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.put(listener, CONTENT);
}
}
+ @Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(mLock) {
mListeners.remove(listener);
@@ -241,6 +241,7 @@
}
}
+ @Override
public Map<String, ?> getAll() {
synchronized (mLock) {
awaitLoadedLocked();
@@ -249,6 +250,7 @@
}
}
+ @Override
@Nullable
public String getString(String key, @Nullable String defValue) {
synchronized (mLock) {
@@ -258,6 +260,7 @@
}
}
+ @Override
@Nullable
public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (mLock) {
@@ -267,6 +270,7 @@
}
}
+ @Override
public int getInt(String key, int defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -274,6 +278,7 @@
return v != null ? v : defValue;
}
}
+ @Override
public long getLong(String key, long defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -281,6 +286,7 @@
return v != null ? v : defValue;
}
}
+ @Override
public float getFloat(String key, float defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -288,6 +294,7 @@
return v != null ? v : defValue;
}
}
+ @Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -296,6 +303,7 @@
}
}
+ @Override
public boolean contains(String key) {
synchronized (mLock) {
awaitLoadedLocked();
@@ -303,6 +311,7 @@
}
}
+ @Override
public Editor edit() {
// TODO: remove the need to call awaitLoadedLocked() when
// requesting an editor. will require some work on the
@@ -347,71 +356,81 @@
}
public final class EditorImpl implements Editor {
- private final Object mLock = new Object();
+ private final Object mEditorLock = new Object();
- @GuardedBy("mLock")
- private final Map<String, Object> mModified = Maps.newHashMap();
+ @GuardedBy("mEditorLock")
+ private final Map<String, Object> mModified = new HashMap<>();
- @GuardedBy("mLock")
+ @GuardedBy("mEditorLock")
private boolean mClear = false;
+ @Override
public Editor putString(String key, @Nullable String value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putStringSet(String key, @Nullable Set<String> values) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
return this;
}
}
+ @Override
public Editor putInt(String key, int value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putLong(String key, long value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putFloat(String key, float value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor putBoolean(String key, boolean value) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, value);
return this;
}
}
+ @Override
public Editor remove(String key) {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mModified.put(key, this);
return this;
}
}
+ @Override
public Editor clear() {
- synchronized (mLock) {
+ synchronized (mEditorLock) {
mClear = true;
return this;
}
}
+ @Override
public void apply() {
final long startTime = System.currentTimeMillis();
final MemoryCommitResult mcr = commitToMemory();
final Runnable awaitCommit = new Runnable() {
+ @Override
public void run() {
try {
mcr.writtenToDiskLatch.await();
@@ -429,6 +448,7 @@
QueuedWork.addFinisher(awaitCommit);
Runnable postWriteRunnable = new Runnable() {
+ @Override
public void run() {
awaitCommit.run();
QueuedWork.removeFinisher(awaitCommit);
@@ -471,13 +491,13 @@
listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
}
- synchronized (mLock) {
+ synchronized (mEditorLock) {
boolean changesMade = false;
if (mClear) {
- if (!mMap.isEmpty()) {
+ if (!mapToWriteToDisk.isEmpty()) {
changesMade = true;
- mMap.clear();
+ mapToWriteToDisk.clear();
}
mClear = false;
}
@@ -489,18 +509,18 @@
// setting a value to "null" for a given key is specified to be
// equivalent to calling remove on that key.
if (v == this || v == null) {
- if (!mMap.containsKey(k)) {
+ if (!mapToWriteToDisk.containsKey(k)) {
continue;
}
- mMap.remove(k);
+ mapToWriteToDisk.remove(k);
} else {
- if (mMap.containsKey(k)) {
- Object existingValue = mMap.get(k);
+ if (mapToWriteToDisk.containsKey(k)) {
+ Object existingValue = mapToWriteToDisk.get(k);
if (existingValue != null && existingValue.equals(v)) {
continue;
}
}
- mMap.put(k, v);
+ mapToWriteToDisk.put(k, v);
}
changesMade = true;
@@ -522,6 +542,7 @@
mapToWriteToDisk);
}
+ @Override
public boolean commit() {
long startTime = 0;
@@ -564,11 +585,7 @@
}
} else {
// Run this function on the main thread.
- ActivityThread.sMainThreadHandler.post(new Runnable() {
- public void run() {
- notifyListeners(mcr);
- }
- });
+ ActivityThread.sMainThreadHandler.post(() -> notifyListeners(mcr));
}
}
}
@@ -594,6 +611,7 @@
final boolean isFromSyncCommit = (postWriteRunnable == null);
final Runnable writeToDiskRunnable = new Runnable() {
+ @Override
public void run() {
synchronized (mWritingToDiskLock) {
writeToFile(mcr, isFromSyncCommit);
@@ -646,7 +664,7 @@
return str;
}
- // Note: must hold mWritingToDiskLock
+ @GuardedBy("mWritingToDiskLock")
private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
long startTime = 0;
long existsTime = 0;
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
new file mode 100644
index 0000000..07001e2
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity configuration changed callback.
+ * @hide
+ */
+public class ActivityConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ActivityConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ // TODO(lifecycler): detect if PIP or multi-window mode changed and report it here.
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
+ client.handleActivityConfigurationChanged(token, mConfiguration, INVALID_DISPLAY);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ActivityConfigurationChangeItem> CREATOR =
+ new Creator<ActivityConfigurationChangeItem>() {
+ public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ActivityConfigurationChangeItem(in);
+ }
+
+ public ActivityConfigurationChangeItem[] newArray(int size) {
+ return new ActivityConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
new file mode 100644
index 0000000..a64108d
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Request for lifecycle state that an activity should reach.
+ * @hide
+ */
+public abstract class ActivityLifecycleItem extends ClientTransactionItem {
+
+ static final boolean DEBUG_ORDER = false;
+
+ @IntDef({UNDEFINED, RESUMED, PAUSED, STOPPED, DESTROYED})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LifecycleState{}
+ public static final int UNDEFINED = -1;
+ public static final int RESUMED = 0;
+ public static final int PAUSED = 1;
+ public static final int STOPPED = 2;
+ public static final int DESTROYED = 3;
+
+ /** A final lifecycle state that an activity should reach. */
+ @LifecycleState
+ public abstract int getTargetState();
+}
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
new file mode 100644
index 0000000..76664d8
--- /dev/null
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ResultInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import java.util.List;
+
+/**
+ * Activity result delivery callback.
+ * @hide
+ */
+public class ActivityResultItem extends ClientTransactionItem {
+
+ private final List<ResultInfo> mResultInfoList;
+
+ public ActivityResultItem(List<ResultInfo> resultInfos) {
+ mResultInfoList = resultInfos;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
+ client.handleSendResult(token, mResultInfoList);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedList(mResultInfoList, flags);
+ }
+
+ /** Read from Parcel. */
+ private ActivityResultItem(Parcel in) {
+ mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
+ }
+
+ public static final Parcelable.Creator<ActivityResultItem> CREATOR =
+ new Parcelable.Creator<ActivityResultItem>() {
+ public ActivityResultItem createFromParcel(Parcel in) {
+ return new ActivityResultItem(in);
+ }
+
+ public ActivityResultItem[] newArray(int size) {
+ return new ActivityResultItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ActivityResultItem other = (ActivityResultItem) o;
+ return mResultInfoList.equals(other.mResultInfoList);
+ }
+
+ @Override
+ public int hashCode() {
+ return mResultInfoList.hashCode();
+ }
+}
diff --git a/core/java/android/app/servertransaction/BaseClientRequest.java b/core/java/android/app/servertransaction/BaseClientRequest.java
new file mode 100644
index 0000000..4bd01af
--- /dev/null
+++ b/core/java/android/app/servertransaction/BaseClientRequest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+
+/**
+ * Base interface for individual requests from server to client.
+ * Each of them can be prepared before scheduling and, eventually, executed.
+ * @hide
+ */
+public interface BaseClientRequest {
+
+ /**
+ * Prepare the client request before scheduling.
+ * An example of this might be informing about pending updates for some values.
+ *
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ default void prepare(ClientTransactionHandler client, IBinder token) {
+ }
+
+ /**
+ * Execute the request.
+ * @param client Target client handler.
+ * @param token Target activity token.
+ */
+ void execute(ClientTransactionHandler client, IBinder token);
+}
diff --git a/core/java/android/app/servertransaction/ClientTransaction.aidl b/core/java/android/app/servertransaction/ClientTransaction.aidl
new file mode 100644
index 0000000..ad8bcbf
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransaction.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+/** @hide */
+parcelable ClientTransaction;
\ No newline at end of file
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
new file mode 100644
index 0000000..d2289ba
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.app.ClientTransactionHandler;
+import android.app.IApplicationThread;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A container that holds a sequence of messages, which may be sent to a client.
+ * This includes a list of callbacks and a final lifecycle state.
+ *
+ * @see com.android.server.am.ClientLifecycleManager
+ * @see ClientTransactionItem
+ * @see ActivityLifecycleItem
+ * @hide
+ */
+public class ClientTransaction implements Parcelable {
+
+ /** A list of individual callbacks to a client. */
+ private List<ClientTransactionItem> mActivityCallbacks;
+
+ /**
+ * Final lifecycle state in which the client activity should be after the transaction is
+ * executed.
+ */
+ private ActivityLifecycleItem mLifecycleStateRequest;
+
+ /** Target client. */
+ private IApplicationThread mClient;
+
+ /** Target client activity. Might be null if the entire transaction is targeting an app. */
+ private IBinder mActivityToken;
+
+ public ClientTransaction(IApplicationThread client, IBinder activityToken) {
+ mClient = client;
+ mActivityToken = activityToken;
+ }
+
+ /**
+ * Add a message to the end of the sequence of callbacks.
+ * @param activityCallback A single message that can contain a lifecycle request/callback.
+ */
+ public void addCallback(ClientTransactionItem activityCallback) {
+ if (mActivityCallbacks == null) {
+ mActivityCallbacks = new ArrayList<>();
+ }
+ mActivityCallbacks.add(activityCallback);
+ }
+
+ /**
+ * Set the lifecycle state in which the client should be after executing the transaction.
+ * @param stateRequest A lifecycle request initialized with right parameters.
+ */
+ public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) {
+ mLifecycleStateRequest = stateRequest;
+ }
+
+ /**
+ * Do what needs to be done while the transaction is being scheduled on the client side.
+ * @param clientTransactionHandler Handler on the client side that will executed all operations
+ * requested by transaction items.
+ */
+ public void prepare(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.prepare(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Execute the transaction.
+ * @param clientTransactionHandler Handler on the client side that will execute all operations
+ * requested by transaction items.
+ */
+ public void execute(android.app.ClientTransactionHandler clientTransactionHandler) {
+ if (mActivityCallbacks != null) {
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; ++i) {
+ mActivityCallbacks.get(i).execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ mLifecycleStateRequest.execute(clientTransactionHandler, mActivityToken);
+ }
+ }
+
+ /**
+ * Schedule the transaction after it was initialized. It will be send to client and all its
+ * individual parts will be applied in the following sequence:
+ * 1. The client calls {@link #prepare(ClientTransactionHandler)}, which triggers all work that
+ * needs to be done before actually scheduling the transaction for callbacks and lifecycle
+ * state request.
+ * 2. The transaction message is scheduled.
+ * 3. The client calls {@link #execute(ClientTransactionHandler)}, which executes all callbacks
+ * and necessary lifecycle transitions.
+ */
+ public void schedule() throws RemoteException {
+ mClient.scheduleTransaction(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStrongBinder(mClient.asBinder());
+ final boolean writeActivityToken = mActivityToken != null;
+ dest.writeBoolean(writeActivityToken);
+ if (writeActivityToken) {
+ dest.writeStrongBinder(mActivityToken);
+ }
+ dest.writeParcelable(mLifecycleStateRequest, flags);
+ final boolean writeActivityCallbacks = mActivityCallbacks != null;
+ dest.writeBoolean(writeActivityCallbacks);
+ if (writeActivityCallbacks) {
+ dest.writeParcelableList(mActivityCallbacks, flags);
+ }
+ }
+
+ /** Read from Parcel. */
+ private ClientTransaction(Parcel in) {
+ mClient = (IApplicationThread) in.readStrongBinder();
+ final boolean readActivityToken = in.readBoolean();
+ if (readActivityToken) {
+ mActivityToken = in.readStrongBinder();
+ }
+ mLifecycleStateRequest = in.readParcelable(getClass().getClassLoader());
+ final boolean readActivityCallbacks = in.readBoolean();
+ if (readActivityCallbacks) {
+ mActivityCallbacks = new ArrayList<>();
+ in.readParcelableList(mActivityCallbacks, getClass().getClassLoader());
+ }
+ }
+
+ public static final Creator<ClientTransaction> CREATOR =
+ new Creator<ClientTransaction>() {
+ public ClientTransaction createFromParcel(Parcel in) {
+ return new ClientTransaction(in);
+ }
+
+ public ClientTransaction[] newArray(int size) {
+ return new ClientTransaction[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ClientTransaction other = (ClientTransaction) o;
+ return Objects.equals(mActivityCallbacks, other.mActivityCallbacks)
+ && Objects.equals(mLifecycleStateRequest, other.mLifecycleStateRequest)
+ && mClient == other.mClient
+ && mActivityToken == other.mActivityToken;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + Objects.hashCode(mActivityCallbacks);
+ result = 31 * result + Objects.hashCode(mLifecycleStateRequest);
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
new file mode 100644
index 0000000..6f2cc00
--- /dev/null
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
+
+import android.os.Parcelable;
+
+/**
+ * A callback message to a client that can be scheduled and executed.
+ * Examples of these might be activity configuration change, multi-window mode change, activity
+ * result delivery etc.
+ *
+ * @see ClientTransaction
+ * @see com.android.server.am.ClientLifecycleManager
+ * @hide
+ */
+public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
+
+ /** Get the state in which this callback can be executed. */
+ @LifecycleState
+ public int getPreExecutionState() {
+ return UNDEFINED;
+ }
+
+ /** Get the state that must follow this callback. */
+ @LifecycleState
+ public int getPostExecutionState() {
+ return UNDEFINED;
+ }
+
+
+ // Parcelable
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
new file mode 100644
index 0000000..055923e
--- /dev/null
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * App configuration change message.
+ * @hide
+ */
+public class ConfigurationChangeItem extends ClientTransactionItem {
+
+ private final Configuration mConfiguration;
+
+ public ConfigurationChangeItem(Configuration configuration) {
+ mConfiguration = new Configuration(configuration);
+ }
+
+ @Override
+ public void prepare(android.app.ClientTransactionHandler client, IBinder token) {
+ client.updatePendingConfiguration(mConfiguration);
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleConfigurationChanged(mConfiguration);
+ }
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private ConfigurationChangeItem(Parcel in) {
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<ConfigurationChangeItem> CREATOR =
+ new Creator<ConfigurationChangeItem>() {
+ public ConfigurationChangeItem createFromParcel(Parcel in) {
+ return new ConfigurationChangeItem(in);
+ }
+
+ public ConfigurationChangeItem[] newArray(int size) {
+ return new ConfigurationChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
+ return mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ return mConfiguration.hashCode();
+ }
+}
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
new file mode 100644
index 0000000..38fd5fb
--- /dev/null
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to destroy an activity.
+ * @hide
+ */
+public class DestroyActivityItem extends ActivityLifecycleItem {
+
+ private final boolean mFinished;
+ private final int mConfigChanges;
+
+ public DestroyActivityItem(boolean finished, int configChanges) {
+ mFinished = finished;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
+ client.handleDestroyActivity(token, mFinished, mConfigChanges,
+ false /* getNonConfigInstance */);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return DESTROYED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private DestroyActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<DestroyActivityItem> CREATOR =
+ new Creator<DestroyActivityItem>() {
+ public DestroyActivityItem createFromParcel(Parcel in) {
+ return new DestroyActivityItem(in);
+ }
+
+ public DestroyActivityItem[] newArray(int size) {
+ return new DestroyActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final DestroyActivityItem other = (DestroyActivityItem) o;
+ return mFinished == other.mFinished && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
new file mode 100644
index 0000000..417ebac
--- /dev/null
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.os.Trace;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request to launch an activity.
+ * @hide
+ */
+public class LaunchActivityItem extends ActivityLifecycleItem {
+
+ private final Intent mIntent;
+ private final int mIdent;
+ private final ActivityInfo mInfo;
+ private final Configuration mCurConfig;
+ private final Configuration mOverrideConfig;
+ private final CompatibilityInfo mCompatInfo;
+ private final String mReferrer;
+ private final IVoiceInteractor mVoiceInteractor;
+ private final int mProcState;
+ private final Bundle mState;
+ private final PersistableBundle mPersistentState;
+ private final List<ResultInfo> mPendingResults;
+ private final List<ReferrerIntent> mPendingNewIntents;
+ // TODO(lifecycler): use lifecycle request instead of this param.
+ private final boolean mNotResumed;
+ private final boolean mIsForward;
+ private final ProfilerInfo mProfilerInfo;
+
+ public LaunchActivityItem(Intent intent, int ident, ActivityInfo info,
+ Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
+ PersistableBundle persistentState, List<ResultInfo> pendingResults,
+ List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
+ ProfilerInfo profilerInfo) {
+ mIntent = intent;
+ mIdent = ident;
+ mInfo = info;
+ mCurConfig = curConfig;
+ mOverrideConfig = overrideConfig;
+ mCompatInfo = compatInfo;
+ mReferrer = referrer;
+ mVoiceInteractor = voiceInteractor;
+ mProcState = procState;
+ mState = state;
+ mPersistentState = persistentState;
+ mPendingResults = pendingResults;
+ mPendingNewIntents = pendingNewIntents;
+ mNotResumed = notResumed;
+ mIsForward = isForward;
+ mProfilerInfo = profilerInfo;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ client.updateProcessState(mProcState, false);
+ client.updatePendingConfiguration(mCurConfig);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
+ client.handleLaunchActivity(token, mIntent, mIdent, mInfo, mOverrideConfig, mCompatInfo,
+ mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults,
+ mPendingNewIntents, mNotResumed, mIsForward, mProfilerInfo);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return mNotResumed ? PAUSED : RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write from Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeTypedObject(mIntent, flags);
+ dest.writeInt(mIdent);
+ dest.writeTypedObject(mInfo, flags);
+ dest.writeTypedObject(mCurConfig, flags);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ dest.writeTypedObject(mCompatInfo, flags);
+ dest.writeString(mReferrer);
+ dest.writeStrongBinder(mVoiceInteractor != null ? mVoiceInteractor.asBinder() : null);
+ dest.writeInt(mProcState);
+ dest.writeBundle(mState);
+ dest.writePersistableBundle(mPersistentState);
+ dest.writeTypedList(mPendingResults, flags);
+ dest.writeTypedList(mPendingNewIntents, flags);
+ dest.writeBoolean(mNotResumed);
+ dest.writeBoolean(mIsForward);
+ dest.writeTypedObject(mProfilerInfo, flags);
+ }
+
+ /** Read from Parcel. */
+ private LaunchActivityItem(Parcel in) {
+ mIntent = in.readTypedObject(Intent.CREATOR);
+ mIdent = in.readInt();
+ mInfo = in.readTypedObject(ActivityInfo.CREATOR);
+ mCurConfig = in.readTypedObject(Configuration.CREATOR);
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ mCompatInfo = in.readTypedObject(CompatibilityInfo.CREATOR);
+ mReferrer = in.readString();
+ mVoiceInteractor = (IVoiceInteractor) in.readStrongBinder();
+ mProcState = in.readInt();
+ mState = in.readBundle(getClass().getClassLoader());
+ mPersistentState = in.readPersistableBundle(getClass().getClassLoader());
+ mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
+ mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ mNotResumed = in.readBoolean();
+ mIsForward = in.readBoolean();
+ mProfilerInfo = in.readTypedObject(ProfilerInfo.CREATOR);
+ }
+
+ public static final Creator<LaunchActivityItem> CREATOR =
+ new Creator<LaunchActivityItem>() {
+ public LaunchActivityItem createFromParcel(Parcel in) {
+ return new LaunchActivityItem(in);
+ }
+
+ public LaunchActivityItem[] newArray(int size) {
+ return new LaunchActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final LaunchActivityItem other = (LaunchActivityItem) o;
+ return mIntent.filterEquals(other.mIntent) && mIdent == other.mIdent
+ && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
+ && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+ && Objects.equals(mCompatInfo, other.mCompatInfo)
+ && Objects.equals(mReferrer, other.mReferrer)
+ && mProcState == other.mProcState && areBundlesEqual(mState, other.mState)
+ && areBundlesEqual(mPersistentState, other.mPersistentState)
+ && Objects.equals(mPendingResults, other.mPendingResults)
+ && Objects.equals(mPendingNewIntents, other.mPendingNewIntents)
+ && mNotResumed == other.mNotResumed && mIsForward == other.mIsForward
+ && Objects.equals(mProfilerInfo, other.mProfilerInfo);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mIntent.filterHashCode();
+ result = 31 * result + mIdent;
+ result = 31 * result + Objects.hashCode(mCurConfig);
+ result = 31 * result + Objects.hashCode(mOverrideConfig);
+ result = 31 * result + Objects.hashCode(mCompatInfo);
+ result = 31 * result + Objects.hashCode(mReferrer);
+ result = 31 * result + Objects.hashCode(mProcState);
+ result = 31 * result + (mState != null ? mState.size() : 0);
+ result = 31 * result + (mPersistentState != null ? mPersistentState.size() : 0);
+ result = 31 * result + Objects.hashCode(mPendingResults);
+ result = 31 * result + Objects.hashCode(mPendingNewIntents);
+ result = 31 * result + (mNotResumed ? 1 : 0);
+ result = 31 * result + (mIsForward ? 1 : 0);
+ result = 31 * result + Objects.hashCode(mProfilerInfo);
+ return result;
+ }
+
+ private boolean activityInfoEqual(ActivityInfo other) {
+ return mInfo.flags == other.flags && mInfo.maxAspectRatio == other.maxAspectRatio
+ && Objects.equals(mInfo.launchToken, other.launchToken)
+ && Objects.equals(mInfo.getComponentName(), other.getComponentName());
+ }
+
+ private static boolean areBundlesEqual(BaseBundle extras, BaseBundle newExtras) {
+ if (extras == null || newExtras == null) {
+ return extras == newExtras;
+ }
+
+ if (extras.size() != newExtras.size()) {
+ return false;
+ }
+
+ for (String key : extras.keySet()) {
+ if (key != null) {
+ final Object value = extras.get(key);
+ final Object newValue = newExtras.get(key);
+ if (!Objects.equals(value, newValue)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
new file mode 100644
index 0000000..ccd80d8
--- /dev/null
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Activity move to a different display message.
+ * @hide
+ */
+public class MoveToDisplayItem extends ClientTransactionItem {
+
+ private final int mTargetDisplayId;
+ private final Configuration mConfiguration;
+
+ public MoveToDisplayItem(int targetDisplayId, Configuration configuration) {
+ mTargetDisplayId = targetDisplayId;
+ mConfiguration = configuration;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
+ client.handleActivityConfigurationChanged(token, mConfiguration, mTargetDisplayId);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mTargetDisplayId);
+ dest.writeTypedObject(mConfiguration, flags);
+ }
+
+ /** Read from Parcel. */
+ private MoveToDisplayItem(Parcel in) {
+ mTargetDisplayId = in.readInt();
+ mConfiguration = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MoveToDisplayItem> CREATOR = new Creator<MoveToDisplayItem>() {
+ public MoveToDisplayItem createFromParcel(Parcel in) {
+ return new MoveToDisplayItem(in);
+ }
+
+ public MoveToDisplayItem[] newArray(int size) {
+ return new MoveToDisplayItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MoveToDisplayItem other = (MoveToDisplayItem) o;
+ return mTargetDisplayId == other.mTargetDisplayId
+ && mConfiguration.equals(other.mConfiguration);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mTargetDisplayId;
+ result = 31 * result + mConfiguration.hashCode();
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
new file mode 100644
index 0000000..a0c617f
--- /dev/null
+++ b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Multi-window mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class MultiWindowModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInMultiWindowMode;
+ private final Configuration mOverrideConfig;
+
+ public MultiWindowModeChangeItem(boolean isInMultiWindowMode,
+ Configuration overrideConfig) {
+ mIsInMultiWindowMode = isInMultiWindowMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInMultiWindowMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private MultiWindowModeChangeItem(Parcel in) {
+ mIsInMultiWindowMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<MultiWindowModeChangeItem> CREATOR =
+ new Creator<MultiWindowModeChangeItem>() {
+ public MultiWindowModeChangeItem createFromParcel(Parcel in) {
+ return new MultiWindowModeChangeItem(in);
+ }
+
+ public MultiWindowModeChangeItem[] newArray(int size) {
+ return new MultiWindowModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
+ return mIsInMultiWindowMode == other.mIsInMultiWindowMode
+ && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInMultiWindowMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
new file mode 100644
index 0000000..61a8965
--- /dev/null
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.PAUSED;
+import static android.app.servertransaction.ActivityLifecycleItem.RESUMED;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Trace;
+
+import com.android.internal.content.ReferrerIntent;
+
+import java.util.List;
+
+/**
+ * New intent message.
+ * @hide
+ */
+public class NewIntentItem extends ClientTransactionItem {
+
+ private final List<ReferrerIntent> mIntents;
+ private final boolean mPause;
+
+ public NewIntentItem(List<ReferrerIntent> intents, boolean pause) {
+ mIntents = intents;
+ mPause = pause;
+ }
+
+ @Override
+ public int getPreExecutionState() {
+ return PAUSED;
+ }
+
+ @Override
+ public int getPostExecutionState() {
+ return RESUMED;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
+ client.handleNewIntent(token, mIntents, mPause);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mPause);
+ dest.writeTypedList(mIntents, flags);
+ }
+
+ /** Read from Parcel. */
+ private NewIntentItem(Parcel in) {
+ mPause = in.readBoolean();
+ mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
+ }
+
+ public static final Parcelable.Creator<NewIntentItem> CREATOR =
+ new Parcelable.Creator<NewIntentItem>() {
+ public NewIntentItem createFromParcel(Parcel in) {
+ return new NewIntentItem(in);
+ }
+
+ public NewIntentItem[] newArray(int size) {
+ return new NewIntentItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final NewIntentItem other = (NewIntentItem) o;
+ return mPause == other.mPause && mIntents.equals(other.mIntents);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mPause ? 1 : 0);
+ result = 31 * result + mIntents.hashCode();
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
new file mode 100644
index 0000000..e561a4b
--- /dev/null
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to paused state.
+ * @hide
+ */
+public class PauseActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "PauseActivityItem";
+
+ private final boolean mFinished;
+ private final boolean mUserLeaving;
+ private final int mConfigChanges;
+ private final boolean mDontReport;
+
+ private int mLifecycleSeq;
+
+ public PauseActivityItem(boolean finished, boolean userLeaving, int configChanges,
+ boolean dontReport) {
+ mFinished = finished;
+ mUserLeaving = userLeaving;
+ mConfigChanges = configChanges;
+ mDontReport = dontReport;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Pause transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
+ client.handlePauseActivity(token, mFinished, mUserLeaving, mConfigChanges, mDontReport,
+ mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return PAUSED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mFinished);
+ dest.writeBoolean(mUserLeaving);
+ dest.writeInt(mConfigChanges);
+ dest.writeBoolean(mDontReport);
+ }
+
+ /** Read from Parcel. */
+ private PauseActivityItem(Parcel in) {
+ mFinished = in.readBoolean();
+ mUserLeaving = in.readBoolean();
+ mConfigChanges = in.readInt();
+ mDontReport = in.readBoolean();
+ }
+
+ public static final Creator<PauseActivityItem> CREATOR =
+ new Creator<PauseActivityItem>() {
+ public PauseActivityItem createFromParcel(Parcel in) {
+ return new PauseActivityItem(in);
+ }
+
+ public PauseActivityItem[] newArray(int size) {
+ return new PauseActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PauseActivityItem other = (PauseActivityItem) o;
+ return mFinished == other.mFinished && mUserLeaving == other.mUserLeaving
+ && mConfigChanges == other.mConfigChanges && mDontReport == other.mDontReport;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mFinished ? 1 : 0);
+ result = 31 * result + (mUserLeaving ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ result = 31 * result + (mDontReport ? 1 : 0);
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/PipModeChangeItem.java b/core/java/android/app/servertransaction/PipModeChangeItem.java
new file mode 100644
index 0000000..923839e
--- /dev/null
+++ b/core/java/android/app/servertransaction/PipModeChangeItem.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Picture in picture mode change message.
+ * @hide
+ */
+// TODO(lifecycler): Remove the use of this and just use the configuration change message to
+// communicate multi-window mode change with WindowConfiguration.
+public class PipModeChangeItem extends ClientTransactionItem {
+
+ private final boolean mIsInPipMode;
+ private final Configuration mOverrideConfig;
+
+ public PipModeChangeItem(boolean isInPipMode, Configuration overrideConfig) {
+ mIsInPipMode = isInPipMode;
+ mOverrideConfig = overrideConfig;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mIsInPipMode);
+ dest.writeTypedObject(mOverrideConfig, flags);
+ }
+
+ /** Read from Parcel. */
+ private PipModeChangeItem(Parcel in) {
+ mIsInPipMode = in.readBoolean();
+ mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
+ }
+
+ public static final Creator<PipModeChangeItem> CREATOR =
+ new Creator<PipModeChangeItem>() {
+ public PipModeChangeItem createFromParcel(Parcel in) {
+ return new PipModeChangeItem(in);
+ }
+
+ public PipModeChangeItem[] newArray(int size) {
+ return new PipModeChangeItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final PipModeChangeItem other = (PipModeChangeItem) o;
+ return mIsInPipMode == other.mIsInPipMode && mOverrideConfig.equals(other.mOverrideConfig);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mIsInPipMode ? 1 : 0);
+ result = 31 * result + mOverrideConfig.hashCode();
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
new file mode 100644
index 0000000..ea31a46
--- /dev/null
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to resumed state.
+ * @hide
+ */
+public class ResumeActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "ResumeActivityItem";
+
+ private final int mProcState;
+ private final boolean mIsForward;
+
+ private int mLifecycleSeq;
+
+ public ResumeActivityItem(int procState, boolean isForward) {
+ mProcState = procState;
+ mIsForward = isForward;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Resume transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ client.updateProcessState(mProcState, false);
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
+ client.handleResumeActivity(token, true /* clearHide */, mIsForward,
+ true /* reallyResume */, mLifecycleSeq, "RESUME_ACTIVITY");
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return RESUMED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mProcState);
+ dest.writeBoolean(mIsForward);
+ }
+
+ /** Read from Parcel. */
+ private ResumeActivityItem(Parcel in) {
+ mProcState = in.readInt();
+ mIsForward = in.readBoolean();
+ }
+
+ public static final Creator<ResumeActivityItem> CREATOR =
+ new Creator<ResumeActivityItem>() {
+ public ResumeActivityItem createFromParcel(Parcel in) {
+ return new ResumeActivityItem(in);
+ }
+
+ public ResumeActivityItem[] newArray(int size) {
+ return new ResumeActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ResumeActivityItem other = (ResumeActivityItem) o;
+ return mProcState == other.mProcState && mIsForward == other.mIsForward;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mProcState;
+ result = 31 * result + (mIsForward ? 1 : 0);
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
new file mode 100644
index 0000000..d62c507
--- /dev/null
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+import android.util.Slog;
+
+/**
+ * Request to move an activity to stopped state.
+ * @hide
+ */
+public class StopActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "StopActivityItem";
+
+ private final boolean mShowWindow;
+ private final int mConfigChanges;
+
+ private int mLifecycleSeq;
+
+ public StopActivityItem(boolean showWindow, int configChanges) {
+ mShowWindow = showWindow;
+ mConfigChanges = configChanges;
+ }
+
+ @Override
+ public void prepare(ClientTransactionHandler client, IBinder token) {
+ mLifecycleSeq = client.getLifecycleSeq();
+ if (DEBUG_ORDER) {
+ Slog.d(TAG, "Stop transaction for " + client + " received seq: "
+ + mLifecycleSeq);
+ }
+ }
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
+ client.handleStopActivity(token, mShowWindow, mConfigChanges, mLifecycleSeq);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return STOPPED;
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ dest.writeInt(mConfigChanges);
+ }
+
+ /** Read from Parcel. */
+ private StopActivityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ mConfigChanges = in.readInt();
+ }
+
+ public static final Creator<StopActivityItem> CREATOR =
+ new Creator<StopActivityItem>() {
+ public StopActivityItem createFromParcel(Parcel in) {
+ return new StopActivityItem(in);
+ }
+
+ public StopActivityItem[] newArray(int size) {
+ return new StopActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final StopActivityItem other = (StopActivityItem) o;
+ return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mShowWindow ? 1 : 0);
+ result = 31 * result + mConfigChanges;
+ return result;
+ }
+}
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
new file mode 100644
index 0000000..8e88b38
--- /dev/null
+++ b/core/java/android/app/servertransaction/WindowVisibilityItem.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Window visibility change message.
+ * @hide
+ */
+public class WindowVisibilityItem extends ClientTransactionItem {
+
+ private final boolean mShowWindow;
+
+ public WindowVisibilityItem(boolean showWindow) {
+ mShowWindow = showWindow;
+ }
+
+ @Override
+ public void execute(android.app.ClientTransactionHandler client, IBinder token) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityShowWindow");
+ client.handleWindowVisibility(token, mShowWindow);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeBoolean(mShowWindow);
+ }
+
+ /** Read from Parcel. */
+ private WindowVisibilityItem(Parcel in) {
+ mShowWindow = in.readBoolean();
+ }
+
+ public static final Creator<WindowVisibilityItem> CREATOR =
+ new Creator<WindowVisibilityItem>() {
+ public WindowVisibilityItem createFromParcel(Parcel in) {
+ return new WindowVisibilityItem(in);
+ }
+
+ public WindowVisibilityItem[] newArray(int size) {
+ return new WindowVisibilityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final WindowVisibilityItem other = (WindowVisibilityItem) o;
+ return mShowWindow == other.mShowWindow;
+ }
+
+ @Override
+ public int hashCode() {
+ return 17 + 31 * (mShowWindow ? 1 : 0);
+ }
+}
diff --git a/core/java/android/app/slice/SliceItem.java b/core/java/android/app/slice/SliceItem.java
index 8d81199..cdeee35 100644
--- a/core/java/android/app/slice/SliceItem.java
+++ b/core/java/android/app/slice/SliceItem.java
@@ -216,7 +216,7 @@
* @return The slice held by this {@link #FORMAT_ACTION} or {@link #FORMAT_SLICE} SliceItem
*/
public Slice getSlice() {
- if (getFormat() == FORMAT_ACTION) {
+ if (FORMAT_ACTION.equals(getFormat())) {
return ((Pair<PendingIntent, Slice>) mObj).second;
}
return (Slice) mObj;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 837c00a..f8cdce6 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1156,6 +1156,7 @@
dest.writeString(permission);
dest.writeString(taskAffinity);
dest.writeString(targetActivity);
+ dest.writeString(launchToken);
dest.writeInt(flags);
dest.writeInt(screenOrientation);
dest.writeInt(configChanges);
@@ -1282,6 +1283,7 @@
permission = source.readString();
taskAffinity = source.readString();
targetActivity = source.readString();
+ launchToken = source.readString();
flags = source.readInt();
screenOrientation = source.readInt();
configChanges = source.readInt();
diff --git a/core/java/android/database/OWNERS b/core/java/android/database/OWNERS
new file mode 100644
index 0000000..84e81e4
--- /dev/null
+++ b/core/java/android/database/OWNERS
@@ -0,0 +1,2 @@
+fkupolov@google.com
+omakoto@google.com
\ No newline at end of file
diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java
index b7e353a..52527ed 100644
--- a/core/java/android/hardware/location/ContextHubClient.java
+++ b/core/java/android/hardware/location/ContextHubClient.java
@@ -16,43 +16,48 @@
package android.hardware.location;
import android.annotation.RequiresPermission;
-import android.os.Handler;
+import android.os.RemoteException;
+
+import dalvik.system.CloseGuard;
import java.io.Closeable;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* A class describing a client of the Context Hub Service.
*
- * Clients can send messages to nanoapps at a Context Hub through this object.
+ * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
+ * by this object are thread-safe and can be used without external synchronization.
*
* @hide
*/
public class ContextHubClient implements Closeable {
/*
- * The ContextHubClient interface associated with this client.
+ * The proxy to the client interface at the service.
*/
- // TODO: Implement this interface and associate with ContextHubClient object
- // private final IContextHubClient mClientInterface;
+ private final IContextHubClient mClientProxy;
/*
- * The listening callback associated with this client.
+ * The callback interface associated with this client.
*/
- private ContextHubClientCallback mCallback;
+ private final IContextHubClientCallback mCallbackInterface;
/*
* The Context Hub that this client is attached to.
*/
- private ContextHubInfo mAttachedHub;
+ private final ContextHubInfo mAttachedHub;
- /*
- * The handler to invoke mCallback.
- */
- private Handler mCallbackHandler;
+ private final CloseGuard mCloseGuard = CloseGuard.get();
- ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) {
- mCallback = callback;
- mCallbackHandler = handler;
+ private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
+
+ /* package */ ContextHubClient(
+ IContextHubClient clientProxy, IContextHubClientCallback callback,
+ ContextHubInfo hubInfo) {
+ mClientProxy = clientProxy;
+ mCallbackInterface = callback;
mAttachedHub = hubInfo;
+ mCloseGuard.open("close");
}
/**
@@ -71,7 +76,14 @@
* All futures messages targeted for this client are dropped at the service.
*/
public void close() {
- throw new UnsupportedOperationException("TODO: Implement this");
+ if (!mIsClosed.getAndSet(true)) {
+ mCloseGuard.close();
+ try {
+ mClientProxy.close();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -90,6 +102,22 @@
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@ContextHubTransaction.Result
public int sendMessageToNanoApp(NanoAppMessage message) {
- throw new UnsupportedOperationException("TODO: Implement this");
+ try {
+ return mClientProxy.sendMessageToNanoApp(message);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
}
}
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 2411727..b31c7bc 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -456,6 +456,54 @@
}
/**
+ * Creates an interface to the ContextHubClient to send down to the service.
+ *
+ * @param callback the callback to invoke at the client process
+ * @param handler the handler to post callbacks for this client
+ *
+ * @return the callback interface
+ */
+ private IContextHubClientCallback createClientCallback(
+ ContextHubClientCallback callback, Handler handler) {
+ return new IContextHubClientCallback.Stub() {
+ @Override
+ public void onMessageFromNanoApp(NanoAppMessage message) {
+ handler.post(() -> callback.onMessageFromNanoApp(message));
+ }
+
+ @Override
+ public void onHubReset() {
+ handler.post(() -> callback.onHubReset());
+ }
+
+ @Override
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {
+ handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode));
+ }
+
+ @Override
+ public void onNanoAppLoaded(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppLoaded(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppUnloaded(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppUnloaded(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppEnabled(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppEnabled(nanoAppId));
+ }
+
+ @Override
+ public void onNanoAppDisabled(long nanoAppId) {
+ handler.post(() -> callback.onNanoAppDisabled(nanoAppId));
+ }
+ };
+ }
+
+ /**
* Creates and registers a client and its callback with the Context Hub Service.
*
* A client is registered with the Context Hub Service for a specified Context Hub. When the
@@ -463,19 +511,37 @@
* {@link ContextHubClient} object, and receive notifications through the provided callback.
*
* @param callback the notification callback to register
- * @param hubInfo the hub to attach this client to
- * @param handler the handler to invoke the callback, if null uses the main thread's Looper
- *
+ * @param hubInfo the hub to attach this client to
+ * @param handler the handler to invoke the callback, if null uses the main thread's Looper
* @return the registered client object
*
- * @see ContextHubClientCallback
+ * @throws IllegalArgumentException if hubInfo does not represent a valid hub
+ * @throws IllegalStateException if there were too many registered clients at the service
+ * @throws NullPointerException if callback or hubInfo is null
*
* @hide
+ * @see ContextHubClientCallback
*/
public ContextHubClient createClient(
ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) {
- throw new UnsupportedOperationException(
- "TODO: Implement this, and throw an exception on error");
+ if (callback == null) {
+ throw new NullPointerException("Callback cannot be null");
+ }
+ if (hubInfo == null) {
+ throw new NullPointerException("Hub info cannot be null");
+ }
+
+ Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler;
+ IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler);
+
+ IContextHubClient client;
+ try {
+ client = mService.createClient(clientInterface, hubInfo.getId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ return new ContextHubClient(client, clientInterface, hubInfo);
}
/**
diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl
new file mode 100644
index 0000000..d81126a
--- /dev/null
+++ b/core/java/android/hardware/location/IContextHubClient.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * @hide
+ */
+interface IContextHubClient {
+
+ // Sends a message to a nanoapp
+ int sendMessageToNanoApp(in NanoAppMessage message);
+
+ // Closes the connection with the Context Hub
+ void close();
+}
diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl
new file mode 100644
index 0000000..1c76bcb
--- /dev/null
+++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+import android.hardware.location.NanoAppMessage;
+
+/**
+ * An interface used by the Context Hub Service to invoke callbacks for lifecycle notifications of a
+ * Context Hub and nanoapps, as well as for nanoapp messaging.
+ *
+ * @hide
+ */
+oneway interface IContextHubClientCallback {
+
+ // Callback invoked when receiving a message from a nanoapp.
+ void onMessageFromNanoApp(in NanoAppMessage message);
+
+ // Callback invoked when the attached Context Hub has reset.
+ void onHubReset();
+
+ // Callback invoked when a nanoapp aborts at the attached Context Hub.
+ void onNanoAppAborted(long nanoAppId, int abortCode);
+
+ // Callback invoked when a nanoapp is loaded at the attached Context Hub.
+ void onNanoAppLoaded(long nanoAppId);
+
+ // Callback invoked when a nanoapp is unloaded from the attached Context Hub.
+ void onNanoAppUnloaded(long nanoAppId);
+
+ // Callback invoked when a nanoapp is enabled at the attached Context Hub.
+ void onNanoAppEnabled(long nanoAppId);
+
+ // Callback invoked when a nanoapp is disabled at the attached Context Hub.
+ void onNanoAppDisabled(long nanoAppId);
+}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index ff8c1d0..1bb7c8f 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -17,12 +17,14 @@
package android.hardware.location;
// Declare any non-default types here with import statements
-import android.hardware.location.ContextHubMessage;
import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubMessage;
import android.hardware.location.NanoApp;
-import android.hardware.location.NanoAppInstanceInfo;
import android.hardware.location.NanoAppFilter;
+import android.hardware.location.NanoAppInstanceInfo;
import android.hardware.location.IContextHubCallback;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
/**
* @hide
@@ -52,4 +54,7 @@
// send a message to a nanoApp
int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+
+ // Creates a client to send and receive messages
+ IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 13b9206..7836cd0 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -21,18 +21,16 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.Paint.Align;
import android.graphics.Region.Op;
+import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.Keyboard.Key;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Message;
-import android.os.UserHandle;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.GestureDetector;
@@ -986,6 +984,9 @@
private void sendAccessibilityEventForUnicodeCharacter(int eventType, int code) {
if (mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isObservedEventType(eventType)) {
+ return;
+ }
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
onInitializeAccessibilityEvent(event);
final String text;
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 5dd8d05..068f5f7 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -555,6 +555,11 @@
int FORCE_ALL_APPS_STANDBY = 11;
/**
+ * Whether to enable background check on all apps or not.
+ */
+ int FORCE_BACKGROUND_CHECK = 12;
+
+ /**
* Whether to disable non-essential sensors. (e.g. edge sensors.)
*/
int OPTIONAL_SENSORS = 13;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 729c0ff..1e0948a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10456,6 +10456,15 @@
"storage_settings_clobber_threshold";
/**
+ * If set to 1, {@link Secure#LOCATION_MODE} will be set to {@link Secure#LOCATION_MODE_OFF}
+ * temporarily for all users.
+ *
+ * @hide
+ */
+ public static final String LOCATION_GLOBAL_KILL_SWITCH =
+ "location_global_kill_switch";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 953501c..cd362c7 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,8 +438,21 @@
* AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
*
* save(username, password);
- *
* </pre>
+ *
+ *
+ * <a name="Privacy"></a>
+ * <h3>Privacy</h3>
+ *
+ * <p>The {@link #onFillRequest(FillRequest, CancellationSignal, FillCallback)} method is called
+ * without the user content. The Android system strips some properties of the
+ * {@link android.app.assist.AssistStructure.ViewNode view nodes} passed to these calls, but not all
+ * of them. For example, the data provided in the {@link android.view.ViewStructure.HtmlInfo}
+ * objects set by {@link android.webkit.WebView} is never stripped out.
+ *
+ * <p>Because this data could contain PII (Personally Identifiable Information, such as username or
+ * email address), the service should only use it locally (i.e., in the app's process) for
+ * heuristics purposes, but it should not be sent to external servers.
*/
public abstract class AutofillService extends Service {
private static final String TAG = "AutofillService";
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index aa2f1c1..3d01ec2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -56,7 +56,9 @@
Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
boolean allLayers, boolean useIdentityTransform);
private static native void nativeCaptureLayers(IBinder layerHandleToken, Surface consumer,
- int rotation);
+ Rect sourceCrop, float frameScale);
+ private static native GraphicBuffer nativeCaptureLayers(IBinder layerHandleToken,
+ Rect sourceCrop, float frameScale);
private static native long nativeCreateTransaction();
private static native long nativeGetNativeTransactionFinalizer();
@@ -1177,14 +1179,24 @@
* Captures a layer and its children into the provided {@link Surface}.
*
* @param layerHandleToken The root layer to capture.
- * @param consumer The {@link Surface} to capture the layer into.
- * @param rotation Apply a custom clockwise rotation to the screenshot, i.e.
- * Surface.ROTATION_0,90,180,270. Surfaceflinger will always capture in its
- * native portrait orientation by default, so this is useful for returning
- * captures that are independent of device orientation.
+ * @param consumer The {@link Surface} to capture the layer into.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired.
+ * @param frameScale The desired scale of the returned buffer; the raw
+ * screen will be scaled up/down.
*/
- public static void captureLayers(IBinder layerHandleToken, Surface consumer, int rotation) {
- nativeCaptureLayers(layerHandleToken, consumer, rotation);
+ public static void captureLayers(IBinder layerHandleToken, Surface consumer, Rect sourceCrop,
+ float frameScale) {
+ nativeCaptureLayers(layerHandleToken, consumer, sourceCrop, frameScale);
+ }
+
+ /**
+ * Same as {@link #captureLayers(IBinder, Surface, Rect, float)} except this
+ * captures to a {@link GraphicBuffer} instead of a {@link Surface}.
+ */
+ public static GraphicBuffer captureLayersToBuffer(IBinder layerHandleToken, Rect sourceCrop,
+ float frameScale) {
+ return nativeCaptureLayers(layerHandleToken, sourceCrop, frameScale);
}
public static class Transaction implements Closeable {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 345e300..0525ab1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7208,7 +7208,8 @@
* @param text The announcement text.
*/
public void announceForAccessibility(CharSequence text) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT) && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_ANNOUNCEMENT);
onInitializeAccessibilityEvent(event);
@@ -10967,7 +10968,8 @@
if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) != 0) {
mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_FOCUSED;
invalidate();
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
event.setAction(action);
@@ -11792,7 +11794,8 @@
private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,
int fromIndex, int toIndex) {
- if (mParent == null) {
+ if (mParent == null || !AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY)) {
return;
}
AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -26182,7 +26185,8 @@
@Override
public void run() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_SCROLLED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_SCROLLED);
event.setScrollDeltaX(mDeltaX);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index eb5fc92..905c071 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -604,8 +604,10 @@
public static final int TYPE_DRAG = FIRST_SYSTEM_WINDOW+16;
/**
- * Window type: panel that slides out from under the status bar
- * In multiuser systems shows on all users' windows.
+ * Window type: panel that slides out from over the status bar
+ * In multiuser systems shows on all users' windows. These windows
+ * are displayed on top of the stauts bar and any {@link #TYPE_STATUS_BAR_PANEL}
+ * windows.
* @hide
*/
public static final int TYPE_STATUS_BAR_SUB_PANEL = FIRST_SYSTEM_WINDOW+17;
diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java
index d785117..da5a1cd 100644
--- a/core/java/android/view/accessibility/AccessibilityCache.java
+++ b/core/java/android/view/accessibility/AccessibilityCache.java
@@ -23,8 +23,6 @@
import android.util.LongSparseArray;
import android.util.SparseArray;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.ArrayList;
import java.util.List;
@@ -33,8 +31,7 @@
* It is updated when windows change or nodes change.
* @hide
*/
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public final class AccessibilityCache {
+public class AccessibilityCache {
private static final String LOG_TAG = "AccessibilityCache";
@@ -329,6 +326,8 @@
final long oldParentId = oldInfo.getParentNodeId();
if (info.getParentNodeId() != oldParentId) {
clearSubTreeLocked(windowId, oldParentId);
+ } else {
+ oldInfo.recycle();
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 5adea66..1d19a9f 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -23,6 +24,8 @@
import com.android.internal.util.BitUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -709,6 +712,38 @@
*/
public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 0x00000004;
+
+ /** @hide */
+ @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+ TYPE_VIEW_CLICKED,
+ TYPE_VIEW_LONG_CLICKED,
+ TYPE_VIEW_SELECTED,
+ TYPE_VIEW_FOCUSED,
+ TYPE_VIEW_TEXT_CHANGED,
+ TYPE_WINDOW_STATE_CHANGED,
+ TYPE_NOTIFICATION_STATE_CHANGED,
+ TYPE_VIEW_HOVER_ENTER,
+ TYPE_VIEW_HOVER_EXIT,
+ TYPE_TOUCH_EXPLORATION_GESTURE_START,
+ TYPE_TOUCH_EXPLORATION_GESTURE_END,
+ TYPE_WINDOW_CONTENT_CHANGED,
+ TYPE_VIEW_SCROLLED,
+ TYPE_VIEW_TEXT_SELECTION_CHANGED,
+ TYPE_ANNOUNCEMENT,
+ TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+ TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED,
+ TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY,
+ TYPE_GESTURE_DETECTION_START,
+ TYPE_GESTURE_DETECTION_END,
+ TYPE_TOUCH_INTERACTION_START,
+ TYPE_TOUCH_INTERACTION_END,
+ TYPE_WINDOWS_CHANGED,
+ TYPE_VIEW_CONTEXT_CLICKED,
+ TYPE_ASSIST_READING_CONTEXT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EventType {}
+
/**
* Mask for {@link AccessibilityEvent} all types.
*
@@ -741,7 +776,7 @@
private static final SynchronizedPool<AccessibilityEvent> sPool =
new SynchronizedPool<AccessibilityEvent>(MAX_POOL_SIZE);
- private int mEventType;
+ private @EventType int mEventType;
private CharSequence mPackageName;
private long mEventTime;
int mMovementGranularity;
@@ -833,7 +868,7 @@
*
* @return The event type.
*/
- public int getEventType() {
+ public @EventType int getEventType() {
return mEventType;
}
@@ -890,7 +925,7 @@
*
* @throws IllegalStateException If called from an AccessibilityService.
*/
- public void setEventType(int eventType) {
+ public void setEventType(@EventType int eventType) {
enforceNotSealed();
mEventType = eventType;
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index c3d6c69..d890f32 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -28,6 +28,8 @@
import android.util.LongSparseArray;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -86,6 +88,12 @@
private static final LongSparseArray<AccessibilityInteractionClient> sClients =
new LongSparseArray<>();
+ private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
+ new SparseArray<>();
+
+ private static AccessibilityCache sAccessibilityCache =
+ new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
+
private final AtomicInteger mInteractionIdCounter = new AtomicInteger();
private final Object mInstanceLock = new Object();
@@ -100,12 +108,6 @@
private Message mSameThreadMessage;
- private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache =
- new SparseArray<>();
-
- private static final AccessibilityCache sAccessibilityCache =
- new AccessibilityCache(new AccessibilityCache.AccessibilityNodeRefresher());
-
/**
* @return The client for the current thread.
*/
@@ -133,6 +135,50 @@
}
}
+ /**
+ * Gets a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @return The cached connection if such.
+ */
+ public static IAccessibilityServiceConnection getConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ return sConnectionCache.get(connectionId);
+ }
+ }
+
+ /**
+ * Adds a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ * @param connection The connection.
+ */
+ public static void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.put(connectionId, connection);
+ }
+ }
+
+ /**
+ * Removes a cached accessibility service connection.
+ *
+ * @param connectionId The connection id.
+ */
+ public static void removeConnection(int connectionId) {
+ synchronized (sConnectionCache) {
+ sConnectionCache.remove(connectionId);
+ }
+ }
+
+ /**
+ * This method is only for testing. Replacing the cache is a generally terrible idea, but
+ * tests need to be able to verify this class's interactions with the cache
+ */
+ @VisibleForTesting
+ public static void setCache(AccessibilityCache cache) {
+ sAccessibilityCache = cache;
+ }
+
private AccessibilityInteractionClient() {
/* reducing constructor visibility */
}
@@ -300,7 +346,7 @@
if (success) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, bypassCache);
if (infos != null && !infos.isEmpty()) {
for (int i = 1; i < infos.size(); i++) {
infos.get(i).recycle();
@@ -356,7 +402,7 @@
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
if (infos != null) {
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
return infos;
}
}
@@ -409,7 +455,7 @@
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
if (infos != null) {
- finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
+ finalizeAndCacheAccessibilityNodeInfos(infos, connectionId, false);
return infos;
}
}
@@ -460,7 +506,7 @@
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
return info;
}
} else {
@@ -509,7 +555,7 @@
if (success) {
AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
interactionId);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, false);
return info;
}
} else {
@@ -731,13 +777,17 @@
*
* @param info The info.
* @param connectionId The id of the connection to the system.
+ * @param bypassCache Whether or not to bypass the cache. The node is added to the cache if
+ * this value is {@code false}
*/
private void finalizeAndCacheAccessibilityNodeInfo(AccessibilityNodeInfo info,
- int connectionId) {
+ int connectionId, boolean bypassCache) {
if (info != null) {
info.setConnectionId(connectionId);
info.setSealed(true);
- sAccessibilityCache.add(info);
+ if (!bypassCache) {
+ sAccessibilityCache.add(info);
+ }
}
}
@@ -746,14 +796,16 @@
*
* @param infos The {@link AccessibilityNodeInfo}s.
* @param connectionId The id of the connection to the system.
+ * @param bypassCache Whether or not to bypass the cache. The nodes are added to the cache if
+ * this value is {@code false}
*/
private void finalizeAndCacheAccessibilityNodeInfos(List<AccessibilityNodeInfo> infos,
- int connectionId) {
+ int connectionId, boolean bypassCache) {
if (infos != null) {
final int infosCount = infos.size();
for (int i = 0; i < infosCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
- finalizeAndCacheAccessibilityNodeInfo(info, connectionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, bypassCache);
}
}
}
@@ -773,41 +825,6 @@
}
/**
- * Gets a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- * @return The cached connection if such.
- */
- public IAccessibilityServiceConnection getConnection(int connectionId) {
- synchronized (sConnectionCache) {
- return sConnectionCache.get(connectionId);
- }
- }
-
- /**
- * Adds a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- * @param connection The connection.
- */
- public void addConnection(int connectionId, IAccessibilityServiceConnection connection) {
- synchronized (sConnectionCache) {
- sConnectionCache.put(connectionId, connection);
- }
- }
-
- /**
- * Removes a cached accessibility service connection.
- *
- * @param connectionId The connection id.
- */
- public void removeConnection(int connectionId) {
- synchronized (sConnectionCache) {
- sConnectionCache.remove(connectionId);
- }
- }
-
- /**
* Checks whether the infos are a fully connected tree with no duplicates.
*
* @param infos The result list to check.
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 35f6acb..0375635 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -24,6 +24,7 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -187,6 +188,7 @@
*
* @hide
*/
+ @TestApi
public interface AccessibilityServicesStateChangeListener {
/**
@@ -452,6 +454,18 @@
}
/**
+ * Returns whether there are observers registered for this event type. If
+ * this method returns false you shuold not generate events of this type
+ * to conserve resources.
+ *
+ * @param type The event type.
+ * @return Whether the event is being observed.
+ */
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return mIsEnabled && (mRelevantEventTypes & type) != 0;
+ }
+
+ /**
* Requests feedback interruption from all accessibility services.
*/
public void interrupt() {
@@ -683,6 +697,7 @@
* for a callback on the process's main handler.
* @hide
*/
+ @TestApi
public void addAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener, @Nullable Handler handler) {
synchronized (mLock) {
@@ -698,6 +713,7 @@
*
* @hide
*/
+ @TestApi
public void removeAccessibilityServicesStateChangeListener(
@NonNull AccessibilityServicesStateChangeListener listener) {
// Final CopyOnWriteArrayList - no lock needed.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 665d694..6f99254 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -2758,7 +2758,9 @@
* <p>For example, an HTML form with 2 fields for username and password:
*
* <pre class="prettyprint">
+ * <label>Username:</label>
* <input type="text" name="username" id="user" value="Type your username" autocomplete="username" placeholder="Email or username">
+ * <label>Password:</label>
* <input type="password" name="password" id="pass" autocomplete="current-password" placeholder="Password">
* </pre>
*
@@ -2772,6 +2774,7 @@
* username.setHtmlInfo(username.newHtmlInfoBuilder("input")
* .addAttribute("type", "text")
* .addAttribute("name", "username")
+ * .addAttribute("label", "Username:")
* .build());
* username.setHint("Email or username");
* username.setAutofillType(View.AUTOFILL_TYPE_TEXT);
@@ -2785,6 +2788,7 @@
* password.setHtmlInfo(password.newHtmlInfoBuilder("input")
* .addAttribute("type", "password")
* .addAttribute("name", "password")
+ * .addAttribute("label", "Password:")
* .build());
* password.setHint("Password");
* password.setAutofillType(View.AUTOFILL_TYPE_TEXT);
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index 4d3189e..b379280 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -1952,7 +1952,8 @@
CharSequence beforeText = mInputText.getText();
if (!text.equals(beforeText.toString())) {
mInputText.setText(text);
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
mInputText.onInitializeAccessibilityEvent(event);
@@ -2612,7 +2613,7 @@
}
private void sendAccessibilityEventForVirtualText(int eventType) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
mInputText.onInitializeAccessibilityEvent(event);
mInputText.onPopulateAccessibilityEvent(event);
@@ -2623,7 +2624,7 @@
private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
String text) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setClassName(Button.class.getName());
event.setPackageName(mContext.getPackageName());
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d9bc51f..71532a7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -10836,6 +10836,10 @@
void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
int fromIndex, int removedCount, int addedCount) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)) {
+ return;
+ }
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index d807120..bfde6ac 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -504,7 +504,8 @@
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
- if (!accessibilityManager.isEnabled()) {
+ if (!accessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
return;
}
// treat toasts as notifications since they are used to
diff --git a/core/java/com/android/internal/content/ReferrerIntent.java b/core/java/com/android/internal/content/ReferrerIntent.java
index 8d9a1cf..76dcc9b 100644
--- a/core/java/com/android/internal/content/ReferrerIntent.java
+++ b/core/java/com/android/internal/content/ReferrerIntent.java
@@ -19,6 +19,8 @@
import android.content.Intent;
import android.os.Parcel;
+import java.util.Objects;
+
/**
* Subclass of Intent that also contains referrer (as a package name) information.
*/
@@ -48,4 +50,21 @@
return new ReferrerIntent[size];
}
};
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof ReferrerIntent)) {
+ return false;
+ }
+ final ReferrerIntent other = (ReferrerIntent) obj;
+ return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + filterHashCode();
+ result = 31 * result + Objects.hashCode(mReferrer);
+ return result;
+ }
}
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index bab0306aa..5ec9094 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -113,6 +113,11 @@
void showGlobalActionsMenu();
/**
+ * Notifies the status bar that a new rotation suggestion is available.
+ */
+ void onProposedRotationChanged(int rotation);
+
+ /**
* Set whether the top app currently hides the statusbar.
*
* @param hidesStatusBar whether it is being hidden
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index c22be2c..9a6e542 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@
mBuffer[indexOf(mCursor++)] = t;
}
+ /**
+ * Returns object of type <T> at the next writable slot, creating one if it is not already
+ * available. In case of any errors while creating the object, <code>null</code> will
+ * be returned.
+ */
+ public T getNextSlot() {
+ final int nextSlotIdx = indexOf(mCursor++);
+ T item = mBuffer[nextSlotIdx];
+ if (item == null) {
+ try {
+ item = (T) mBuffer.getClass().getComponentType().newInstance();
+ } catch (IllegalAccessException | InstantiationException e) {
+ return null;
+ }
+ mBuffer[nextSlotIdx] = item;
+ }
+ return item;
+ }
+
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index daf745f..bfe4323 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -61,17 +61,19 @@
* Returns a default user icon for the given user.
*
* Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
+ *
+ * @param resources resources object to fetch user icon / color.
* @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
* @param light whether we want a light icon (suitable for a dark background)
*/
- public static Drawable getDefaultUserIcon(int userId, boolean light) {
+ public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) {
int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray;
if (userId != UserHandle.USER_NULL) {
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
- Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
+ Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
+ icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
diff --git a/core/java/com/android/internal/widget/ExploreByTouchHelper.java b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
index 50ad547..759a41a 100644
--- a/core/java/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/core/java/com/android/internal/widget/ExploreByTouchHelper.java
@@ -186,6 +186,9 @@
}
final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+ if (event == null) {
+ return false;
+ }
return parent.requestSendAccessibilityEvent(mView, event);
}
@@ -240,6 +243,9 @@
if (parent != null) {
final AccessibilityEvent event = createEvent(virtualViewId,
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ if (event == null) {
+ return;
+ }
event.setContentChangeTypes(changeTypes);
parent.requestSendAccessibilityEvent(mView, event);
}
@@ -305,6 +311,9 @@
* the specified item.
*/
private AccessibilityEvent createEventForHost(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
mView.onInitializeAccessibilityEvent(event);
@@ -325,6 +334,9 @@
* the specified item.
*/
private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+ return null;
+ }
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
event.setEnabled(true);
event.setClassName(DEFAULT_CLASS_NAME);
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index bb1bfad..f77e6c4 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -291,7 +291,7 @@
}
static void nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerHandleToken,
- jobject surfaceObj, int rotation) {
+ jobject surfaceObj, jobject sourceCropObj, jfloat frameScale) {
sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
if (layerHandle == NULL) {
@@ -303,7 +303,42 @@
return;
}
- ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), rotation);
+ Rect sourceCrop;
+ if (sourceCropObj != NULL) {
+ sourceCrop = rectFromObj(env, sourceCropObj);
+ }
+
+ ScreenshotClient::captureLayers(layerHandle, consumer->getIGraphicBufferProducer(), sourceCrop,
+ frameScale);
+}
+
+static jobject nativeCaptureLayersToBuffer(JNIEnv* env, jclass clazz, jobject layerHandleToken,
+ jobject sourceCropObj, jfloat frameScale) {
+
+ sp<IBinder> layerHandle = ibinderForJavaObject(env, layerHandleToken);
+ if (layerHandle == NULL) {
+ return NULL;
+ }
+
+ Rect sourceCrop;
+ if (sourceCropObj != NULL) {
+ sourceCrop = rectFromObj(env, sourceCropObj);
+ }
+
+ sp<GraphicBuffer> buffer;
+ status_t res = ScreenshotClient::captureLayersToBuffer(layerHandle, sourceCrop, frameScale,
+ &buffer);
+ if (res != NO_ERROR) {
+ return NULL;
+ }
+
+ return env->CallStaticObjectMethod(gGraphicBufferClassInfo.clazz,
+ gGraphicBufferClassInfo.builder,
+ buffer->getWidth(),
+ buffer->getHeight(),
+ buffer->getPixelFormat(),
+ (jint)buffer->getUsage(),
+ (jlong)buffer.get());
}
static void nativeApplyTransaction(JNIEnv* env, jclass clazz, jlong transactionObj, jboolean sync) {
@@ -975,8 +1010,10 @@
{"nativeScreenshotToBuffer",
"(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/GraphicBuffer;",
(void*)nativeScreenshotToBuffer },
- {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;I)V",
+ {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;F)V",
(void*)nativeCaptureLayers },
+ {"nativeCaptureLayers", "(Landroid/os/IBinder;Landroid/graphics/Rect;F)Landroid/graphics/GraphicBuffer;",
+ (void*)nativeCaptureLayersToBuffer },
};
int register_android_view_SurfaceControl(JNIEnv* env)
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto
index d2cd190..d724437 100644
--- a/core/proto/android/server/alarmmanagerservice.proto
+++ b/core/proto/android/server/alarmmanagerservice.proto
@@ -20,6 +20,7 @@
import "frameworks/base/core/proto/android/app/pendingintent.proto";
import "frameworks/base/core/proto/android/internal/locallog.proto";
import "frameworks/base/core/proto/android/os/worksource.proto";
+import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto";
package com.android.server;
@@ -32,10 +33,9 @@
optional int64 last_time_change_realtime = 4;
// Current settings
optional ConstantsProto settings = 5;
- // UIDs currently in the foreground.
- repeated int32 foreground_uids = 6;
- // Packages forced into app standby.
- repeated string forced_app_standby_packages = 7;
+
+ // Dump from ForceAppStandbyTracker.
+ optional ForceAppStandbyTrackerProto force_app_standby_tracker = 6;
optional bool is_interactive = 8;
// Only valid if is_interactive is false.
diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto
new file mode 100644
index 0000000..8753bf7
--- /dev/null
+++ b/core/proto/android/server/forceappstandbytracker.proto
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.server;
+
+option java_multiple_files = true;
+
+// Dump from ForceAppStandbyTracker.
+message ForceAppStandbyTrackerProto {
+ // Whether all apps are forced standby or not.
+ optional bool force_all_apps_standby = 1;
+
+ // UIDs currently in the foreground.
+ repeated int32 foreground_uids = 2;
+
+ // App ids that are in power-save whitelist.
+ repeated int32 power_save_whitelist_app_ids = 3;
+
+ // App ids that are in temporary power-save whitelist.
+ repeated int32 temp_power_save_whitelist_app_ids = 4;
+
+ message RunAnyInBackgroundRestrictedPackages {
+ optional int32 uid = 1;
+ optional string package_name = 2;
+ }
+
+ // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND.
+ repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5;
+}
diff --git a/core/res/res/drawable/btn_colored_material.xml b/core/res/res/drawable/btn_colored_material.xml
index c3c5760..7ba21e8 100644
--- a/core/res/res/drawable/btn_colored_material.xml
+++ b/core/res/res/drawable/btn_colored_material.xml
@@ -23,7 +23,7 @@
<item>
<shape android:shape="rectangle"
android:tint="@color/btn_colored_background_material">
- <corners android:radius="@dimen/control_corner_material" />
+ <corners android:radius="?attr/buttonCornerRadius" />
<solid android:color="@color/white" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
diff --git a/core/res/res/drawable/btn_default_mtrl_shape.xml b/core/res/res/drawable/btn_default_mtrl_shape.xml
index 8a31d5e..9d9cd06 100644
--- a/core/res/res/drawable/btn_default_mtrl_shape.xml
+++ b/core/res/res/drawable/btn_default_mtrl_shape.xml
@@ -23,7 +23,7 @@
android:insetBottom="@dimen/button_inset_vertical_material">
<shape android:shape="rectangle"
android:tint="?attr/colorButtonNormal">
- <corners android:radius="@dimen/control_corner_material" />
+ <corners android:radius="?attr/buttonCornerRadius" />
<solid android:color="@color/white" />
<padding android:left="@dimen/button_padding_horizontal_material"
android:top="@dimen/button_padding_vertical_material"
diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index caeb43a..49b0ee7 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -23,7 +23,8 @@
android:id="@+id/actions"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_action_list_height"
- android:paddingEnd="4dp"
+ android:paddingEnd="12dp"
+ android:paddingStart="8dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:visibility="gone"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9a0eafd..da8a9cc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1016,6 +1016,9 @@
<!-- Style for the "neutral" buttons within button bars. -->
<attr name="buttonBarNeutralButtonStyle" format="reference" />
+ <!-- Corner radius of buttons. -->
+ <attr name="buttonCornerRadius" format="dimension" />
+
<!-- Style for the search query widget. -->
<attr name="searchViewStyle" format="reference" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c104616..171f74f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1955,7 +1955,7 @@
<string name="config_dozeLongPressSensorType" translatable="false"></string>
<!-- Control whether the always on display mode is available. This should only be enabled on
- devices where the display has be tuned to be power efficient in DOZE and/or DOZE_SUSPEND
+ devices where the display has been tuned to be power efficient in DOZE and/or DOZE_SUSPEND
states. -->
<bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
@@ -3174,6 +3174,12 @@
<!-- Corner radius of system dialogs -->
<dimen name="config_dialogCornerRadius">2dp</dimen>
+ <!-- Corner radius of system buttons -->
+ <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
+ <!-- Controls whether system buttons use all caps for text -->
+ <bool name="config_buttonTextAllCaps">true</bool>
+ <!-- Name of the font family used for system buttons -->
+ <string name="config_fontFamilyButton">@string/font_family_button_material</string>
<string translatable="false" name="config_batterySaverDeviceSpecificConfig"></string>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 946216c..a659b37 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -152,11 +152,11 @@
<dimen name="dialog_padding">16dp</dimen>
<!-- The margin on the start of the content view -->
- <dimen name="notification_content_margin_start">16dp</dimen>
+ <dimen name="notification_content_margin_start">24dp</dimen>
<!-- The margin on the end of the content view
Keep in sync with notification_content_plus_picture_margin! -->
- <dimen name="notification_content_margin_end">16dp</dimen>
+ <dimen name="notification_content_margin_end">24dp</dimen>
<!-- The margin on the end of the content view with a picture.
Keep in sync with notification_content_plus_picture_margin! -->
@@ -166,7 +166,7 @@
content end margin.
Keep equal to (notification_content_picture_margin + notification_content_margin_end)!
-->
- <dimen name="notification_content_plus_picture_margin_end">72dp</dimen>
+ <dimen name="notification_content_plus_picture_margin_end">80dp</dimen>
<!-- The additional margin on the sides of the ambient view. -->
<dimen name="notification_extra_margin_ambient">16dp</dimen>
@@ -175,10 +175,10 @@
<dimen name="notification_action_list_height">56dp</dimen>
<!-- height of the content margin to accomodate for the header -->
- <dimen name="notification_content_margin_top">37.5dp</dimen>
+ <dimen name="notification_content_margin_top">41.5dp</dimen>
<!-- height of the content margin on the bottom -->
- <dimen name="notification_content_margin_bottom">16dp</dimen>
+ <dimen name="notification_content_margin_bottom">18dp</dimen>
<!-- The height of the progress bar. -->
<dimen name="notification_progress_bar_height">15dp</dimen>
@@ -187,16 +187,16 @@
<dimen name="notification_progress_margin_top">8dp</dimen>
<!-- height of the notification header (for icon and package name) -->
- <dimen name="notification_header_height">48dp</dimen>
+ <dimen name="notification_header_height">54dp</dimen>
<!-- The height of the background for a notification header on a group -->
- <dimen name="notification_header_background_height">45.5dp</dimen>
+ <dimen name="notification_header_background_height">49.5dp</dimen>
<!-- The top padding for the notification header -->
- <dimen name="notification_header_padding_top">10dp</dimen>
+ <dimen name="notification_header_padding_top">14dp</dimen>
<!-- The bottom padding for the notification header -->
- <dimen name="notification_header_padding_bottom">11dp</dimen>
+ <dimen name="notification_header_padding_bottom">13dp</dimen>
<!-- The margin at the bottom of the notification header. -->
<dimen name="notification_header_margin_bottom">5dp</dimen>
@@ -266,6 +266,8 @@
<dimen name="alert_dialog_round_padding">27dip</dimen>
<!-- Dialog title height -->
<dimen name="alert_dialog_title_height">64dip</dimen>
+ <!-- Dialog button bar width -->
+ <dimen name="alert_dialog_button_bar_width">64dp</dimen>
<!-- Dialog button bar height -->
<dimen name="alert_dialog_button_bar_height">48dip</dimen>
<!-- Leanback dialog vertical margin -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 812595d..3dbb89c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2853,9 +2853,12 @@
<!-- @hide For use by platform and tools only. Developers should not specify this value. -->
<public name="compileSdkVersionCodename" />
<public name="screenReaderFocusable" />
+ <public name="buttonCornerRadius" />
</public-group>
<public-group type="style" first-id="0x010302e0">
+ <public name="Widget.DeviceDefault.Button.Colored" />
+ <public name="Widget.DeviceDefault.Button.Borderless.Colored" />
</public-group>
<public-group type="id" first-id="0x01020044">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e4310e1..2f1d679 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2853,7 +2853,7 @@
<string name="unsupported_display_size_show">Always show</string>
<!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. -->
- <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
+ <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for an incompatible version of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
<!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. -->
<string name="unsupported_compile_sdk_show">Always show</string>
<!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. -->
diff --git a/core/res/res/values/styles_device_defaults.xml b/core/res/res/values/styles_device_defaults.xml
index 93a5264..189b3b7 100644
--- a/core/res/res/values/styles_device_defaults.xml
+++ b/core/res/res/values/styles_device_defaults.xml
@@ -37,6 +37,10 @@
<style name="Widget.DeviceDefault.Button.Small" parent="Widget.Material.Button.Small"/>
<style name="Widget.DeviceDefault.Button.Inset" parent="Widget.Material.Button.Inset"/>
<style name="Widget.DeviceDefault.Button.Toggle" parent="Widget.Material.Button.Toggle"/>
+ <style name="Widget.DeviceDefault.Button.Colored" parent="Widget.Material.Button.Colored">
+ <item name="textAppearance">?attr/textAppearanceButton</item>
+ <item name="textColor">@color/btn_colored_text_material</item>
+ </style>
<style name="Widget.DeviceDefault.TextView" parent="Widget.Material.TextView"/>
<style name="Widget.DeviceDefault.CheckedTextView" parent="Widget.Material.CheckedTextView"/>
<style name="Widget.DeviceDefault.AutoCompleteTextView" parent="Widget.Material.AutoCompleteTextView"/>
@@ -77,6 +81,15 @@
<style name="Widget.DeviceDefault.ActionButton.CloseMode" parent="Widget.Material.ActionButton.CloseMode"/>
<style name="Widget.DeviceDefault.ActionBar" parent="Widget.Material.ActionBar"/>
<style name="Widget.DeviceDefault.Button.Borderless" parent="Widget.Material.Button.Borderless"/>
+ <!-- Colored borderless ink button -->
+ <style name="Widget.DeviceDefault.Button.Borderless.Colored">
+ <item name="textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored</item>
+ </style>
+ <!-- Alert dialog button bar button -->
+ <style name="Widget.DeviceDefault.Button.ButtonBar.AlertDialog" parent="Widget.DeviceDefault.Button.Borderless.Colored">
+ <item name="minWidth">@dimen/alert_dialog_button_bar_width</item>
+ <item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
+ </style>
<style name="Widget.DeviceDefault.Tab" parent="Widget.Material.Tab"/>
<style name="Widget.DeviceDefault.CalendarView" parent="Widget.Material.CalendarView"/>
<style name="Widget.DeviceDefault.DatePicker" parent="Widget.Material.DatePicker"/>
@@ -211,7 +224,10 @@
<style name="TextAppearance.DeviceDefault.SearchResult.Title" parent="TextAppearance.Material.SearchResult.Title"/>
<style name="TextAppearance.DeviceDefault.SearchResult.Subtitle" parent="TextAppearance.Material.SearchResult.Subtitle"/>
<style name="TextAppearance.DeviceDefault.Widget" parent="TextAppearance.Material.Widget"/>
- <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button"/>
+ <style name="TextAppearance.DeviceDefault.Widget.Button" parent="TextAppearance.Material.Widget.Button">
+ <item name="fontFamily">@string/config_fontFamilyButton</item>
+ <item name="textAllCaps">@bool/config_buttonTextAllCaps</item>
+ </style>
<style name="TextAppearance.DeviceDefault.Widget.IconMenu.Item" parent="TextAppearance.Material.Widget.IconMenu.Item"/>
<style name="TextAppearance.DeviceDefault.Widget.TabWidget" parent="TextAppearance.Material.Widget.TabWidget"/>
<style name="TextAppearance.DeviceDefault.Widget.TextView" parent="TextAppearance.Material.Widget.TextView"/>
@@ -220,6 +236,9 @@
<style name="TextAppearance.DeviceDefault.Widget.DropDownItem" parent="TextAppearance.Material.Widget.DropDownItem"/>
<style name="TextAppearance.DeviceDefault.Widget.TextView.SpinnerItem" parent="TextAppearance.Material.Widget.TextView.SpinnerItem"/>
<style name="TextAppearance.DeviceDefault.Widget.EditText" parent="TextAppearance.Material.Widget.EditText"/>
+ <style name="TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored" parent="TextAppearance.DeviceDefault.Widget.Button">
+ <item name="textColor">@color/btn_colored_borderless_text_material</item>
+ </style>
<style name="TextAppearance.DeviceDefault.Widget.PopupMenu" parent="TextAppearance.Material.Widget.PopupMenu"/>
<style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Large" parent="TextAppearance.Material.Widget.PopupMenu.Large"/>
<style name="TextAppearance.DeviceDefault.Widget.PopupMenu.Small" parent="TextAppearance.Material.Widget.PopupMenu.Small"/>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index 2cd4dcb..def650a 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -561,7 +561,7 @@
<!-- Alert dialog button bar button -->
<style name="Widget.Material.Button.ButtonBar.AlertDialog" parent="Widget.Material.Button.Borderless.Colored">
- <item name="minWidth">64dp</item>
+ <item name="minWidth">@dimen/alert_dialog_button_bar_width</item>
<item name="minHeight">@dimen/alert_dialog_button_bar_height</item>
</style>
@@ -1304,7 +1304,7 @@
<item name="paddingBottom">@dimen/notification_header_padding_bottom</item>
<item name="layout_marginBottom">@dimen/notification_header_margin_bottom</item>
<item name="paddingStart">@dimen/notification_content_margin_start</item>
- <item name="paddingEnd">16dp</item>
+ <item name="paddingEnd">@dimen/notification_content_margin_end</item>
<item name="gravity">top</item>
</style>
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 68d5523..39310a8 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -69,12 +69,13 @@
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.DeviceDefault.Widget.PopupMenu.Small</item>
<!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
<item name="buttonStyle">@style/Widget.DeviceDefault.Button</item>
<item name="buttonStyleSmall">@style/Widget.DeviceDefault.Button.Small</item>
<item name="buttonStyleInset">@style/Widget.DeviceDefault.Button.Inset</item>
-
<item name="buttonStyleToggle">@style/Widget.DeviceDefault.Button.Toggle</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
<item name="switchStyle">@style/Widget.DeviceDefault.CompoundButton.Switch</item>
<item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless</item>
@@ -219,6 +220,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme
@@ -232,6 +240,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and
@@ -247,6 +262,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent
@@ -261,6 +283,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for dialog windows and activities. This changes the window to be
@@ -273,8 +302,14 @@
<item name="buttonBarStyle">@style/DeviceDefault.ButtonBar.AlertDialog</item>
<item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Button.Borderless.Small</item>
+ <!-- Text styles -->
<item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
<item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_dark</item>
@@ -297,6 +332,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
@@ -309,6 +351,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
@@ -322,6 +371,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -351,6 +407,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for a window without an action bar that will be displayed either
@@ -365,6 +428,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for a presentation window on a secondary display. -->
@@ -377,6 +447,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for panel windows. This removes all extraneous window
@@ -391,6 +468,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -404,6 +488,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear
@@ -417,6 +508,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -430,6 +528,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -443,6 +548,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -456,6 +568,13 @@
<item name="colorPrimary">@color/primary_device_default_dark</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
<item name="colorAccent">@color/accent_device_default_dark</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar">
@@ -467,6 +586,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
@@ -478,6 +604,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style -->
@@ -501,6 +634,7 @@
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.DeviceDefault.Widget.PopupMenu.Small</item>
<!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
<item name="buttonStyle">@style/Widget.DeviceDefault.Light.Button</item>
<item name="buttonStyleSmall">@style/Widget.DeviceDefault.Light.Button.Small</item>
@@ -644,6 +778,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar -->
@@ -656,6 +797,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar.
@@ -669,6 +817,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar
@@ -684,6 +839,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent
@@ -698,6 +860,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be
@@ -711,11 +880,15 @@
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
<item name="buttonBarStyle">@style/DeviceDefault.Light.ButtonBar.AlertDialog</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
<item name="borderlessButtonStyle">@style/Widget.DeviceDefault.Light.Button.Borderless.Small</item>
<item name="textAppearance">@style/TextAppearance.DeviceDefault</item>
<item name="textAppearanceInverse">@style/TextAppearance.DeviceDefault.Inverse</item>
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
<!-- Color palette -->
<item name="colorPrimary">@color/primary_device_default_light</item>
@@ -734,6 +907,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar -->
@@ -746,6 +926,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum
@@ -759,6 +946,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. -->
@@ -798,6 +992,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault light theme for a window without an action bar that will be displayed either
@@ -812,6 +1013,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault light theme for a presentation window on a secondary display. -->
@@ -824,6 +1032,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault light theme for panel windows. This removes all extraneous window
@@ -838,6 +1053,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert">
@@ -851,6 +1073,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar">
@@ -862,6 +1091,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
@@ -873,6 +1109,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- DeviceDefault theme for a window that should look like the Settings app. -->
@@ -882,6 +1125,7 @@
<item name="popupTheme">@style/ThemeOverlay.DeviceDefault.Popup.Light</item>
<!-- Color palette -->
+ <item name="colorBackground">@color/background_device_default_light</item>
<item name="colorPrimary">@color/primary_device_default_settings_light</item>
<item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
<item name="colorSecondary">@color/secondary_device_default_settings_light</item>
@@ -896,6 +1140,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<!-- @hide DeviceDefault theme for a window that should use Settings theme colors
@@ -908,6 +1159,13 @@
<item name="colorAccent">@color/accent_device_default_light</item>
<item name="colorControlNormal">?attr/textColorPrimary</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.QuickSettings.Dialog" parent="Theme.DeviceDefault.Light.Dialog">
@@ -930,6 +1188,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.Material.Settings.Dialog">
@@ -942,6 +1207,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Settings.DialogWhenLarge" parent="Theme.Material.Settings.DialogWhenLarge">
@@ -954,6 +1226,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -966,6 +1245,13 @@
<!-- Dialog attributes -->
<item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item>
<item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
+
+ <!-- Text styles -->
+ <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+
+ <!-- Button styles -->
+ <item name="buttonCornerRadius">@dimen/config_buttonCornerRadius</item>
+ <item name="buttonBarButtonStyle">@style/Widget.DeviceDefault.Button.ButtonBar.AlertDialog</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index c317121..9e6b1ab 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -103,6 +103,7 @@
<item name="buttonStyleSmall">@style/Widget.Material.Button.Small</item>
<item name="buttonStyleInset">@style/Widget.Material.Button.Inset</item>
<item name="buttonStyleToggle">@style/Widget.Material.Button.Toggle</item>
+ <item name="buttonCornerRadius">@dimen/control_corner_material</item>
<item name="switchStyle">@style/Widget.Material.CompoundButton.Switch</item>
<item name="mediaRouteButtonStyle">@style/Widget.Material.MediaRouteButton</item>
@@ -472,6 +473,7 @@
<item name="buttonStyleSmall">@style/Widget.Material.Light.Button.Small</item>
<item name="buttonStyleInset">@style/Widget.Material.Light.Button.Inset</item>
<item name="buttonStyleToggle">@style/Widget.Material.Light.Button.Toggle</item>
+ <item name="buttonCornerRadius">@dimen/control_corner_material</item>
<item name="switchStyle">@style/Widget.Material.Light.CompoundButton.Switch</item>
<item name="mediaRouteButtonStyle">@style/Widget.Material.Light.MediaRouteButton</item>
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
new file mode 100644
index 0000000..e0fcf08
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+// TODO(lifecycler): Add to presubmit after checking for flakiness.
+public class ClientTransactionTests {
+
+ @Test
+ public void testPrepare() {
+ ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class);
+ IBinder token = mock(IBinder.class);
+
+ ClientTransaction transaction = new ClientTransaction(null /* client */,
+ token /* activityToken */);
+ transaction.addCallback(callback1);
+ transaction.addCallback(callback2);
+ transaction.setLifecycleStateRequest(stateRequest);
+
+ transaction.prepare(clientTransactionHandler);
+
+ verify(callback1, times(1)).prepare(clientTransactionHandler, token);
+ verify(callback2, times(1)).prepare(clientTransactionHandler, token);
+ verify(stateRequest, times(1)).prepare(clientTransactionHandler, token);
+ }
+
+ @Test
+ public void testExecute() {
+ ClientTransactionItem callback1 = mock(ClientTransactionItem.class);
+ ClientTransactionItem callback2 = mock(ClientTransactionItem.class);
+ ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
+ IBinder token = mock(IBinder.class);
+
+ ClientTransaction transaction = new ClientTransaction(null /* client */,
+ token /* activityToken */);
+ transaction.addCallback(callback1);
+ transaction.addCallback(callback2);
+ transaction.setLifecycleStateRequest(stateRequest);
+
+ ClientTransactionHandler clientTransactionHandler = mock(ClientTransactionHandler.class);
+ transaction.prepare(clientTransactionHandler);
+ transaction.execute(clientTransactionHandler);
+
+ verify(callback1, times(1)).execute(clientTransactionHandler, token);
+ verify(callback2, times(1)).execute(clientTransactionHandler, token);
+ verify(stateRequest, times(1)).execute(clientTransactionHandler, token);
+ }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
new file mode 100644
index 0000000..9db7550
--- /dev/null
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.servertransaction;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
+import android.app.IApplicationThread;
+import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
+import android.app.ProfilerInfo;
+import android.app.ResultInfo;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** Test parcelling and unparcelling of transactions and transaction items. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+// TODO(lifecycler): Add to presubmit after checking for flakiness.
+public class TransactionParcelTests {
+
+ private Parcel mParcel;
+
+ @Before
+ public void setUp() throws Exception {
+ mParcel = Parcel.obtain();
+ }
+
+ @Test
+ public void testConfigurationChange() {
+ // Write to parcel
+ ConfigurationChangeItem item = new ConfigurationChangeItem(config());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testActivityConfigChange() {
+ // Write to parcel
+ ActivityConfigurationChangeItem item = new ActivityConfigurationChangeItem(config());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ ActivityConfigurationChangeItem result =
+ ActivityConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testMoveToDisplay() {
+ // Write to parcel
+ MoveToDisplayItem item = new MoveToDisplayItem(4 /* targetDisplayId */, config());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testNewIntent() {
+ // Write to parcel
+ NewIntentItem item = new NewIntentItem(referrerIntentList(), true /* pause */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testActivityResult() {
+ // Write to parcel
+ ActivityResultItem item = new ActivityResultItem(resultInfoList());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testPipModeChange() {
+ // Write to parcel
+ PipModeChangeItem item = new PipModeChangeItem(true /* isInPipMode */, config());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ PipModeChangeItem result = PipModeChangeItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testMultiWindowModeChange() {
+ // Write to parcel
+ MultiWindowModeChangeItem item = new MultiWindowModeChangeItem(
+ true /* isInMultiWindowMode */, config());
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ MultiWindowModeChangeItem result =
+ MultiWindowModeChangeItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testWindowVisibilityChange() {
+ // Write to parcel
+ WindowVisibilityItem item = new WindowVisibilityItem(true /* showWindow */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+
+ // Check different value
+ item = new WindowVisibilityItem(false);
+
+ mParcel = Parcel.obtain();
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testDestroy() {
+ DestroyActivityItem item = new DestroyActivityItem(true /* finished */,
+ 135 /* configChanges */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testLaunch() {
+ // Write to parcel
+ Intent intent = new Intent("action");
+ int ident = 57;
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.flags = 42;
+ activityInfo.maxAspectRatio = 2.4f;
+ activityInfo.launchToken = "token";
+ activityInfo.applicationInfo = new ApplicationInfo();
+ activityInfo.packageName = "packageName";
+ activityInfo.name = "name";
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.assetsSeq = 5;
+ CompatibilityInfo compat = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
+ String referrer = "referrer";
+ int procState = 4;
+ Bundle bundle = new Bundle();
+ bundle.putString("key", "value");
+ PersistableBundle persistableBundle = new PersistableBundle();
+ persistableBundle.putInt("k", 4);
+
+ LaunchActivityItem item = new LaunchActivityItem(intent, ident, activityInfo,
+ config(), overrideConfig, compat, referrer, null /* voiceInteractor */,
+ procState, bundle, persistableBundle, resultInfoList(), referrerIntentList(),
+ true /* notResumed */, true /* isForward */, null /* profilerInfo */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ LaunchActivityItem result = LaunchActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testPause() {
+ // Write to parcel
+ PauseActivityItem item = new PauseActivityItem(true /* finished */, true /* userLeaving */,
+ 135 /* configChanges */, true /* dontReport */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testResume() {
+ // Write to parcel
+ ResumeActivityItem item = new ResumeActivityItem(27 /* procState */, true /* isForward */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testStop() {
+ // Write to parcel
+ StopActivityItem item = new StopActivityItem(true /* showWindow */, 14 /* configChanges */);
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertTrue(item.equals(result));
+ }
+
+ @Test
+ public void testClientTransaction() {
+ // Write to parcel
+ WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
+ ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+
+ StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+ 78 /* configChanges */);
+
+ IApplicationThread appThread = new StubAppThread();
+ Binder activityToken = new Binder();
+
+ ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+ transaction.addCallback(callback1);
+ transaction.addCallback(callback2);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+
+ writeAndPrepareForReading(transaction);
+
+ // Read from parcel and assert
+ ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(transaction.hashCode(), result.hashCode());
+ assertTrue(transaction.equals(result));
+ }
+
+ @Test
+ public void testClientTransactionCallbacksOnly() {
+ // Write to parcel
+ WindowVisibilityItem callback1 = new WindowVisibilityItem(true);
+ ActivityConfigurationChangeItem callback2 = new ActivityConfigurationChangeItem(config());
+
+ IApplicationThread appThread = new StubAppThread();
+ Binder activityToken = new Binder();
+
+ ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+ transaction.addCallback(callback1);
+ transaction.addCallback(callback2);
+
+ writeAndPrepareForReading(transaction);
+
+ // Read from parcel and assert
+ ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(transaction.hashCode(), result.hashCode());
+ assertTrue(transaction.equals(result));
+ }
+
+ @Test
+ public void testClientTransactionLifecycleOnly() {
+ // Write to parcel
+ StopActivityItem lifecycleRequest = new StopActivityItem(true /* showWindow */,
+ 78 /* configChanges */);
+
+ IApplicationThread appThread = new StubAppThread();
+ Binder activityToken = new Binder();
+
+ ClientTransaction transaction = new ClientTransaction(appThread, activityToken);
+ transaction.setLifecycleStateRequest(lifecycleRequest);
+
+ writeAndPrepareForReading(transaction);
+
+ // Read from parcel and assert
+ ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(transaction.hashCode(), result.hashCode());
+ assertTrue(transaction.equals(result));
+ }
+
+ private static List<ResultInfo> resultInfoList() {
+ String resultWho1 = "resultWho1";
+ int requestCode1 = 7;
+ int resultCode1 = 4;
+ Intent data1 = new Intent("action1");
+ ResultInfo resultInfo1 = new ResultInfo(resultWho1, requestCode1, resultCode1, data1);
+
+ String resultWho2 = "resultWho2";
+ int requestCode2 = 8;
+ int resultCode2 = 6;
+ Intent data2 = new Intent("action2");
+ ResultInfo resultInfo2 = new ResultInfo(resultWho2, requestCode2, resultCode2, data2);
+
+ List<ResultInfo> resultInfoList = new ArrayList<>();
+ resultInfoList.add(resultInfo1);
+ resultInfoList.add(resultInfo2);
+
+ return resultInfoList;
+ }
+
+ private static List<ReferrerIntent> referrerIntentList() {
+ Intent intent1 = new Intent("action1");
+ ReferrerIntent referrerIntent1 = new ReferrerIntent(intent1, "referrer1");
+
+ Intent intent2 = new Intent("action2");
+ ReferrerIntent referrerIntent2 = new ReferrerIntent(intent2, "referrer2");
+
+ List<ReferrerIntent> referrerIntents = new ArrayList<>();
+ referrerIntents.add(referrerIntent1);
+ referrerIntents.add(referrerIntent2);
+
+ return referrerIntents;
+ }
+
+ private static Configuration config() {
+ Configuration config = new Configuration();
+ config.densityDpi = 10;
+ config.fontScale = 0.3f;
+ config.screenHeightDp = 15;
+ config.orientation = ORIENTATION_LANDSCAPE;
+ return config;
+ }
+
+ /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
+ private void writeAndPrepareForReading(Parcelable parcelable) {
+ parcelable.writeToParcel(mParcel, 0 /* flags */);
+ mParcel.setDataPosition(0);
+ }
+
+ /** Stub implementation of IApplicationThread that can be presented as {@link Binder}. */
+ class StubAppThread extends android.app.IApplicationThread.Stub {
+
+ @Override
+ public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleReceiver(Intent intent, ActivityInfo activityInfo,
+ CompatibilityInfo compatibilityInfo, int i, String s, Bundle bundle, boolean b,
+ int i1, int i2) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleCreateService(IBinder iBinder, ServiceInfo serviceInfo,
+ CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleStopService(IBinder iBinder) throws RemoteException {
+ }
+
+ @Override
+ public void bindApplication(String s, ApplicationInfo applicationInfo,
+ List<ProviderInfo> list, ComponentName componentName, ProfilerInfo profilerInfo,
+ Bundle bundle, IInstrumentationWatcher iInstrumentationWatcher,
+ IUiAutomationConnection iUiAutomationConnection, int i, boolean b, boolean b1,
+ boolean b2, boolean b3, Configuration configuration,
+ CompatibilityInfo compatibilityInfo, Map map, Bundle bundle1, String s1)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleExit() throws RemoteException {
+ }
+
+ @Override
+ public void scheduleServiceArgs(IBinder iBinder, ParceledListSlice parceledListSlice)
+ throws RemoteException {
+ }
+
+ @Override
+ public void updateTimeZone() throws RemoteException {
+ }
+
+ @Override
+ public void processInBackground() throws RemoteException {
+ }
+
+ @Override
+ public void scheduleBindService(IBinder iBinder, Intent intent, boolean b, int i)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleUnbindService(IBinder iBinder, Intent intent) throws RemoteException {
+ }
+
+ @Override
+ public void dumpService(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+ String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleRegisteredReceiver(IIntentReceiver iIntentReceiver, Intent intent,
+ int i, String s, Bundle bundle, boolean b, boolean b1, int i1, int i2)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleLowMemory() throws RemoteException {
+ }
+
+ @Override
+ public void scheduleRelaunchActivity(IBinder iBinder, List<ResultInfo> list,
+ List<ReferrerIntent> list1, int i, boolean b, Configuration configuration,
+ Configuration configuration1, boolean b1) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleSleeping(IBinder iBinder, boolean b) throws RemoteException {
+ }
+
+ @Override
+ public void profilerControl(boolean b, ProfilerInfo profilerInfo, int i)
+ throws RemoteException {
+ }
+
+ @Override
+ public void setSchedulingGroup(int i) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleCreateBackupAgent(ApplicationInfo applicationInfo,
+ CompatibilityInfo compatibilityInfo, int i) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleDestroyBackupAgent(ApplicationInfo applicationInfo,
+ CompatibilityInfo compatibilityInfo) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleOnNewActivityOptions(IBinder iBinder, Bundle bundle)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleSuicide() throws RemoteException {
+ }
+
+ @Override
+ public void dispatchPackageBroadcast(int i, String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleCrash(String s) throws RemoteException {
+ }
+
+ @Override
+ public void dumpActivity(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+ String s, String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void clearDnsCache() throws RemoteException {
+ }
+
+ @Override
+ public void setHttpProxy(String s, String s1, String s2, Uri uri) throws RemoteException {
+ }
+
+ @Override
+ public void setCoreSettings(Bundle bundle) throws RemoteException {
+ }
+
+ @Override
+ public void updatePackageCompatibilityInfo(String s, CompatibilityInfo compatibilityInfo)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleTrimMemory(int i) throws RemoteException {
+ }
+
+ @Override
+ public void dumpMemInfo(ParcelFileDescriptor parcelFileDescriptor,
+ Debug.MemoryInfo memoryInfo, boolean b, boolean b1, boolean b2, boolean b3,
+ boolean b4, String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void dumpGfxInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+ throws RemoteException {
+ }
+
+ @Override
+ public void dumpProvider(ParcelFileDescriptor parcelFileDescriptor, IBinder iBinder,
+ String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void dumpDbInfo(ParcelFileDescriptor parcelFileDescriptor, String[] strings)
+ throws RemoteException {
+ }
+
+ @Override
+ public void unstableProviderDied(IBinder iBinder) throws RemoteException {
+ }
+
+ @Override
+ public void requestAssistContextExtras(IBinder iBinder, IBinder iBinder1, int i, int i1,
+ int i2) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleTranslucentConversionComplete(IBinder iBinder, boolean b)
+ throws RemoteException {
+ }
+
+ @Override
+ public void setProcessState(int i) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleInstallProvider(ProviderInfo providerInfo) throws RemoteException {
+ }
+
+ @Override
+ public void updateTimePrefs(int i) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleEnterAnimationComplete(IBinder iBinder) throws RemoteException {
+ }
+
+ @Override
+ public void notifyCleartextNetwork(byte[] bytes) throws RemoteException {
+ }
+
+ @Override
+ public void startBinderTracking() throws RemoteException {
+ }
+
+ @Override
+ public void stopBinderTrackingAndDump(ParcelFileDescriptor parcelFileDescriptor)
+ throws RemoteException {
+ }
+
+ @Override
+ public void scheduleLocalVoiceInteractionStarted(IBinder iBinder,
+ IVoiceInteractor iVoiceInteractor) throws RemoteException {
+ }
+
+ @Override
+ public void handleTrustStorageUpdate() throws RemoteException {
+ }
+
+ @Override
+ public void attachAgent(String s) throws RemoteException {
+ }
+
+ @Override
+ public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
+ throws RemoteException {
+ }
+
+ @Override
+ public void setNetworkBlockSeq(long l) throws RemoteException {
+ }
+
+ @Override
+ public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
+ ParcelFileDescriptor fd) {
+ }
+
+ @Override
+ public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 454d209..4ce6029 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -231,6 +231,7 @@
Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS,
Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST,
+ Settings.Global.LOCATION_GLOBAL_KILL_SWITCH,
Settings.Global.LOCATION_SETTINGS_LINK_TO_PERMISSIONS_ENABLED,
Settings.Global.LOCK_SOUND,
Settings.Global.LOW_BATTERY_SOUND,
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
similarity index 95%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index c8dc9ff..2f32d13 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,26 +14,23 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package android.view.accessibility;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
+
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.support.test.runner.AndroidJUnit4;
-import android.view.accessibility.AccessibilityCache;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityWindowInfo;
import android.view.View;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -48,27 +45,27 @@
@RunWith(AndroidJUnit4.class)
public class AccessibilityCacheTest {
- private static int WINDOW_ID_1 = 0xBEEF;
- private static int WINDOW_ID_2 = 0xFACE;
- private static int SINGLE_VIEW_ID = 0xCAFE;
- private static int OTHER_VIEW_ID = 0xCAB2;
- private static int PARENT_VIEW_ID = 0xFED4;
- private static int CHILD_VIEW_ID = 0xFEED;
- private static int OTHER_CHILD_VIEW_ID = 0xACE2;
- private static int MOCK_CONNECTION_ID = 1;
+ private static final int WINDOW_ID_1 = 0xBEEF;
+ private static final int WINDOW_ID_2 = 0xFACE;
+ private static final int SINGLE_VIEW_ID = 0xCAFE;
+ private static final int OTHER_VIEW_ID = 0xCAB2;
+ private static final int PARENT_VIEW_ID = 0xFED4;
+ private static final int CHILD_VIEW_ID = 0xFEED;
+ private static final int OTHER_CHILD_VIEW_ID = 0xACE2;
+ private static final int MOCK_CONNECTION_ID = 1;
AccessibilityCache mAccessibilityCache;
AccessibilityCache.AccessibilityNodeRefresher mAccessibilityNodeRefresher;
- AtomicInteger numA11yNodeInfosInUse = new AtomicInteger(0);
- AtomicInteger numA11yWinInfosInUse = new AtomicInteger(0);
+ AtomicInteger mNumA11yNodeInfosInUse = new AtomicInteger(0);
+ AtomicInteger mNumA11yWinInfosInUse = new AtomicInteger(0);
@Before
public void setUp() {
mAccessibilityNodeRefresher = mock(AccessibilityCache.AccessibilityNodeRefresher.class);
when(mAccessibilityNodeRefresher.refreshNode(anyObject(), anyBoolean())).thenReturn(true);
mAccessibilityCache = new AccessibilityCache(mAccessibilityNodeRefresher);
- AccessibilityNodeInfo.setNumInstancesInUseCounter(numA11yNodeInfosInUse);
- AccessibilityWindowInfo.setNumInstancesInUseCounter(numA11yWinInfosInUse);
+ AccessibilityNodeInfo.setNumInstancesInUseCounter(mNumA11yNodeInfosInUse);
+ AccessibilityWindowInfo.setNumInstancesInUseCounter(mNumA11yWinInfosInUse);
}
@After
@@ -76,7 +73,8 @@
// Make sure we're recycling all of our window and node infos
mAccessibilityCache.clear();
AccessibilityInteractionClient.getInstance().clearCache();
- assertEquals(0, numA11yWinInfosInUse.get());
+ assertEquals(0, mNumA11yWinInfosInUse.get());
+ assertEquals(0, mNumA11yNodeInfosInUse.get());
}
@Test
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
new file mode 100644
index 0000000..8a5fc2d
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for AccessibilityInteractionClient
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityInteractionClientTest {
+ private static final int MOCK_CONNECTION_ID = 0xabcd;
+
+ private MockConnection mMockConnection = new MockConnection();
+ @Mock private AccessibilityCache mMockCache;
+
+ @Before
+ public void setUp() {
+ initMocks(this);
+ AccessibilityInteractionClient.setCache(mMockCache);
+ AccessibilityInteractionClient.addConnection(MOCK_CONNECTION_ID, mMockConnection);
+ }
+
+ /**
+ * When the AccessibilityCache refreshes the nodes it contains, it gets very confused if
+ * it is called to update itself during the refresh. It tries to update the node that it's
+ * in the process of refreshing, which leads to AccessibilityNodeInfos in inconsistent states.
+ */
+ @Test
+ public void findA11yNodeInfoByA11yId_whenBypassingCache_doesntTouchCache() {
+ final int windowId = 0x1234;
+ final long accessibilityNodeId = 0x4321L;
+ AccessibilityNodeInfo nodeFromConnection = AccessibilityNodeInfo.obtain();
+ nodeFromConnection.setSourceNodeId(accessibilityNodeId, windowId);
+ mMockConnection.mInfosToReturn = Arrays.asList(nodeFromConnection);
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ AccessibilityNodeInfo node = client.findAccessibilityNodeInfoByAccessibilityId(
+ MOCK_CONNECTION_ID, windowId, accessibilityNodeId, true, 0, null);
+ assertEquals("Node got lost along the way", nodeFromConnection, node);
+
+ verifyZeroInteractions(mMockCache);
+ }
+
+ private static class MockConnection extends AccessibilityServiceConnectionImpl {
+ List<AccessibilityNodeInfo> mInfosToReturn;
+
+ @Override
+ public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+ Bundle arguments) {
+ try {
+ callback.setFindAccessibilityNodeInfosResult(mInfosToReturn, interactionId);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
similarity index 78%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 7f97973..79e6316 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -1,10 +1,25 @@
-package com.android.server.accessibility;
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
import static org.junit.Assert.fail;
import android.support.test.runner.AndroidJUnit4;
import android.util.ArraySet;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import com.android.internal.util.CollectionUtils;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
new file mode 100644
index 0000000..d3bbee7
--- /dev/null
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Region;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
+ * all of the methods
+ */
+public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceConnection.Stub {
+ public void setServiceInfo(AccessibilityServiceInfo info) {}
+
+ public boolean findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
+ long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
+ Bundle arguments) {
+ return false;
+ }
+
+ public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,
+ long accessibilityNodeId, String text, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId) {
+ return false;
+ }
+
+ public boolean findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
+ long accessibilityNodeId, String viewId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId) {
+ return false;
+ }
+
+ public boolean findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ long threadId) {
+ return false;
+ }
+
+ public boolean focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ long threadId) {
+ return false;
+ }
+
+ public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
+ int action, Bundle arguments, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long threadId) {
+ return false;
+ }
+
+ public AccessibilityWindowInfo getWindow(int windowId) {
+ return null;
+ }
+
+ public List<AccessibilityWindowInfo> getWindows() {
+ return null;
+ }
+
+ public AccessibilityServiceInfo getServiceInfo() {
+ return null;
+ }
+
+ public boolean performGlobalAction(int action) {
+ return false;
+ }
+
+ public void disableSelf() {}
+
+ public void setOnKeyEventResult(boolean handled, int sequence) {}
+
+ public float getMagnificationScale() {
+ return 0.0f;
+ }
+
+ public float getMagnificationCenterX() {
+ return 0.0f;
+ }
+
+ public float getMagnificationCenterY() {
+ return 0.0f;
+ }
+
+ public Region getMagnificationRegion() {
+ return null;
+ }
+
+ public boolean resetMagnification(boolean animate) {
+ return false;
+ }
+
+ public boolean setMagnificationScaleAndCenter(float scale, float centerX, float centerY,
+ boolean animate) {
+ return false;
+ }
+
+ public void setMagnificationCallbackEnabled(boolean enabled) {}
+
+ public boolean setSoftKeyboardShowMode(int showMode) {
+ return false;
+ }
+
+ public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
+
+ public boolean isAccessibilityButtonAvailable() {
+ return false;
+ }
+
+ public void sendGesture(int sequence, ParceledListSlice gestureSteps) {}
+
+ public boolean isFingerprintGestureDetectionAvailable() {
+ return false;
+ }
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 3fe730f..2daf733 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -40,6 +40,7 @@
import java.io.ByteArrayInputStream;
import java.io.Closeable;
+import java.security.KeyPair;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
@@ -418,6 +419,18 @@
@Nullable @WorkerThread
public static PrivateKey getPrivateKey(@NonNull Context context, @NonNull String alias)
throws KeyChainException, InterruptedException {
+ KeyPair keyPair = getKeyPair(context, alias);
+ if (keyPair != null) {
+ return keyPair.getPrivate();
+ }
+
+ return null;
+ }
+
+ /** @hide */
+ @Nullable @WorkerThread
+ public static KeyPair getKeyPair(@NonNull Context context, @NonNull String alias)
+ throws KeyChainException, InterruptedException {
if (alias == null) {
throw new NullPointerException("alias == null");
}
@@ -439,7 +452,7 @@
return null;
} else {
try {
- return AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+ return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
KeyStore.getInstance(), keyId, KeyStore.UID_SELF);
} catch (RuntimeException | UnrecoverableKeyException e) {
throw new KeyChainException(e);
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 83c82af..94a05b2 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -427,7 +427,6 @@
ATRACE_CALL();
constexpr const int kMaxIterations = 20;
- *out_last_reference = 0u;
for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
in_out_value->data != 0u && iteration < kMaxIterations;
iteration++) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index a77c4b9..b033137 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -210,7 +210,7 @@
// are OR'd together with `in_out_flags`.
// `in_out_config` is populated with the configuration for which the resolved value was defined.
// `out_last_reference` is populated with the last reference ID before resolving to an actual
- // value.
+ // value. This is only initialized if the passed in `in_out_value` is a reference.
// Returns the cookie of the APK the resolved resource was defined in, or kInvalidCookie if
// it was not found.
ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index ab1a22e..567adfe 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -319,7 +319,7 @@
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
EXPECT_EQ(basic::R::integer::ref2, value.data);
- uint32_t last_ref;
+ uint32_t last_ref = 0u;
cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
ASSERT_NE(kInvalidCookie, cookie);
EXPECT_EQ(Res_value::TYPE_INT_DEC, value.dataType);
@@ -342,7 +342,7 @@
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
EXPECT_EQ(basic::R::array::integerArray1, value.data);
- uint32_t last_ref;
+ uint32_t last_ref = 0u;
cookie = assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_ref);
ASSERT_NE(kInvalidCookie, cookie);
EXPECT_EQ(Res_value::TYPE_REFERENCE, value.dataType);
@@ -350,6 +350,25 @@
EXPECT_EQ(basic::R::array::integerArray1, last_ref);
}
+TEST_F(AssetManager2Test, KeepLastReferenceIdUnmodifiedIfNoReferenceIsResolved) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({basic_assets_.get()});
+
+ ResTable_config selected_config;
+ memset(&selected_config, 0, sizeof(selected_config));
+
+ uint32_t flags = 0u;
+
+ // Create some kind of Res_value that is NOT a reference.
+ Res_value value;
+ value.dataType = Res_value::TYPE_STRING;
+ value.data = 0;
+
+ uint32_t last_ref = basic::R::string::test1;
+ EXPECT_EQ(1, assetmanager.ResolveReference(1, &value, &selected_config, &flags, &last_ref));
+ EXPECT_EQ(basic::R::string::test1, last_ref);
+}
+
static bool IsConfigurationPresent(const std::set<ResTable_config>& configurations,
const ResTable_config& configuration) {
return configurations.count(configuration) > 0;
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 46d4ae7..845acc0 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -110,7 +110,8 @@
// We only respect the nothingToDraw check when we are composing a layer. This
// ensures that we paint the layer even if it is not currently visible in the
// event that the properties change and it becomes visible.
- if (!renderNode->isRenderable() || (renderNode->nothingToDraw() && mComposeLayer)) {
+ if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) ||
+ (renderNode->nothingToDraw() && mComposeLayer)) {
return;
}
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index dff259f..7dd271f 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -350,6 +350,72 @@
EXPECT_EQ(3, canvas.getIndex());
}
+RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+ class ProjectionTestCanvas : public SkCanvas {
+ public:
+ ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
+ void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+ mDrawCounter++;
+ }
+
+ int getDrawCounter() { return mDrawCounter; }
+
+ private:
+ int mDrawCounter = 0;
+ };
+
+ auto receiverBackground = TestUtils::createSkiaNode(
+ 0, 0, 100, 100,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectionReceiver(true);
+ },
+ "B"); // a receiver with an empty display list
+
+ auto projectingRipple = TestUtils::createSkiaNode(
+ 0, 0, 100, 100,
+ [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ properties.setProjectBackwards(true);
+ properties.setClipToBounds(false);
+ SkPaint paint;
+ canvas.drawRect(0, 0, 100, 100, paint);
+ },
+ "P");
+ auto child = TestUtils::createSkiaNode(
+ 0, 0, 100, 100,
+ [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+ SkPaint paint;
+ canvas.drawRect(0, 0, 100, 100, paint);
+ canvas.drawRenderNode(projectingRipple.get());
+ },
+ "C");
+ auto parent = TestUtils::createSkiaNode(
+ 0, 0, 100, 100,
+ [&receiverBackground, &child](RenderProperties& properties,
+ SkiaRecordingCanvas& canvas) {
+ canvas.drawRenderNode(receiverBackground.get());
+ canvas.drawRenderNode(child.get());
+ },
+ "A");
+ ContextFactory contextFactory;
+ std::unique_ptr<CanvasContext> canvasContext(
+ CanvasContext::create(renderThread, false, parent.get(), &contextFactory));
+ TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+ DamageAccumulator damageAccumulator;
+ info.damageAccumulator = &damageAccumulator;
+ parent->prepareTree(info);
+
+ // parent(A) -> (receiverBackground, child)
+ // child(C) -> (rect[0, 0, 100, 100], projectingRipple)
+ // projectingRipple(P) -> (rect[0, 0, 100, 100]) -> projects backwards
+ // receiverBackground(B) -> (empty) -> projection receiver
+
+ // create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+ ProjectionTestCanvas canvas(100, 100);
+ RenderNodeDrawable drawable(parent.get(), &canvas, true);
+ canvas.drawDrawable(&drawable);
+ EXPECT_EQ(2, canvas.getDrawCounter());
+}
+
RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
/* R is backward projected on B and C is a layer.
A
diff --git a/location/tests/Android.mk b/location/tests/Android.mk
new file mode 100644
index 0000000..57848f3
--- /dev/null
+++ b/location/tests/Android.mk
@@ -0,0 +1,3 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 7678490..3c49b80 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -25,6 +25,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.util.ArrayMap;
import android.util.Log;
import android.view.Surface;
@@ -34,6 +35,8 @@
import java.io.RandomAccessFile;
import java.lang.ref.WeakReference;
+import com.android.internal.annotations.GuardedBy;
+
/**
* Used to record audio and video. The recording control is based on a
* simple state machine (see below).
@@ -76,7 +79,7 @@
* <a href="{@docRoot}guide/topics/media/audio-capture.html">Audio Capture</a> developer guide.</p>
* </div>
*/
-public class MediaRecorder
+public class MediaRecorder implements AudioRouting
{
static {
System.loadLibrary("media_jni");
@@ -1243,6 +1246,7 @@
private static final int MEDIA_RECORDER_TRACK_EVENT_INFO = 101;
private static final int MEDIA_RECORDER_TRACK_EVENT_LIST_END = 1000;
+ private static final int MEDIA_RECORDER_AUDIO_ROUTING_CHANGED = 10000;
@Override
public void handleMessage(Message msg) {
@@ -1265,6 +1269,16 @@
return;
+ case MEDIA_RECORDER_AUDIO_ROUTING_CHANGED:
+ AudioManager.resetAudioPortGeneration();
+ synchronized (mRoutingChangeListeners) {
+ for (NativeRoutingEventHandlerDelegate delegate
+ : mRoutingChangeListeners.values()) {
+ delegate.notifyClient();
+ }
+ }
+ return;
+
default:
Log.e(TAG, "Unknown message type " + msg.what);
return;
@@ -1272,6 +1286,155 @@
}
}
+ //--------------------------------------------------------------------------
+ // Explicit Routing
+ //--------------------
+ private AudioDeviceInfo mPreferredDevice = null;
+
+ /**
+ * Specifies an audio device (via an {@link AudioDeviceInfo} object) to route
+ * the input from this MediaRecorder.
+ * @param deviceInfo The {@link AudioDeviceInfo} specifying the audio source.
+ * If deviceInfo is null, default routing is restored.
+ * @return true if succesful, false if the specified {@link AudioDeviceInfo} is non-null and
+ * does not correspond to a valid audio input device.
+ */
+ @Override
+ public boolean setPreferredDevice(AudioDeviceInfo deviceInfo) {
+ if (deviceInfo != null && !deviceInfo.isSource()) {
+ return false;
+ }
+ int preferredDeviceId = deviceInfo != null ? deviceInfo.getId() : 0;
+ boolean status = native_setInputDevice(preferredDeviceId);
+ if (status == true) {
+ synchronized (this) {
+ mPreferredDevice = deviceInfo;
+ }
+ }
+ return status;
+ }
+
+ /**
+ * Returns the selected input device specified by {@link #setPreferredDevice}. Note that this
+ * is not guaranteed to correspond to the actual device being used for recording.
+ */
+ @Override
+ public AudioDeviceInfo getPreferredDevice() {
+ synchronized (this) {
+ return mPreferredDevice;
+ }
+ }
+
+ /**
+ * Returns an {@link AudioDeviceInfo} identifying the current routing of this MediaRecorder
+ * Note: The query is only valid if the MediaRecorder is currently recording.
+ * If the recorder is not recording, the returned device can be null or correspond to previously
+ * selected device when the recorder was last active.
+ */
+ @Override
+ public AudioDeviceInfo getRoutedDevice() {
+ int deviceId = native_getRoutedDeviceId();
+ if (deviceId == 0) {
+ return null;
+ }
+ AudioDeviceInfo[] devices =
+ AudioManager.getDevicesStatic(AudioManager.GET_DEVICES_INPUTS);
+ for (int i = 0; i < devices.length; i++) {
+ if (devices[i].getId() == deviceId) {
+ return devices[i];
+ }
+ }
+ return null;
+ }
+
+ /*
+ * Call BEFORE adding a routing callback handler or AFTER removing a routing callback handler.
+ */
+ private void enableNativeRoutingCallbacksLocked(boolean enabled) {
+ if (mRoutingChangeListeners.size() == 0) {
+ native_enableDeviceCallback(enabled);
+ }
+ }
+
+ /**
+ * The list of AudioRouting.OnRoutingChangedListener interfaces added (with
+ * {@link #addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, Handler)}
+ * by an app to receive (re)routing notifications.
+ */
+ @GuardedBy("mRoutingChangeListeners")
+ private ArrayMap<AudioRouting.OnRoutingChangedListener,
+ NativeRoutingEventHandlerDelegate> mRoutingChangeListeners = new ArrayMap<>();
+
+ /**
+ * Adds an {@link AudioRouting.OnRoutingChangedListener} to receive notifications of routing
+ * changes on this MediaRecorder.
+ * @param listener The {@link AudioRouting.OnRoutingChangedListener} interface to receive
+ * notifications of rerouting events.
+ * @param handler Specifies the {@link Handler} object for the thread on which to execute
+ * the callback. If <code>null</code>, the handler on the main looper will be used.
+ */
+ @Override
+ public void addOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener,
+ Handler handler) {
+ synchronized (mRoutingChangeListeners) {
+ if (listener != null && !mRoutingChangeListeners.containsKey(listener)) {
+ enableNativeRoutingCallbacksLocked(true);
+ mRoutingChangeListeners.put(
+ listener, new NativeRoutingEventHandlerDelegate(this, listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Removes an {@link AudioRouting.OnRoutingChangedListener} which has been previously added
+ * to receive rerouting notifications.
+ * @param listener The previously added {@link AudioRouting.OnRoutingChangedListener} interface
+ * to remove.
+ */
+ @Override
+ public void removeOnRoutingChangedListener(AudioRouting.OnRoutingChangedListener listener) {
+ synchronized (mRoutingChangeListeners) {
+ if (mRoutingChangeListeners.containsKey(listener)) {
+ mRoutingChangeListeners.remove(listener);
+ enableNativeRoutingCallbacksLocked(false);
+ }
+ }
+ }
+
+ /**
+ * Helper class to handle the forwarding of native events to the appropriate listener
+ * (potentially) handled in a different thread
+ */
+ private class NativeRoutingEventHandlerDelegate {
+ private MediaRecorder mMediaRecorder;
+ private AudioRouting.OnRoutingChangedListener mOnRoutingChangedListener;
+ private Handler mHandler;
+
+ NativeRoutingEventHandlerDelegate(final MediaRecorder mediaRecorder,
+ final AudioRouting.OnRoutingChangedListener listener, Handler handler) {
+ mMediaRecorder = mediaRecorder;
+ mOnRoutingChangedListener = listener;
+ mHandler = handler != null ? handler : mEventHandler;
+ }
+
+ void notifyClient() {
+ if (mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mOnRoutingChangedListener != null) {
+ mOnRoutingChangedListener.onRoutingChanged(mMediaRecorder);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private native final boolean native_setInputDevice(int deviceId);
+ private native final int native_getRoutedDeviceId();
+ private native final void native_enableDeviceCallback(boolean enabled);
+
/**
* Called from native code when an interesting event happens. This method
* just uses the EventHandler system to post the event back to the main app thread.
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 497684c..d2bc174 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -657,6 +657,56 @@
return mybundle;
}
+
+static jboolean
+android_media_MediaRecorder_setInputDevice(JNIEnv *env, jobject thiz, jint device_id)
+{
+ ALOGV("android_media_MediaRecorder_setInputDevice");
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return false;
+ }
+
+ if (process_media_recorder_call(env, mr->setInputDevice(device_id),
+ "java/lang/RuntimeException", "setInputDevice failed.")) {
+ return false;
+ }
+ return true;
+}
+
+static jint
+android_media_MediaRecorder_getRoutedDeviceId(JNIEnv *env, jobject thiz)
+{
+ ALOGV("android_media_MediaRecorder_getRoutedDeviceId");
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return AUDIO_PORT_HANDLE_NONE;
+ }
+
+ audio_port_handle_t deviceId;
+ process_media_recorder_call(env, mr->getRoutedDeviceId(&deviceId),
+ "java/lang/RuntimeException", "getRoutedDeviceId failed.");
+ return (jint) deviceId;
+}
+
+static void
+android_media_MediaRecorder_enableDeviceCallback(JNIEnv *env, jobject thiz, jboolean enabled)
+{
+ ALOGV("android_media_MediaRecorder_enableDeviceCallback %d", enabled);
+
+ sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
+ if (mr == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ process_media_recorder_call(env, mr->enableAudioDeviceCallback(enabled),
+ "java/lang/RuntimeException", "enableDeviceCallback failed.");
+}
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -689,6 +739,10 @@
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
{"native_getMetrics", "()Landroid/os/PersistableBundle;", (void *)android_media_MediaRecorder_native_getMetrics},
+
+ {"native_setInputDevice", "(I)Z", (void *)android_media_MediaRecorder_setInputDevice},
+ {"native_getRoutedDeviceId", "()I", (void *)android_media_MediaRecorder_getRoutedDeviceId},
+ {"native_enableDeviceCallback", "(Z)V", (void *)android_media_MediaRecorder_enableDeviceCallback},
};
// This function only registers the native methods, and is called from
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index 56a675a..15ea367 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -195,7 +195,12 @@
if(!handleObexPacket(packet)) {
return;
}
- if (!mHasBody) {
+ /* Don't Pre-Send continue when Remote requested for SRM
+ * Let the Application confirm.
+ */
+ if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+ + " not hasBody case: " + mHasBody);
+ if (!mHasBody && !mSrmEnabled) {
while ((!mGetOperation) && (!finalBitSet)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) {
@@ -204,8 +209,13 @@
}
}
}
-
- while ((!mGetOperation) && (!finalBitSet) && (mPrivateInput.available() == 0)) {
+ /* Don't Pre-Send continue when Remote requested for SRM
+ * Let the Application confirm.
+ */
+ if (V) Log.v(TAG, "Get App confirmation if SRM ENABLED case: " + mSrmEnabled
+ + " not finalPacket: " + finalBitSet + " not GETOp Case: " + mGetOperation);
+ while ((!mSrmEnabled) && (!mGetOperation) && (!finalBitSet)
+ && (mPrivateInput.available() == 0)) {
sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
if (mPrivateInput.available() > 0) {
break;
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 12d31cf..6a8d780 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -27,6 +27,7 @@
<timezone id="Atlantic/South_Georgia"></timezone>
<timezone id="Atlantic/Azores"></timezone>
<timezone id="Atlantic/Cape_Verde"></timezone>
+ <timezone id="Etc/UTC"></timezone>
<timezone id="Africa/Casablanca"></timezone>
<timezone id="Europe/London"></timezone>
<timezone id="Europe/Amsterdam"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 2186169..eb33842 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -105,7 +105,8 @@
}
}
return new UserIconDrawable(iconSize).setIconDrawable(
- UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake();
+ UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+ .bake();
}
/** Formats a double from 0.0..100.0 with an option to round **/
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
index ee7885d..0703330 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryManager.java
@@ -18,7 +18,6 @@
import android.content.ComponentName;
import android.content.Context;
import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -27,7 +26,6 @@
import com.android.settingslib.applications.InterestingConfigChanges;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -104,10 +102,10 @@
}
for (int i = 0; i < mCategories.size(); i++) {
DashboardCategory category = mCategories.get(i);
- for (int j = 0; j < category.tiles.size(); j++) {
- Tile tile = category.tiles.get(j);
+ for (int j = 0; j < category.getTilesCount(); j++) {
+ Tile tile = category.getTile(j);
if (tileBlacklist.contains(tile.intent.getComponent())) {
- category.tiles.remove(j--);
+ category.removeTile(j--);
}
}
}
@@ -181,7 +179,7 @@
newCategory = new DashboardCategory();
categoryByKeyMap.put(newCategoryKey, newCategory);
}
- newCategory.tiles.add(tile);
+ newCategory.addTile(tile);
}
}
}
@@ -198,7 +196,7 @@
synchronized void sortCategories(Context context,
Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
- sortCategoriesForExternalTiles(context, categoryEntry.getValue());
+ categoryEntry.getValue().sortTiles(context.getPackageName());
}
}
@@ -210,16 +208,16 @@
synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
final DashboardCategory category = categoryEntry.getValue();
- final int count = category.tiles.size();
+ final int count = category.getTilesCount();
final Set<ComponentName> components = new ArraySet<>();
for (int i = count - 1; i >= 0; i--) {
- final Tile tile = category.tiles.get(i);
+ final Tile tile = category.getTile(i);
if (tile.intent == null) {
continue;
}
final ComponentName tileComponent = tile.intent.getComponent();
if (components.contains(tileComponent)) {
- category.tiles.remove(i);
+ category.removeTile(i);
} else {
components.add(tileComponent);
}
@@ -234,28 +232,7 @@
*/
private synchronized void sortCategoriesForExternalTiles(Context context,
DashboardCategory dashboardCategory) {
- final String skipPackageName = context.getPackageName();
+ dashboardCategory.sortTiles(context.getPackageName());
- // Sort tiles based on [priority, package within priority]
- Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
- final String package1 = tile1.intent.getComponent().getPackageName();
- final String package2 = tile2.intent.getComponent().getPackageName();
- final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
- // First sort by priority
- final int priorityCompare = tile2.priority - tile1.priority;
- if (priorityCompare != 0) {
- return priorityCompare;
- }
- // Then sort by package name, skip package take precedence
- if (packageCompare != 0) {
- if (TextUtils.equals(package1, skipPackageName)) {
- return -1;
- }
- if (TextUtils.equals(package2, skipPackageName)) {
- return 1;
- }
- }
- return packageCompare;
- });
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
index f6f8168..a966e82 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,6 +16,8 @@
package com.android.settingslib.drawer;
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -23,6 +25,8 @@
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
public class DashboardCategory implements Parcelable {
@@ -48,39 +52,59 @@
/**
* List of the category's children
*/
- public List<Tile> tiles = new ArrayList<>();
+ private List<Tile> mTiles = new ArrayList<>();
+ DashboardCategory(DashboardCategory in) {
+ if (in != null) {
+ title = in.title;
+ key = in.key;
+ priority = in.priority;
+ for (Tile tile : in.mTiles) {
+ mTiles.add(tile);
+ }
+ }
+ }
public DashboardCategory() {
// Empty
}
+ /**
+ * Get a copy of the list of the category's children.
+ *
+ * Note: the returned list serves as a read-only list. If tiles needs to be added or removed
+ * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
+ */
+ public List<Tile> getTiles() {
+ return Collections.unmodifiableList(mTiles);
+ }
+
public void addTile(Tile tile) {
- tiles.add(tile);
+ mTiles.add(tile);
}
public void addTile(int n, Tile tile) {
- tiles.add(n, tile);
+ mTiles.add(n, tile);
}
public void removeTile(Tile tile) {
- tiles.remove(tile);
+ mTiles.remove(tile);
}
public void removeTile(int n) {
- tiles.remove(n);
+ mTiles.remove(n);
}
public int getTilesCount() {
- return tiles.size();
+ return mTiles.size();
}
public Tile getTile(int n) {
- return tiles.get(n);
+ return mTiles.get(n);
}
public boolean containsComponent(ComponentName component) {
- for (Tile tile : tiles) {
+ for (Tile tile : mTiles) {
if (TextUtils.equals(tile.intent.getComponent().getClassName(),
component.getClassName())) {
if (DEBUG) {
@@ -95,6 +119,40 @@
return false;
}
+ /**
+ * Sort priority value for tiles in this category.
+ */
+ public void sortTiles() {
+ Collections.sort(mTiles, TILE_COMPARATOR);
+ }
+
+ /**
+ * Sort priority value and package name for tiles in this category.
+ */
+ public void sortTiles(String skipPackageName) {
+ // Sort mTiles based on [priority, package within priority]
+ Collections.sort(mTiles, (tile1, tile2) -> {
+ final String package1 = tile1.intent.getComponent().getPackageName();
+ final String package2 = tile2.intent.getComponent().getPackageName();
+ final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
+ // First sort by priority
+ final int priorityCompare = tile2.priority - tile1.priority;
+ if (priorityCompare != 0) {
+ return priorityCompare;
+ }
+ // Then sort by package name, skip package take precedence
+ if (packageCompare != 0) {
+ if (TextUtils.equals(package1, skipPackageName)) {
+ return -1;
+ }
+ if (TextUtils.equals(package2, skipPackageName)) {
+ return 1;
+ }
+ }
+ return packageCompare;
+ });
+ }
+
@Override
public int describeContents() {
return 0;
@@ -106,11 +164,11 @@
dest.writeString(key);
dest.writeInt(priority);
- final int count = tiles.size();
+ final int count = mTiles.size();
dest.writeInt(count);
for (int n = 0; n < count; n++) {
- Tile tile = tiles.get(n);
+ Tile tile = mTiles.get(n);
tile.writeToParcel(dest, flags);
}
}
@@ -124,7 +182,7 @@
for (int n = 0; n < count; n++) {
Tile tile = Tile.CREATOR.createFromParcel(in);
- tiles.add(tile);
+ mTiles.add(tile);
}
}
@@ -141,4 +199,13 @@
return new DashboardCategory[size];
}
};
+
+ public static final Comparator<Tile> TILE_COMPARATOR =
+ new Comparator<Tile>() {
+ @Override
+ public int compare(Tile lhs, Tile rhs) {
+ return rhs.priority - lhs.priority;
+ }
+ };
+
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 038dcf8..e986e0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -253,7 +253,7 @@
}
ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
for (DashboardCategory category : categories) {
- Collections.sort(category.tiles, TILE_COMPARATOR);
+ category.sortTiles();
}
Collections.sort(categories, CATEGORY_COMPARATOR);
if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
@@ -595,14 +595,6 @@
return pathSegments.get(0);
}
- public static final Comparator<Tile> TILE_COMPARATOR =
- new Comparator<Tile>() {
- @Override
- public int compare(Tile lhs, Tile rhs) {
- return rhs.priority - lhs.priority;
- }
- };
-
private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
new Comparator<DashboardCategory>() {
@Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
index b27d823..8a09df2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
@@ -64,7 +64,8 @@
if (um.getUserIcon(userId) != null) {
icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
} else {
- icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+ icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), userId, /* light= */ false);
}
}
this.mIcon = encircle(context, icon);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
index d7eae5f..f099c90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryManagerTest.java
@@ -95,8 +95,9 @@
mCategoryManager.backwardCompatCleanupForCategory(mTileByComponentCache, mCategoryByKeyMap);
assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
- assertThat(mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT).tiles.size()).isEqualTo(1);
- assertThat(mCategoryByKeyMap.get(oldCategory).tiles.size()).isEqualTo(1);
+ assertThat(
+ mCategoryByKeyMap.get(CategoryKey.CATEGORY_ACCOUNT).getTilesCount()).isEqualTo(1);
+ assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
}
@Test
@@ -114,9 +115,10 @@
// Added 1 more category to category map.
assertThat(mCategoryByKeyMap.size()).isEqualTo(2);
// The new category map has CATEGORY_NETWORK type now, which contains 1 tile.
- assertThat(mCategoryByKeyMap.get(CategoryKey.CATEGORY_NETWORK).tiles.size()).isEqualTo(1);
+ assertThat(
+ mCategoryByKeyMap.get(CategoryKey.CATEGORY_NETWORK).getTilesCount()).isEqualTo(1);
// Old category still exists.
- assertThat(mCategoryByKeyMap.get(oldCategory).tiles.size()).isEqualTo(1);
+ assertThat(mCategoryByKeyMap.get(oldCategory).getTilesCount()).isEqualTo(1);
}
@Test
@@ -136,9 +138,9 @@
tile3.intent =
new Intent().setComponent(new ComponentName(testPackage, "class3"));
tile3.priority = 200;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
// Sort their priorities
@@ -146,9 +148,9 @@
mCategoryByKeyMap);
// Verify they are now sorted.
- assertThat(category.tiles.get(0)).isSameAs(tile3);
- assertThat(category.tiles.get(1)).isSameAs(tile1);
- assertThat(category.tiles.get(2)).isSameAs(tile2);
+ assertThat(category.getTile(0)).isSameAs(tile3);
+ assertThat(category.getTile(1)).isSameAs(tile1);
+ assertThat(category.getTile(2)).isSameAs(tile2);
}
@Test
@@ -169,9 +171,9 @@
tile3.intent =
new Intent().setComponent(new ComponentName(testPackage1, "class3"));
tile3.priority = 50;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
// Sort their priorities
@@ -179,9 +181,9 @@
mCategoryByKeyMap);
// Verify they are now sorted.
- assertThat(category.tiles.get(0)).isSameAs(tile2);
- assertThat(category.tiles.get(1)).isSameAs(tile1);
- assertThat(category.tiles.get(2)).isSameAs(tile3);
+ assertThat(category.getTile(0)).isSameAs(tile2);
+ assertThat(category.getTile(1)).isSameAs(tile1);
+ assertThat(category.getTile(2)).isSameAs(tile3);
}
@Test
@@ -202,9 +204,9 @@
tile3.intent =
new Intent().setComponent(new ComponentName(testPackage, "class3"));
tile3.priority = 50;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
// Sort their priorities
@@ -212,9 +214,9 @@
mCategoryByKeyMap);
// Verify the sorting order is not changed
- assertThat(category.tiles.get(0)).isSameAs(tile1);
- assertThat(category.tiles.get(1)).isSameAs(tile2);
- assertThat(category.tiles.get(2)).isSameAs(tile3);
+ assertThat(category.getTile(0)).isSameAs(tile1);
+ assertThat(category.getTile(1)).isSameAs(tile2);
+ assertThat(category.getTile(2)).isSameAs(tile3);
}
@Test
@@ -236,10 +238,10 @@
final Tile tile4 = new Tile();
tile4.intent = new Intent().setComponent(new ComponentName(testPackage, "class3"));
tile4.priority = -1;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
- category.tiles.add(tile4);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
+ category.addTile(tile4);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
// Sort their priorities
@@ -247,10 +249,10 @@
mCategoryByKeyMap);
// Verify the sorting order is not changed
- assertThat(category.tiles.get(0)).isSameAs(tile1);
- assertThat(category.tiles.get(1)).isSameAs(tile2);
- assertThat(category.tiles.get(2)).isSameAs(tile3);
- assertThat(category.tiles.get(3)).isSameAs(tile4);
+ assertThat(category.getTile(0)).isSameAs(tile1);
+ assertThat(category.getTile(1)).isSameAs(tile2);
+ assertThat(category.getTile(2)).isSameAs(tile3);
+ assertThat(category.getTile(3)).isSameAs(tile4);
}
@Test
@@ -270,9 +272,9 @@
final Tile tile3 = new Tile();
tile3.intent = new Intent().setComponent(new ComponentName(testPackage3, "class3"));
tile3.priority = 1;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
// Sort their priorities
@@ -280,9 +282,9 @@
mCategoryByKeyMap);
// Verify the sorting order is internal first, follow by package name ordering
- assertThat(category.tiles.get(0)).isSameAs(tile2);
- assertThat(category.tiles.get(1)).isSameAs(tile3);
- assertThat(category.tiles.get(2)).isSameAs(tile1);
+ assertThat(category.getTile(0)).isSameAs(tile2);
+ assertThat(category.getTile(1)).isSameAs(tile3);
+ assertThat(category.getTile(2)).isSameAs(tile1);
}
@Test
@@ -303,14 +305,14 @@
tile3.intent =
new Intent().setComponent(new ComponentName(testPackage, "class3"));
tile3.priority = 50;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
- assertThat(category.tiles.size()).isEqualTo(3);
+ assertThat(category.getTilesCount()).isEqualTo(3);
}
@Test
@@ -331,13 +333,13 @@
tile3.intent =
new Intent().setComponent(new ComponentName(testPackage, "class1"));
tile3.priority = 50;
- category.tiles.add(tile1);
- category.tiles.add(tile2);
- category.tiles.add(tile3);
+ category.addTile(tile1);
+ category.addTile(tile2);
+ category.addTile(tile3);
mCategoryByKeyMap.put(CategoryKey.CATEGORY_HOMEPAGE, category);
mCategoryManager.filterDuplicateTiles(mCategoryByKeyMap);
- assertThat(category.tiles.size()).isEqualTo(1);
+ assertThat(category.getTilesCount()).isEqualTo(1);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
index dad3a28..a395a4a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -216,7 +216,7 @@
List<DashboardCategory> categoryList = TileUtils.getCategories(
mContext, cache, false /* categoryDefinedInManifest */, testAction,
TileUtils.SETTING_PKG);
- assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory);
+ assertThat(categoryList.get(0).getTile(0).category).isEqualTo(testCategory);
}
@Test
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 258c96c..7fb6ede 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@
import android.os.UserManagerInternal;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -343,7 +344,8 @@
}
case Settings.CALL_METHOD_GET_SECURE: {
- Setting setting = getSecureSetting(name, requestingUserId);
+ Setting setting = getSecureSetting(name, requestingUserId,
+ /*enableOverride=*/ true);
return packageValueForCallResult(setting, isTrackingGeneration(args));
}
@@ -1073,6 +1075,10 @@
}
private Setting getSecureSetting(String name, int requestingUserId) {
+ return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
+ }
+
+ private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
if (DEBUG) {
Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
}
@@ -1102,6 +1108,14 @@
return getSsaidSettingLocked(callingPkg, owningUserId);
}
}
+ if (enableOverride) {
+ if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+ final Setting overridden = getLocationProvidersAllowedSetting(owningUserId);
+ if (overridden != null) {
+ return overridden;
+ }
+ }
+ }
// Not the SSAID; do a straight lookup
synchronized (mLock) {
@@ -1190,6 +1204,35 @@
return null;
}
+ private Setting getLocationProvidersAllowedSetting(int owningUserId) {
+ synchronized (mLock) {
+ final Setting setting = getGlobalSetting(
+ Global.LOCATION_GLOBAL_KILL_SWITCH);
+ if (!"1".equals(setting.getValue())) {
+ return null;
+ }
+ // Global kill-switch is enabled. Return an empty value.
+ final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_SECURE, owningUserId);
+ return settingsState.new Setting(
+ Secure.LOCATION_PROVIDERS_ALLOWED,
+ "", // value
+ "", // tag
+ "", // default value
+ "", // package name
+ false, // from system
+ "0" // id
+ ) {
+ @Override
+ public boolean update(String value, boolean setDefault, String packageName,
+ String tag, boolean forceNonSystemPackage) {
+ Slog.wtf(LOG_TAG, "update shoudln't be called on this instance.");
+ return false;
+ }
+ };
+ }
+ }
+
private boolean insertSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, boolean forceNotify) {
if (DEBUG) {
@@ -2780,6 +2823,12 @@
}
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+
+ // When the global kill switch is updated, send the change notification for
+ // the location setting.
+ if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) {
+ notifyLocationChangeForRunningUsers();
+ }
}
private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
@@ -2799,6 +2848,24 @@
}
}
+ private void notifyLocationChangeForRunningUsers() {
+ final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+
+ for (int i = 0; i < users.size(); i++) {
+ final int userId = users.get(i).id;
+
+ if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
+ continue;
+ }
+
+ final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+ final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
+
+ mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
+ userId, 0, uri).sendToTarget();
+ }
+ }
+
private boolean isGlobalSettingsKey(int key) {
return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
}
@@ -2885,7 +2952,7 @@
} catch (SecurityException e) {
Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e);
}
- if (DEBUG) {
+ if (DEBUG || true) {
Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
}
} break;
diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml
index 6df1657..2e35d86 100644
--- a/packages/SystemUI/res/layout/notification_info.xml
+++ b/packages/SystemUI/res/layout/notification_info.xml
@@ -126,6 +126,7 @@
android:layout_height="48dp"
android:orientation="horizontal"
android:gravity="end"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
android:layout_marginBottom="8dp" >
<TextView
android:id="@+id/more_settings"
@@ -139,7 +140,6 @@
android:text="@string/notification_done"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginEnd="8dp"
style="@style/TextAppearance.NotificationInfo.Button"/>
</LinearLayout>
</com.android.systemui.statusbar.NotificationInfo>
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index 3209f27..7476abd 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -35,7 +35,7 @@
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:paddingStart="16dp"
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
android:textColor="#DD000000"
android:paddingEnd="4dp"/>
@@ -53,7 +53,7 @@
style="@style/TextAppearance.NotificationInfo.Button"
android:layout_width="wrap_content"
android:layout_height="36dp"
- android:layout_marginEnd="8dp"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:text="@string/snooze_undo" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index cf79238..8e065d1 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -250,10 +250,7 @@
<string name="doze_brightness_sensor_type" translatable="false"></string>
<!-- Doze: pulse parameter - how long does it take to fade in? -->
- <integer name="doze_pulse_duration_in">900</integer>
-
- <!-- Doze: pulse parameter - how long does it take to fade in after a pickup? -->
- <integer name="doze_pulse_duration_in_pickup">130</integer>
+ <integer name="doze_pulse_duration_in">130</integer>
<!-- Doze: pulse parameter - once faded in, how long does it stay visible? -->
<integer name="doze_pulse_duration_visible">6000</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f0bad2a..0715d49 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -72,16 +72,19 @@
<dimen name="status_bar_connected_device_bt_indicator_size">17dp</dimen>
<!-- Height of a small notification in the status bar-->
- <dimen name="notification_min_height">92dp</dimen>
+ <dimen name="notification_min_height">100dp</dimen>
<!-- Increased height of a small notification in the status bar -->
- <dimen name="notification_min_height_increased">132dp</dimen>
+ <dimen name="notification_min_height_increased">140dp</dimen>
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>
+ <!-- Height of a small notification in the status bar which was used before android P -->
+ <dimen name="notification_min_height_before_p">92dp</dimen>
+
<!-- Height of a large notification in the status bar -->
- <dimen name="notification_max_height">284dp</dimen>
+ <dimen name="notification_max_height">292dp</dimen>
<!-- Height of an ambient notification on ambient display -->
<dimen name="notification_ambient_height">400dp</dimen>
@@ -89,12 +92,21 @@
<!-- Height of a heads up notification in the status bar for legacy custom views -->
<dimen name="notification_max_heads_up_height_legacy">128dp</dimen>
+ <!-- Height of a heads up notification in the status bar for custom views before andoid P -->
+ <dimen name="notification_max_heads_up_height_before_p">148dp</dimen>
+
<!-- Height of a heads up notification in the status bar -->
- <dimen name="notification_max_heads_up_height">148dp</dimen>
+ <dimen name="notification_max_heads_up_height">156dp</dimen>
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height_increased">188dp</dimen>
+ <!-- Side padding on the lockscreen on the side of notifications -->
+ <dimen name="notification_lockscreen_side_paddings">8dp</dimen>
+
+ <!-- Additional side padding for custom content if the app doesn't target P yet -->
+ <dimen name="notification_content_custom_view_side_padding">@dimen/notification_lockscreen_side_paddings</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>
@@ -109,7 +121,7 @@
<dimen name="notification_min_interaction_height">40dp</dimen>
<!-- the padding of the shelf icon container -->
- <dimen name="shelf_icon_container_padding">13dp</dimen>
+ <dimen name="shelf_icon_container_padding">21dp</dimen>
<!-- The padding of a notification icon on top to the start of the notification. Used for custom
views where the distance can't be measured -->
@@ -213,6 +225,9 @@
etc. -->
<dimen name="qs_footer_height">48dp</dimen>
+ <!-- The padding between the notifications and the quick settings container -->
+ <dimen name="qs_notification_keyguard_padding">8dp</dimen>
+
<!-- Height of the status bar header bar when expanded -->
<dimen name="status_bar_header_height_expanded">124dp</dimen>
@@ -348,10 +363,6 @@
<!-- Default distance from each snap target that GlowPadView considers a "hit" -->
<dimen name="glowpadview_inner_radius">15dip</dimen>
- <!-- bottom_stack_peek_amount + notification_min_height
- + notification_collapse_second_card_padding -->
- <dimen name="min_stack_height">104dp</dimen>
-
<!-- Z distance between notifications if they are in the stack -->
<dimen name="z_distance_between_notifications">0.5dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index cfd95b4..dd31365 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,6 +47,7 @@
<item type="id" name="qs_icon_tag"/>
<item type="id" name="qs_slash_tag"/>
<item type="id" name="scrim"/>
+ <item type="id" name="scrim_blanking"/>
<item type="id" name="scrim_target"/>
<item type="id" name="scrim_alpha_start"/>
<item type="id" name="scrim_alpha_end"/>
@@ -77,6 +78,18 @@
<item type="id" name="action_move_tl_30" />
<item type="id" name="action_move_rb_full" />
+ <item type="id" name="bottom_roundess_animator_tag"/>
+ <item type="id" name="bottom_roundess_animator_start_tag"/>
+ <item type="id" name="bottom_roundess_animator_end_tag"/>
+
+ <item type="id" name="top_roundess_animator_tag"/>
+ <item type="id" name="top_roundess_animator_start_tag"/>
+ <item type="id" name="top_roundess_animator_end_tag"/>
+
+ <item type="id" name="side_padding_animator_tag"/>
+ <item type="id" name="side_padding_animator_start_tag"/>
+ <item type="id" name="side_padding_animator_end_tag"/>
+
<!-- Accessibility actions for the notification menu -->
<item type="id" name="action_snooze_undo"/>
<item type="id" name="action_snooze_shorter"/>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 12f75bb..0219db3 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -307,8 +307,9 @@
void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
int removedCount, int addedCount) {
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
+ if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
+ && (isFocused() || isSelected() && isShown())) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
event.setFromIndex(fromIndex);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
public ScrimController createScrimController(LightBarController lightBarController,
ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
- LockscreenWallpaper lockscreenWallpaper,
- Consumer<Boolean> scrimVisibleListener) {
+ LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
- scrimVisibleListener);
+ scrimVisibleListener, dozeParameters);
}
public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
boolean isBlockingDoze();
void startPendingIntentDismissingKeyguard(PendingIntent intent);
- void abortPulsing();
void extendPulse();
void setAnimateWakeup(boolean animateWakeup);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b99e76a..c92acd0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2028,12 +2028,9 @@
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container,
- ScrimController scrimController,
- FingerprintUnlockController fingerprintUnlockController) {
+ ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
- scrimController, fingerprintUnlockController,
- mDismissCallbackRegistry);
+ fingerprintUnlockController, mDismissCallbackRegistry);
return mStatusBarKeyguardViewManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0f..51175d1 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@
}
case MotionEvent.ACTION_HOVER_ENTER:
case MotionEvent.ACTION_HOVER_MOVE: {
- if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+ && !mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@
break;
}
case MotionEvent.ACTION_HOVER_EXIT: {
- if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+ if (mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+ && mSendingHoverAccessibilityEvents) {
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
event.setImportantForAccessibility(true);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index eef43d2..a984680 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@
@Override
public void onTaskStackChanged() {
if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
if (getState() != STATE_NO_PIP) {
boolean hasPip = false;
@@ -662,9 +660,7 @@
@Override
public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
if (DEBUG) Log.d(TAG, "onActivityPinned()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
StackInfo stackInfo = getPinnedStackInfo();
if (stackInfo == null) {
Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@
@Override
public void onPinnedActivityRestartAttempt(boolean clearedTask) {
if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
// If PIPed activity is launched again by Launcher or intent, make it fullscreen.
movePipToFullscreen();
}
@@ -700,9 +694,7 @@
@Override
public void onPinnedStackAnimationEnded() {
if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
- if (!checkCurrentUserId(mContext, DEBUG)) {
- return;
- }
+
switch (getState()) {
case STATE_PIP_MENU:
showPipMenu();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index cf3cae5..2d3080b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -68,7 +68,6 @@
import com.android.internal.app.AssistUtils;
import com.android.internal.os.BackgroundThread;
import com.android.systemui.Dependency;
-import com.android.systemui.R;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImpl;
@@ -372,7 +371,7 @@
if (mIam == null) return false;
try {
- return mIam.isInLockTaskMode();
+ return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
} catch (RemoteException e) {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015..ff0357a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@
mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
}
+ protected void updateBackgroundClipping() {
+ mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+ mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+ }
+
protected boolean shouldHideBackground() {
return mDark;
}
@@ -901,12 +906,45 @@
contentView.setAlpha(contentAlpha);
}
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+ getCurrentBackgroundRadiusBottom());
+ }
+
+ protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+ mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+ mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+ }
+
+ @Override
+ protected void setBackgroundTop(int backgroundTop) {
+ mBackgroundDimmed.setBackgroundTop(backgroundTop);
+ mBackgroundNormal.setBackgroundTop(backgroundTop);
+ }
+
protected abstract View getContentView();
public int calculateBgColor() {
return calculateBgColor(true /* withTint */, true /* withOverRide */);
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ super.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+ mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+ }
+
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+ return true;
+ }
+ return super.childNeedsClipping(child);
+ }
+
/**
* @param withTint should a possible tint be factored in?
* @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 6349275..8e1b104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@
private static final int MSG_TOGGLE_PANEL = 35 << MSG_SHIFT;
private static final int MSG_SHOW_SHUTDOWN_UI = 36 << MSG_SHIFT;
private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR = 37 << MSG_SHIFT;
+ private static final int MSG_ROTATION_PROPOSAL = 38 << MSG_SHIFT;
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@
default void handleSystemKey(int arg1) { }
default void handleShowGlobalActionsMenu() { }
default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+ default void onRotationProposal(int rotation) { }
}
@VisibleForTesting
@@ -458,6 +461,15 @@
}
}
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ synchronized (mLock) {
+ mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+ mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+ null).sendToTarget();
+ }
+ }
+
private final class H extends Handler {
private H(Looper l) {
super(l);
@@ -654,6 +666,11 @@
mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
}
break;
+ case MSG_ROTATION_PROPOSAL:
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onRotationProposal(msg.arg1);
+ }
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950e..23d9cae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
import android.content.Context;
import android.content.res.Resources;
import android.content.res.Configuration;
+import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.NotificationGuts.GutsContent;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationInflater;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -102,7 +102,9 @@
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mNotificationMinHeightLegacy;
+ private int mNotificationMinHeightBeforeP;
private int mMaxHeadsUpHeightLegacy;
+ private int mMaxHeadsUpHeightBeforeP;
private int mMaxHeadsUpHeight;
private int mMaxHeadsUpHeightIncreased;
private int mNotificationMinHeight;
@@ -435,9 +437,10 @@
boolean customView = layout.getContractedChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+ boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
int minHeight;
- if (customView && beforeN && !mIsSummaryWithChildren) {
- minHeight = mNotificationMinHeightLegacy;
+ if (customView && beforeP && !mIsSummaryWithChildren) {
+ minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -447,8 +450,8 @@
layout.getHeadsUpChild().getId()
!= com.android.internal.R.id.status_bar_latest_event_content;
int headsUpheight;
- if (headsUpCustom && beforeN) {
- headsUpheight = mMaxHeadsUpHeightLegacy;
+ if (headsUpCustom && beforeP) {
+ headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
} else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
headsUpheight = mMaxHeadsUpHeightIncreased;
} else {
@@ -535,6 +538,7 @@
}
onChildrenCountChanged();
row.setIsChildInGroup(false, null);
+ row.setBottomRoundness(0.0f, false /* animate */);
}
@Override
@@ -563,6 +567,7 @@
mNotificationParent.updateBackgroundForGroupState();
}
updateIconVisibilities();
+ updateBackgroundClipping();
}
@Override
@@ -916,6 +921,7 @@
addView(mMenuRow.getMenuView(), menuIndex);
}
for (NotificationContentView l : mLayouts) {
+ l.initView();
l.reInflateViews();
}
mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@
mKeepInParent = keepInParent;
}
+ @Override
public boolean isRemoved() {
return mRemoved;
}
@@ -1264,6 +1271,8 @@
private void initDimens() {
mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_legacy);
+ mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_min_height_before_p);
mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height);
mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@
R.dimen.notification_ambient_height);
mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height_legacy);
+ mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+ R.dimen.notification_max_heads_up_height_before_p);
mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_heads_up_height);
mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@
mPrivateLayout.updateExpandButtons(isExpandable());
updateChildrenHeaderAppearance();
updateChildrenVisibility();
+ applyChildrenRoundness();
}
public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@
}
}
+ @Override
+ protected boolean childNeedsClipping(View child) {
+ if (child instanceof NotificationContentView) {
+ NotificationContentView contentView = (NotificationContentView) child;
+ if (isClippingNeeded()) {
+ return true;
+ } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+ return true;
+ }
+ } else if (child == mChildrenContainer) {
+ if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+ && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+ return true;
+ }
+ } else if (child instanceof NotificationGuts) {
+ return !hasNoRoundingAndNoPadding();
+ }
+ return super.childNeedsClipping(child);
+ }
+
+ @Override
+ protected void applyRoundness() {
+ super.applyRoundness();
+ applyChildrenRoundness();
+ }
+
+ private void applyChildrenRoundness() {
+ if (mIsSummaryWithChildren) {
+ mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+ }
+ }
+
+ @Override
+ public Path getCustomClipPath(View child) {
+ if (child instanceof NotificationGuts) {
+ return getClipPath(true, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+ if (child instanceof NotificationChildrenContainer) {
+ return getClipPath(false, /* ignoreTranslation */
+ true /* clipRoundedToBottom */);
+ }
+ return super.getCustomClipPath(child);
+ }
+
+ private boolean hasNoRoundingAndNoPadding() {
+ return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+ && getCurrentTopRoundness() == 0.0f;
+ }
+
public boolean isShowingAmbient() {
return mShowAmbient;
}
@@ -2344,6 +2406,20 @@
}
}
+ @Override
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ if (mIsSummaryWithChildren) {
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getNotificationChildren();
+ int size = notificationChildren.size();
+ for (int i = 0; i < size; i++) {
+ ExpandableNotificationRow row = notificationChildren.get(i);
+ row.setCurrentSidePaddings(currentSidePaddings);
+ }
+ }
+ super.setCurrentSidePaddings(currentSidePaddings);
+ }
+
public static class NotificationViewState extends ExpandableViewState {
private final StackScrollState mOverallState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 2556890..b3d6e32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Canvas;
import android.graphics.Outline;
+import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
* Like {@link ExpandableView}, but setting an outline for the height and clipping.
*/
public abstract class ExpandableOutlineView extends ExpandableView {
+ private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+ "topRoundness",
+ ExpandableOutlineView::setTopRoundnessInternal,
+ ExpandableOutlineView::getCurrentTopRoundness,
+ R.id.top_roundess_animator_tag,
+ R.id.top_roundess_animator_end_tag,
+ R.id.top_roundess_animator_start_tag);
+ private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+ "bottomRoundness",
+ ExpandableOutlineView::setBottomRoundnessInternal,
+ ExpandableOutlineView::getCurrentBottomRoundness,
+ R.id.bottom_roundess_animator_tag,
+ R.id.bottom_roundess_animator_end_tag,
+ R.id.bottom_roundess_animator_start_tag);
+ private static final AnimationProperties ROUNDNESS_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ private static final Path EMPTY_PATH = new Path();
+
private final Rect mOutlineRect = new Rect();
private boolean mCustomOutline;
private float mOutlineAlpha = -1f;
private float mOutlineRadius;
+ private boolean mAlwaysRoundBothCorners;
+ private Path mTmpPath = new Path();
+ private Path mTmpPath2 = new Path();
+ private float mCurrentBottomRoundness;
+ private float mCurrentTopRoundness;
+ private float mBottomRoundness;
+ private float mTopRoundness;
+ private int mBackgroundTop;
+ protected int mCurrentSidePaddings;
/**
* {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@
private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
- if (!mCustomOutline) {
- outline.setRoundRect(translation,
- mClipTopAmount,
- getWidth() + translation,
- Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
- mOutlineRadius);
- } else {
- outline.setRoundRect(mOutlineRect, mOutlineRadius);
+ Path clipPath = getClipPath();
+ if (clipPath != null && clipPath.isConvex()) {
+ // The path might not be convex in border cases where the view is small and clipped
+ outline.setConvexPath(clipPath);
}
outline.setAlpha(mOutlineAlpha);
}
};
+ private Path getClipPath() {
+ return getClipPath(false, /* ignoreTranslation */
+ false /* clipRoundedToBottom */);
+ }
+
+ protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+ int left;
+ int top;
+ int right;
+ int bottom;
+ int height;
+ Path intersectPath = null;
+ if (!mCustomOutline) {
+ int translation = mShouldTranslateContents && !ignoreTranslation
+ ? (int) getTranslation() : 0;
+ left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+ top = mClipTopAmount + mBackgroundTop;
+ right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+ bottom = Math.max(getActualHeight(), top);
+ int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+ if (bottom != intersectBottom) {
+ if (clipRoundedToBottom) {
+ bottom = intersectBottom;
+ } else {
+ getRoundedRectPath(left, top, right,
+ intersectBottom, 0.0f,
+ 0.0f, mTmpPath2);
+ intersectPath = mTmpPath2;
+ }
+ }
+ } else {
+ left = mOutlineRect.left;
+ top = mOutlineRect.top;
+ right = mOutlineRect.right;
+ bottom = mOutlineRect.bottom;
+ left = Math.max(mCurrentSidePaddings, left);
+ right = Math.min(getWidth() - mCurrentSidePaddings, right);
+ }
+ height = bottom - top;
+ if (height == 0) {
+ return EMPTY_PATH;
+ }
+ float topRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+ float bottomRoundness = mAlwaysRoundBothCorners
+ ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+ if (topRoundness + bottomRoundness > height) {
+ float overShoot = topRoundness + bottomRoundness - height;
+ topRoundness -= overShoot * mCurrentTopRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ bottomRoundness -= overShoot * mCurrentBottomRoundness
+ / (mCurrentTopRoundness + mCurrentBottomRoundness);
+ }
+ getRoundedRectPath(left, top, right, bottom, topRoundness,
+ bottomRoundness, mTmpPath);
+ Path roundedRectPath = mTmpPath;
+ if (intersectPath != null) {
+ roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+ }
+ return roundedRectPath;
+ }
+
+ protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness) {
+ getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+ mTmpPath);
+ return mTmpPath;
+ }
+
+ private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+ float bottomRoundness, Path outPath) {
+ outPath.reset();
+ int width = right - left;
+ float topRoundnessX = topRoundness;
+ float bottomRoundnessX = bottomRoundness;
+ topRoundnessX = Math.min(width / 2, topRoundnessX);
+ bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+ if (topRoundness > 0.0f) {
+ outPath.moveTo(left, top + topRoundness);
+ outPath.quadTo(left, top, left + topRoundnessX, top);
+ outPath.lineTo(right - topRoundnessX, top);
+ outPath.quadTo(right, top, right, top + topRoundness);
+ } else {
+ outPath.moveTo(left, top);
+ outPath.lineTo(right, top);
+ }
+ if (bottomRoundness > 0.0f) {
+ outPath.lineTo(right, bottom - bottomRoundness);
+ outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+ outPath.lineTo(left + bottomRoundnessX, bottom);
+ outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+ } else {
+ outPath.lineTo(right, bottom);
+ outPath.lineTo(left, bottom);
+ }
+ outPath.close();
+ }
+
public ExpandableOutlineView(Context context, AttributeSet attrs) {
super(context, attrs);
setOutlineProvider(mProvider);
initDimens();
}
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ canvas.save();
+ if (childNeedsClipping(child)) {
+ Path clipPath = getCustomClipPath(child);
+ if (clipPath == null) {
+ clipPath = getClipPath();
+ }
+ if (clipPath != null) {
+ canvas.clipPath(clipPath);
+ }
+ }
+ boolean result = super.drawChild(canvas, child, drawingTime);
+ canvas.restore();
+ return result;
+ }
+
+ protected boolean childNeedsClipping(View child) {
+ return false;
+ }
+
+ protected boolean isClippingNeeded() {
+ return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+ }
+
private void initDimens() {
Resources res = getResources();
mShouldTranslateContents =
res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
- setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+ mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+ if (!mAlwaysRoundBothCorners) {
+ mOutlineRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+ setClipToOutline(mAlwaysRoundBothCorners);
+ }
+
+ public void setTopRoundness(float topRoundness, boolean animate) {
+ if (mTopRoundness != topRoundness) {
+ mTopRoundness = topRoundness;
+ PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void applyRoundness() {
+ invalidateOutline();
+ invalidate();
+ }
+
+ public float getCurrentBackgroundRadiusTop() {
+ return mCurrentTopRoundness * mOutlineRadius;
+ }
+
+ public float getCurrentTopRoundness() {
+ return mCurrentTopRoundness;
+ }
+
+ public float getCurrentBottomRoundness() {
+ return mCurrentBottomRoundness;
+ }
+
+ protected float getCurrentBackgroundRadiusBottom() {
+ return mCurrentBottomRoundness * mOutlineRadius;
+ }
+
+ public void setBottomRoundness(float bottomRoundness, boolean animate) {
+ if (mBottomRoundness != bottomRoundness) {
+ mBottomRoundness = bottomRoundness;
+ PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+ ROUNDNESS_PROPERTIES, animate);
+ }
+ }
+
+ protected void setBackgroundTop(int backgroundTop) {
+ if (mBackgroundTop != backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidateOutline();
+ }
+ }
+
+ private void setTopRoundnessInternal(float topRoundness) {
+ mCurrentTopRoundness = topRoundness;
+ applyRoundness();
+ }
+
+ private void setBottomRoundnessInternal(float bottomRoundness) {
+ mCurrentBottomRoundness = bottomRoundness;
+ applyRoundness();
}
public void onDensityOrFontScaleChanged() {
initDimens();
- invalidateOutline();
+ applyRoundness();
}
@Override
public void setActualHeight(int actualHeight, boolean notifyListeners) {
+ int previousHeight = getActualHeight();
super.setActualHeight(actualHeight, notifyListeners);
- invalidateOutline();
+ if (previousHeight != actualHeight) {
+ applyRoundness();
+ }
}
@Override
public void setClipTopAmount(int clipTopAmount) {
+ int previousAmount = getClipTopAmount();
super.setClipTopAmount(clipTopAmount);
- invalidateOutline();
+ if (previousAmount != clipTopAmount) {
+ applyRoundness();
+ }
}
@Override
public void setClipBottomAmount(int clipBottomAmount) {
+ int previousAmount = getClipBottomAmount();
super.setClipBottomAmount(clipBottomAmount);
- invalidateOutline();
+ if (previousAmount != clipBottomAmount) {
+ applyRoundness();
+ }
}
protected void setOutlineAlpha(float alpha) {
if (alpha != mOutlineAlpha) {
mOutlineAlpha = alpha;
- invalidateOutline();
+ applyRoundness();
}
}
@@ -113,8 +335,7 @@
setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
} else {
mCustomOutline = false;
- setClipToOutline(false);
- invalidateOutline();
+ applyRoundness();
}
}
@@ -151,15 +372,22 @@
protected void setOutlineRect(float left, float top, float right, float bottom) {
mCustomOutline = true;
- setClipToOutline(true);
mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
// Outlines need to be at least 1 dp
mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
- invalidateOutline();
+ applyRoundness();
}
+ public Path getCustomClipPath(View child) {
+ return null;
+ }
+
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidateOutline();
+ invalidate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index aac9af8..18b9860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@
return mDark;
}
+ public boolean isRemoved() {
+ return false;
+ }
+
/**
* See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
* the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc..68cf51c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Canvas;
-import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
+import com.android.systemui.R;
+
/**
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View {
+ private final boolean mDontModifyCorners;
private Drawable mBackground;
private int mClipTopAmount;
private int mActualHeight;
private int mClipBottomAmount;
private int mTintColor;
+ private float[] mCornerRadii = new float[8];
+ private int mCurrentSidePaddings;
+ private boolean mBottomIsRounded;
+ private int mBackgroundTop;
+ private boolean mBottomAmountClips = true;
public NotificationBackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
+ mDontModifyCorners = getResources().getBoolean(
+ R.bool.config_clipNotificationsToOutline);
}
@Override
protected void onDraw(Canvas canvas) {
- draw(canvas, mBackground);
+ if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+ canvas.save();
+ canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+ draw(canvas, mBackground);
+ canvas.restore();
+ }
}
private void draw(Canvas canvas, Drawable drawable) {
- int bottom = mActualHeight - mClipBottomAmount;
- if (drawable != null && bottom > mClipTopAmount) {
- drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+ if (drawable != null) {
+ int bottom = mActualHeight;
+ if (mBottomIsRounded && mBottomAmountClips) {
+ bottom -= mClipBottomAmount;
+ }
+ drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+ getWidth() - mCurrentSidePaddings, bottom);
drawable.draw(canvas);
}
}
@@ -87,6 +107,7 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackground.mutate();
if (mBackground != null) {
mBackground.setCallback(this);
setTint(mTintColor);
@@ -94,6 +115,7 @@
if (mBackground instanceof RippleDrawable) {
((RippleDrawable) mBackground).setForceSoftware(true);
}
+ updateBackgroundRadii();
invalidate();
}
@@ -152,4 +174,45 @@
public void setDrawableAlpha(int drawableAlpha) {
mBackground.setAlpha(drawableAlpha);
}
+
+ public void setRoundness(float topRoundness, float bottomRoundNess) {
+ mBottomIsRounded = bottomRoundNess != 0.0f;
+ mCornerRadii[0] = topRoundness;
+ mCornerRadii[1] = topRoundness;
+ mCornerRadii[2] = topRoundness;
+ mCornerRadii[3] = topRoundness;
+ mCornerRadii[4] = bottomRoundNess;
+ mCornerRadii[5] = bottomRoundNess;
+ mCornerRadii[6] = bottomRoundNess;
+ mCornerRadii[7] = bottomRoundNess;
+ updateBackgroundRadii();
+ }
+
+ public void setBottomAmountClips(boolean clips) {
+ if (clips != mBottomAmountClips) {
+ mBottomAmountClips = clips;
+ invalidate();
+ }
+ }
+
+ private void updateBackgroundRadii() {
+ if (mDontModifyCorners) {
+ return;
+ }
+ if (mBackground instanceof LayerDrawable) {
+ GradientDrawable gradientDrawable =
+ (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+ gradientDrawable.setCornerRadii(mCornerRadii);
+ }
+ }
+
+ public void setCurrentSidePaddings(float currentSidePaddings) {
+ mCurrentSidePaddings = (int) currentSidePaddings;
+ invalidate();
+ }
+
+ public void setBackgroundTop(int backgroundTop) {
+ mBackgroundTop = backgroundTop;
+ invalidate();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c89..39c2131 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
import android.os.Build;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -34,8 +35,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.NotificationColorUtil;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@
*/
public class NotificationContentView extends FrameLayout {
+ private static final String TAG = "NotificationContentView";
public static final int VISIBLE_TYPE_CONTRACTED = 0;
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@
public static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
- private final int mMinContractedHeight;
- private final int mNotificationContentMarginEnd;
+ private int mMinContractedHeight;
+ private int mNotificationContentMarginEnd;
private View mContractedChild;
private View mExpandedChild;
private View mHeadsUpChild;
@@ -134,15 +136,22 @@
private int mClipBottomAmount;
private boolean mIsLowPriority;
private boolean mIsContentExpandable;
+ private int mCustomViewSidePaddings;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ initView();
+ }
+
+ public void initView() {
mMinContractedHeight = getResources().getDimensionPixelSize(
R.dimen.min_notification_layout_height);
mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_end);
+ mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+ R.dimen.notification_content_custom_view_side_padding);
}
public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@
: MeasureSpec.makeMeasureSpec(size, useExactly
? MeasureSpec.EXACTLY
: MeasureSpec.AT_MOST);
- mExpandedChild.measure(widthMeasureSpec, spec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
}
if (mContractedChild != null) {
@@ -196,22 +205,22 @@
} else {
heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
int measuredHeight = mContractedChild.getMeasuredHeight();
if (measuredHeight < mMinContractedHeight) {
heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
maxChildHeight = Math.max(maxChildHeight, measuredHeight);
if (updateContractedHeaderWidth()) {
- mContractedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
}
if (mExpandedChild != null
&& mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
// the Expanded child is smaller then the collapsed. Let's remeasure it.
heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
MeasureSpec.EXACTLY);
- mExpandedChild.measure(widthMeasureSpec, heightSpec);
+ measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
}
}
if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@
size = Math.min(size, layoutParams.height);
useExactly = true;
}
- mHeadsUpChild.measure(widthMeasureSpec,
+ measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
- : MeasureSpec.AT_MOST));
+ : MeasureSpec.AT_MOST), 0);
maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
}
if (mSingleLineView != null) {
@@ -382,6 +391,38 @@
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+ updateMargins(child);
+ }
+
+ private void updateMargins(View child) {
+ if (child == null) {
+ return;
+ }
+ NotificationViewWrapper wrapper = getWrapperForView(child);
+ boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+ boolean needsMargins = isCustomView &&
+ child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+ int padding = needsMargins ? mCustomViewSidePaddings : 0;
+ MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+ layoutParams.setMarginStart(padding);
+ layoutParams.setMarginEnd(padding);
+ child.setLayoutParams(layoutParams);
+ }
+
+ private NotificationViewWrapper getWrapperForView(View child) {
+ if (child == mContractedChild) {
+ return mContractedWrapper;
+ }
+ if (child == mExpandedChild) {
+ return mExpandedWrapper;
+ }
+ if (child == mHeadsUpChild) {
+ return mHeadsUpWrapper;
+ }
+ if (child == mAmbientChild) {
+ return mAmbientWrapper;
+ }
+ return null;
}
public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@
mExpandedChild = child;
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@
mHeadsUpChild = child;
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ updateMargins(child);
}
public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@
int endHeight = getViewForVisibleType(mVisibleType).getHeight();
int progress = Math.abs(mContentHeight - startHeight);
int totalDistance = Math.abs(endHeight - startHeight);
+ if (totalDistance == 0) {
+ Log.wtf(TAG, "the total transformation distance is 0"
+ + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+ + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+ + "\n mContentHeight: " + mContentHeight);
+ return 1.0f;
+ }
float amount = (float) progress / (float) totalDistance;
return Math.min(1.0f, amount);
}
@@ -1459,4 +1509,20 @@
}
return false;
}
+
+ public boolean shouldClipToSidePaddings() {
+ boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+ if (mUserExpanding) {
+ needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+ }
+ return needsPaddings;
+ }
+
+ private boolean shouldClipToSidePaddings(int visibleType) {
+ NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+ if (visibleWrapper == null) {
+ return false;
+ }
+ return visibleWrapper.shouldClipToSidePaddings();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
private float mHorizSpaceForIcon = -1;
private int mVertSpaceForIcons = -1;
private int mIconPadding = -1;
+ private int mSidePadding;
private float mAlpha = 0f;
private float mPrevX;
@@ -175,6 +176,7 @@
final Resources res = mContext.getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+ mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mMenuItems.clear();
// Construct the menu items based on the notification
@@ -496,8 +498,8 @@
final int count = mMenuContainer.getChildCount();
for (int i = 0; i < count; i++) {
final View v = mMenuContainer.getChildAt(i);
- final float left = i * mHorizSpaceForIcon;
- final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+ final float left = mSidePadding + i * mHorizSpaceForIcon;
+ final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
v.setX(showOnLeft ? left : right);
}
mOnLeft = showOnLeft;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde..b7a00eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@
private boolean mVibrationOnAnimation;
private boolean mUserTouchingScreen;
private boolean mTouchActive;
+ private float mFirstElementRoundness;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -107,6 +108,7 @@
mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
NotificationPanelView.DOZE_ANIMATION_DURATION);
mShelfState = new ShelfState();
+ setBottomRoundness(1.0f, false /* animate */);
initDimens();
}
@@ -252,6 +254,8 @@
boolean expandingAnimated = mAmbientState.isExpansionChanging()
&& !mAmbientState.isPanelTracking();
int baseZHeight = mAmbientState.getBaseZHeight();
+ int backgroundTop = 0;
+ float firstElementRoundness = 0.0f;
while (notificationIndex < mHostLayout.getChildCount()) {
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
notificationIndex++;
@@ -302,9 +306,20 @@
if (notGoneIndex != 0 || !aboveShelf) {
row.setAboveShelf(false);
}
+ if (notGoneIndex == 0) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState.clampedAppearAmount == 1.0f) {
+ // only if the first icon is fully in the shelf we want to clip to it!
+ backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+ firstElementRoundness = row.getCurrentTopRoundness();
+ }
+ }
notGoneIndex++;
previousColor = ownColorUntinted;
}
+ setBackgroundTop(backgroundTop);
+ setFirstElementRoundness(firstElementRoundness);
mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@
}
}
+ private void setFirstElementRoundness(float firstElementRoundness) {
+ if (mFirstElementRoundness != firstElementRoundness) {
+ mFirstElementRoundness = firstElementRoundness;
+ setTopRoundness(firstElementRoundness, false /* animate */);
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c..ff6c775 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.RemoteInputView;
import android.util.ArrayMap;
@@ -38,11 +37,11 @@
= new ArrayList<>();
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
- private final HeadsUpManager mHeadsUpManager;
+ private final Delegate mDelegate;
- public RemoteInputController(HeadsUpManager headsUpManager) {
+ public RemoteInputController(Delegate delegate) {
addCallback(Dependency.get(StatusBarWindowManager.class));
- mHeadsUpManager = headsUpManager;
+ mDelegate = delegate;
}
/**
@@ -114,7 +113,7 @@
}
private void apply(NotificationData.Entry entry) {
- mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+ mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
boolean remoteInputActive = isRemoteInputActive();
int N = mCallbacks.size();
for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@
}
}
+ public void requestDisallowLongPressAndDismiss() {
+ mDelegate.requestDisallowLongPressAndDismiss();
+ }
+
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mDelegate.lockScrollTo(entry);
+ }
+
public interface Callback {
default void onRemoteInputActive(boolean active) {}
default void onRemoteInputSent(NotificationData.Entry entry) {}
}
+
+ public interface Delegate {
+ /**
+ * Activate remote input if necessary.
+ */
+ void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+ /**
+ * Request that the view does not dismiss nor perform long press for the current touch.
+ */
+ void requestDisallowLongPressAndDismiss();
+
+ /**
+ * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+ * the user scrolls, or {@param v} loses focus or is detached.
+ */
+ void lockScrollTo(NotificationData.Entry entry);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index a53e348..8830352 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -50,6 +51,7 @@
public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
private static final String TAG = "ScrimView";
private final ColorExtractor.GradientColors mColors;
+ private int mDensity;
private boolean mDrawAsSrc;
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@
}
};
private Runnable mChangeRunnable;
+ private int mCornerRadius;
public ScrimView(Context context) {
this(context, null);
@@ -93,6 +96,24 @@
mColors = new ColorExtractor.GradientColors();
updateScreenSize();
updateColorWithTint(false);
+ initView();
+ final Configuration currentConfig = mContext.getResources().getConfiguration();
+ mDensity = currentConfig.densityDpi;
+ }
+
+ private void initView() {
+ mCornerRadius = getResources().getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ int densityDpi = newConfig.densityDpi;
+ if (mDensity != densityDpi) {
+ mDensity = densityDpi;
+ initView();
+ }
}
@Override
@@ -145,6 +166,28 @@
mDrawable.draw(canvas);
canvas.restore();
}
+ // We also need to draw the rounded corners of the background
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+ mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
+ canvas.save();
+ canvas.clipRect(mExcludedRect.right - mCornerRadius,
+ mExcludedRect.bottom - mCornerRadius,
+ mExcludedRect.right, mExcludedRect.bottom);
+ mDrawable.draw(canvas);
+ canvas.restore();
}
}
}
@@ -252,6 +295,13 @@
return false;
}
+ /**
+ * It might look counterintuitive to have another method to set the alpha instead of
+ * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+ * optimizing blend modes, so it makes sense.
+ *
+ * @param alpha Gradient alpha from 0 to 1.
+ */
public void setViewAlpha(float alpha) {
if (alpha != mViewAlpha) {
mViewAlpha = alpha;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 0000000..d7b211f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+ int getAnimationStartTag();
+
+ int getAnimationEndTag();
+
+ int getAnimatorTag();
+
+ Property getProperty();
+
+ static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+ Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+ Property<T, Float> property = new FloatProperty<T>(name) {
+
+ @Override
+ public Float get(T object) {
+ return getter.apply(object);
+ }
+
+ @Override
+ public void setValue(T object, float value) {
+ setter.accept(object, value);
+ }
+ };
+ return new AnimatableProperty() {
+ @Override
+ public int getAnimationStartTag() {
+ return startValueTag;
+ }
+
+ @Override
+ public int getAnimationEndTag() {
+ return endValueTag;
+ }
+
+ @Override
+ public int getAnimatorTag() {
+ return animatorTag;
+ }
+
+ @Override
+ public Property getProperty() {
+ return property;
+ }
+ };
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb..27defca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@
private void transformViewInternal(MessagingLayoutTransformState mlt,
float transformationAmount, boolean to) {
+ ensureVisible();
ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
mMessagingLayout.getMessagingGroups());
ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@
@Override
public void setVisible(boolean visible, boolean force) {
+ super.setVisible(visible, force);
resetTransformedView();
ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
+import android.os.Build;
import android.view.View;
import com.android.systemui.R;
@@ -37,6 +38,7 @@
private final Paint mGreyPaint = new Paint();
private boolean mIsLegacy;
private int mLegacyColor;
+ private boolean mBeforeP;
protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
@@ -115,4 +117,17 @@
super.setLegacy(legacy);
mIsLegacy = legacy;
}
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ // Before P we ensure that they are now drawing inside out content bounds since we inset
+ // the view. If they target P, then we don't have that guarantee and we need to be safe.
+ return !mBeforeP;
+ }
+
+ @Override
+ public void onContentUpdated(ExpandableNotificationRow row) {
+ super.onContentUpdated(row);
+ mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index 4d58cb8..060e6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -60,4 +60,9 @@
public boolean isDimmable() {
return getCustomBackgroundColor() == 0;
}
+
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return true;
+ }
}
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 fd085d9..e07112f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
updateActionOffset();
}
+ @Override
+ public boolean shouldClipToSidePaddings() {
+ return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+ }
+
private void updateActionOffset() {
if (mActionsContainer != null) {
// We should never push the actions higher than they are in the headsup view.
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 1cd5f15..8a767bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
public int getMinLayoutHeight() {
return 0;
}
+
+ public boolean shouldClipToSidePaddings() {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba943..92dcc9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@
*/
public class PropertyAnimator {
+ public static <T extends View> void setProperty(final T view,
+ AnimatableProperty animatableProperty, float newEndValue,
+ AnimationProperties properties, boolean animated) {
+ int animatorTag = animatableProperty.getAnimatorTag();
+ ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+ if (previousAnimator != null || animated) {
+ startAnimation(view, animatableProperty, newEndValue, properties);
+ } else {
+ // no new animation needed, let's just apply the value
+ animatableProperty.getProperty().set(view, newEndValue);
+ }
+ }
+
public static <T extends View> void startAnimation(final T view,
AnimatableProperty animatableProperty, float newEndValue,
AnimationProperties properties) {
@@ -102,10 +115,4 @@
view.setTag(animationEndTag, newEndValue);
}
- public interface AnimatableProperty {
- int getAnimationStartTag();
- int getAnimationEndTag();
- int getAnimatorTag();
- Property getProperty();
- }
}
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 ad07af0..dec5303 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@
public void transformViewFrom(TransformState otherState, float transformationAmount) {
mTransformedView.animate().cancel();
if (sameAs(otherState)) {
- if (mTransformedView.getVisibility() == View.INVISIBLE
- || mTransformedView.getAlpha() != 1.0f) {
- // We have the same content, lets show ourselves
- mTransformedView.setAlpha(1.0f);
- mTransformedView.setVisibility(View.VISIBLE);
- }
+ ensureVisible();
} else {
CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
}
transformViewFullyFrom(otherState, transformationAmount);
}
+ protected void ensureVisible() {
+ if (mTransformedView.getVisibility() == View.INVISIBLE
+ || mTransformedView.getAlpha() != 1.0f) {
+ // We have the same content, lets show ourselves
+ mTransformedView.setAlpha(1.0f);
+ mTransformedView.setVisibility(View.VISIBLE);
+ }
+ }
+
public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(): "); pw.println(getPulseDuration());
+ pw.print(" getPulseInDuration(): "); pw.println(getPulseInDuration());
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration() {
+ return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration(boolean pickupOrDoubleTap) {
- return pickupOrDoubleTap
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration() {
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
}
public int getPulseVisibleDuration() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
-import android.view.animation.Interpolator;
-import com.android.systemui.Interpolators;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
@@ -40,74 +35,59 @@
private final Handler mHandler = new Handler();
private final ScrimController mScrimController;
- private final Context mContext;
-
private boolean mDozing;
private DozeHost.PulseCallback mPulseCallback;
private int mPulseReason;
- private Animator mInFrontAnimator;
- private Animator mBehindAnimator;
- private float mInFrontTarget;
- private float mBehindTarget;
- private boolean mDozingAborted;
- private boolean mWakeAndUnlocking;
private boolean mFullyPulsing;
- private float mAodFrontScrimOpacity = 0;
- private Runnable mSetDozeInFrontAlphaDelayed;
+ private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+ + DozeLog.pulseReasonToString(mPulseReason));
+ }
+ if (!mDozing) {
+ return;
+ }
+
+ // Signal that the pulse is ready to turn the screen on and draw.
+ pulseStarted();
+ }
+
+ @Override
+ public void onFinished() {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+ }
+ if (!mDozing) {
+ return;
+ }
+ mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+ mHandler.postDelayed(mPulseOutExtended,
+ mDozeParameters.getPulseVisibleDurationExtended());
+ mFullyPulsing = true;
+ }
+
+ /**
+ * Transition was aborted before it was over.
+ */
+ @Override
+ public void onCancelled() {
+ pulseFinished();
+ }
+ };
public DozeScrimController(ScrimController scrimController, Context context) {
- mContext = context;
mScrimController = scrimController;
mDozeParameters = new DozeParameters(context);
}
- public void setDozing(boolean dozing, boolean animate) {
+ public void setDozing(boolean dozing) {
if (mDozing == dozing) return;
mDozing = dozing;
- mWakeAndUnlocking = false;
- if (mDozing) {
- mDozingAborted = false;
- abortAnimations();
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
- } else {
+ if (!mDozing) {
cancelPulsing();
- if (animate) {
- startScrimAnimation(false /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- startScrimAnimation(true /* inFront */, 0f /* target */,
- NotificationPanelView.DOZE_ANIMATION_DURATION,
- Interpolators.LINEAR_OUT_SLOW_IN);
- } else {
- abortAnimations();
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
- }
- }
- }
-
- /**
- * Set the opacity of the front scrim when showing AOD1
- *
- * Used to emulate lower brightness values than the hardware supports natively.
- */
- public void setAodDimmingScrim(float scrimOpacity) {
- mAodFrontScrimOpacity = scrimOpacity;
- if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
- && mDozeParameters.getAlwaysOn()) {
- setDozeInFrontAlpha(mAodFrontScrimOpacity);
- }
- }
-
- public void setWakeAndUnlocking() {
- // Immediately abort the doze scrims in case of wake-and-unlock
- // for pulsing so the Keyguard fade-out animation scrim can take over.
- if (!mWakeAndUnlocking) {
- mWakeAndUnlocking = true;
- mScrimController.setDozeBehindAlpha(0f);
- setDozeInFrontAlpha(0f);
}
}
@@ -118,37 +98,21 @@
}
if (!mDozing || mPulseCallback != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ + (mPulseCallback != null));
+ }
// Pulse suppressed.
callback.onPulseFinished();
return;
}
- // Begin pulse. Note that it's very important that the pulse finished callback
+ // Begin pulse. Note that it's very important that the pulse finished callback
// be invoked when we're done so that the caller can drop the pulse wakelock.
mPulseCallback = callback;
mPulseReason = reason;
- setDozeInFrontAlpha(1f);
- mHandler.post(mPulseIn);
- }
- /**
- * Aborts pulsing immediately.
- */
- public void abortPulsing() {
- cancelPulsing();
- if (mDozing && !mWakeAndUnlocking) {
- mScrimController.setDozeBehindAlpha(1f);
- setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
- ? mAodFrontScrimOpacity : 1f);
- }
- }
-
- /**
- * Aborts dozing immediately.
- */
- public void abortDoze() {
- mDozingAborted = true;
- abortPulsing();
+ mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
}
public void pulseOutNow() {
@@ -157,17 +121,6 @@
}
}
- public void onScreenTurnedOn() {
- if (isPulsing()) {
- final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
- || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
- startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
- pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
- mPulseInFinished);
- }
- }
-
public boolean isPulsing() {
return mPulseCallback != null;
}
@@ -181,11 +134,9 @@
}
private void cancelPulsing() {
- if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
if (mPulseCallback != null) {
+ if (DEBUG) Log.d(TAG, "Cancel pulsing");
mFullyPulsing = false;
- mHandler.removeCallbacks(mPulseIn);
mHandler.removeCallbacks(mPulseOut);
mHandler.removeCallbacks(mPulseOutExtended);
pulseFinished();
@@ -193,151 +144,20 @@
}
private void pulseStarted() {
+ DozeLog.tracePulseStart(mPulseReason);
if (mPulseCallback != null) {
mPulseCallback.onPulseStarted();
}
}
private void pulseFinished() {
+ DozeLog.tracePulseFinish();
if (mPulseCallback != null) {
mPulseCallback.onPulseFinished();
mPulseCallback = null;
}
}
- private void abortAnimations() {
- if (mInFrontAnimator != null) {
- mInFrontAnimator.cancel();
- }
- if (mBehindAnimator != null) {
- mBehindAnimator.cancel();
- }
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator) {
- startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
- }
-
- private void startScrimAnimation(final boolean inFront, float target, long duration,
- Interpolator interpolator, final Runnable endRunnable) {
- Animator current = getCurrentAnimator(inFront);
- if (current != null) {
- float currentTarget = getCurrentTarget(inFront);
- if (currentTarget == target) {
- return;
- }
- current.cancel();
- }
- ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float value = (float) animation.getAnimatedValue();
- setDozeAlpha(inFront, value);
- }
- });
- anim.setInterpolator(interpolator);
- anim.setDuration(duration);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setCurrentAnimator(inFront, null);
- if (endRunnable != null) {
- endRunnable.run();
- }
- }
- });
- anim.start();
- setCurrentAnimator(inFront, anim);
- setCurrentTarget(inFront, target);
- }
-
- private float getCurrentTarget(boolean inFront) {
- return inFront ? mInFrontTarget : mBehindTarget;
- }
-
- private void setCurrentTarget(boolean inFront, float target) {
- if (inFront) {
- mInFrontTarget = target;
- } else {
- mBehindTarget = target;
- }
- }
-
- private Animator getCurrentAnimator(boolean inFront) {
- return inFront ? mInFrontAnimator : mBehindAnimator;
- }
-
- private void setCurrentAnimator(boolean inFront, Animator animator) {
- if (inFront) {
- mInFrontAnimator = animator;
- } else {
- mBehindAnimator = animator;
- }
- }
-
- private void setDozeAlpha(boolean inFront, float alpha) {
- if (mWakeAndUnlocking) {
- return;
- }
- if (inFront) {
- mScrimController.setDozeInFrontAlpha(alpha);
- } else {
- mScrimController.setDozeBehindAlpha(alpha);
- }
- }
-
- private float getDozeAlpha(boolean inFront) {
- return inFront
- ? mScrimController.getDozeInFrontAlpha()
- : mScrimController.getDozeBehindAlpha();
- }
-
- private void setDozeInFrontAlpha(float opacity) {
- setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
- }
-
- private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
- if (mSetDozeInFrontAlphaDelayed != null) {
- mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
- mSetDozeInFrontAlphaDelayed = null;
- }
- if (delayMs <= 0) {
- mScrimController.setDozeInFrontAlpha(opacity);
- } else {
- mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
- setDozeInFrontAlpha(opacity);
- }, delayMs);
- }
- }
-
- private final Runnable mPulseIn = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
- + DozeLog.pulseReasonToString(mPulseReason));
- if (!mDozing) return;
- DozeLog.tracePulseStart(mPulseReason);
-
- // Signal that the pulse is ready to turn the screen on and draw.
- pulseStarted();
- }
- };
-
- private final Runnable mPulseInFinished = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
- if (!mDozing) return;
- mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
- mHandler.postDelayed(mPulseOutExtended,
- mDozeParameters.getPulseVisibleDurationExtended());
- mFullyPulsing = true;
- }
- };
-
private final Runnable mPulseOutExtended = new Runnable() {
@Override
public void run() {
@@ -354,38 +174,13 @@
mHandler.removeCallbacks(mPulseOutExtended);
if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
if (!mDozing) return;
- startScrimAnimation(true /* inFront */, 1,
- mDozeParameters.getPulseOutDuration(),
- Interpolators.ALPHA_IN, mPulseOutFinishing);
+ mScrimController.transitionTo(ScrimState.AOD,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ pulseFinished();
+ }
+ });
}
};
-
- private final Runnable mPulseOutFinishing = new Runnable() {
- @Override
- public void run() {
- if (DEBUG) Log.d(TAG, "Pulse out finished");
- DozeLog.tracePulseFinish();
- if (mDozeParameters.getAlwaysOn() && mDozing) {
- // Setting power states can block rendering. For AOD, delay finishing the pulse and
- // setting the power state until the fully black scrim had time to hit the
- // framebuffer.
- mHandler.postDelayed(mPulseOutFinished, 30);
- } else {
- mPulseOutFinished.run();
- }
- }
- };
-
- private final Runnable mPulseOutFinished = new Runnable() {
- @Override
- public void run() {
- // Signal that the pulse is all finished so we can turn the screen off now.
- DozeScrimController.this.pulseFinished();
- if (mDozeParameters.getAlwaysOn()) {
- // Setting power states can happen after we push out the frame. Make sure we
- // stay fully opaque until the power state request reaches the lower levels.
- setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
- }
- }
- };
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
}
private boolean pulsingOrAod() {
- boolean pulsing = mDozeScrimController.isPulsing();
- boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
- return pulsing || dozingWithScreenOn;
+ final ScrimState scrimState = mScrimController.getState();
+ return scrimState == ScrimState.AOD
+ || scrimState == ScrimState.PULSING;
}
@Override
@@ -246,15 +246,12 @@
true /* allowEnterAnimation */);
} else if (mMode == MODE_WAKE_AND_UNLOCK){
Trace.beginSection("MODE_WAKE_AND_UNLOCK");
- mDozeScrimController.abortDoze();
} else {
Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
mUpdateMonitor.awakenFromDream();
}
mStatusBarWindowManager.setStatusBarFocusable(false);
mKeyguardViewMediator.onWakeAndUnlocking();
- mScrimController.setWakeAndUnlocking();
- mDozeScrimController.setWakeAndUnlocking();
if (mStatusBar.getNavigationBarView() != null) {
mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
}
@@ -269,6 +266,7 @@
}
private void showBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
mStatusBarKeyguardViewManager.animateCollapsePanels(
FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c6..836efff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@
mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
- if (mDark && child instanceof StatusBarIconView) {
+ if (child instanceof StatusBarIconView) {
((StatusBarIconView) child).setDark(mDark, false, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index fff6abe..17e3599 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@
private ValueAnimator mDarkAnimator;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private boolean mUserSetupComplete;
+ private int mQsNotificationTopPadding;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -307,6 +308,8 @@
R.dimen.max_notification_fadeout_height);
mIndicationBottomPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
+ mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+ R.dimen.qs_notification_keyguard_padding);
}
public void updateResources() {
@@ -818,7 +821,7 @@
private float getQsExpansionFraction() {
return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
- / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
@Override
@@ -1361,7 +1364,7 @@
// take the maximum and linearly interpolate with the panel expansion for a nice motion.
int maxNotifications = mClockPositionResult.stackScrollerPadding
- mClockPositionResult.stackScrollerPaddingAdjustment;
- int maxQs = getTempQsMaxExpansion();
+ int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
int max = mStatusBarState == StatusBarState.KEYGUARD
? Math.max(maxNotifications, maxQs)
: maxQs;
@@ -1375,7 +1378,7 @@
// from a scrolled quick settings.
return interpolate(getQsExpansionFraction(),
mNotificationStackScroller.getIntrinsicPadding(),
- mQsMaxExpansionHeight);
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
} else {
return mQsExpansionHeight;
}
@@ -1544,7 +1547,7 @@
/ (panelHeightQsExpanded - panelHeightQsCollapsed);
}
setQsExpansion(mQsMinExpansionHeight
- + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+ + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
}
updateExpandedHeight(expandedHeight);
updateHeader();
@@ -1566,14 +1569,6 @@
}
}
- /**
- * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
- * collapsing QS / the panel when QS was scrolled
- */
- private int getTempQsMaxExpansion() {
- return mQsMaxExpansionHeight;
- }
-
private int calculatePanelHeightShade() {
int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@
}
int maxQsHeight = mQsMaxExpansionHeight;
+ if (mKeyguardShowing) {
+ maxQsHeight += mQsNotificationTopPadding;
+ }
+
// If an animation is changing the size of the QS panel, take the animated value.
if (mQsSizeChangeAnimator != null) {
maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..dfd4c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
import android.os.Trace;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -34,12 +36,14 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
import com.android.internal.graphics.ColorUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.function.Consumer;
@@ -56,33 +63,54 @@
* security method gets shown).
*/
public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
- OnHeadsUpChangedListener, OnColorsChangedListener {
+ OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+ private static final String TAG = "ScrimController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
- // Default alpha value for most scrims, if unsure use this constant
+ /**
+ * Default alpha value for most scrims.
+ */
public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
- // A scrim varies its opacity based on a busyness factor, for example
- // how many notifications are currently visible.
+ /**
+ * A scrim varies its opacity based on a busyness factor, for example
+ * how many notifications are currently visible.
+ */
public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+ /**
+ * The most common scrim, the one under the keyguard.
+ */
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+ /**
+ * We fade out the bottom scrim when the bouncer is visible.
+ */
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
- private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
- private static final int TAG_KEY_ANIM = R.id.scrim;
+ /**
+ * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+ */
+ protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+ static final int TAG_KEY_ANIM = R.id.scrim;
+ static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
private static final float NOT_INITIALIZED = -1;
- private final LightBarController mLightBarController;
+ private ScrimState mState = ScrimState.UNINITIALIZED;
+ private final Context mContext;
protected final ScrimView mScrimBehind;
protected final ScrimView mScrimInFront;
- private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final LightBarController mLightBarController;
+ private final UnlockMethodCache mUnlockMethodCache;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -94,61 +122,53 @@
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
- protected boolean mKeyguardShowing;
private float mFraction;
private boolean mDarkenWhileDragging;
- protected boolean mBouncerShowing;
- protected boolean mBouncerIsKeyguard = false;
- private boolean mWakeAndUnlocking;
protected boolean mAnimateChange;
private boolean mUpdatePending;
private boolean mTracking;
private boolean mAnimateKeyguardFadingOut;
- protected long mDurationOverride = -1;
+ protected long mAnimationDuration = -1;
private long mAnimationDelay;
private Runnable mOnAnimationFinished;
private boolean mDeferFinishedListener;
private final Interpolator mInterpolator = new DecelerateInterpolator();
- private boolean mDozing;
- private float mDozeInFrontAlpha;
- private float mDozeBehindAlpha;
private float mCurrentInFrontAlpha = NOT_INITIALIZED;
private float mCurrentBehindAlpha = NOT_INITIALIZED;
- private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+ private int mCurrentInFrontTint;
+ private int mCurrentBehindTint;
private int mPinnedHeadsUpCount;
private float mTopHeadsUpDragAmount;
private View mDraggedHeadsUpView;
- private boolean mForceHideScrims;
- private boolean mSkipFirstFrame;
- private boolean mDontAnimateBouncerChanges;
private boolean mKeyguardFadingOutInProgress;
- private boolean mAnimatingDozeUnlock;
private ValueAnimator mKeyguardFadeoutAnimation;
- /** Wake up from AOD transition is starting; need fully opaque front scrim */
- private boolean mWakingUpFromAodStarting;
- /** Wake up from AOD transition is in progress; need black tint */
- private boolean mWakingUpFromAodInProgress;
- /** Wake up from AOD transition is animating; need to reset when animation finishes */
- private boolean mWakingUpFromAodAnimationRunning;
- private boolean mScrimsVisble;
+ private boolean mScrimsVisible;
private final Consumer<Boolean> mScrimVisibleListener;
+ private boolean mBlankScreen;
+ private boolean mScreenBlankingCallbackCalled;
+ private Callback mCallback;
+
+ private final WakeLock mWakeLock;
+ private boolean mWakeLockHeld;
public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
- ScrimView scrimInFront, View headsUpScrim,
- Consumer<Boolean> scrimVisibleListener) {
+ ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+ DozeParameters dozeParameters) {
mScrimBehind = scrimBehind;
mScrimInFront = scrimInFront;
mHeadsUpScrim = headsUpScrim;
mScrimVisibleListener = scrimVisibleListener;
- final Context context = scrimBehind.getContext();
- mUnlockMethodCache = UnlockMethodCache.getInstance(context);
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+ mContext = scrimBehind.getContext();
+ mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mLightBarController = lightBarController;
- mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+ mWakeLock = createWakeLock();
// Scrim alpha is initially set to the value on the resource but might be changed
// to make sure that text on top of it is legible.
mScrimBehindAlpha = mScrimBehindAlphaResValue;
+ mDozeParameters = dozeParameters;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
mNeedsDrawableColorUpdate = true;
+ final ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+ states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+ }
+ mState = ScrimState.UNINITIALIZED;
+
updateHeadsUpScrim(false);
updateScrims();
}
- public void setKeyguardShowing(boolean showing) {
- mKeyguardShowing = showing;
+ public void transitionTo(ScrimState state) {
+ transitionTo(state, null);
+ }
- // Showing/hiding the keyguard means that scrim colors have to be switched
- mNeedsDrawableColorUpdate = true;
- scheduleUpdate();
+ public void transitionTo(ScrimState state, Callback callback) {
+ if (state == mState) {
+ return;
+ } else if (DEBUG) {
+ Log.d(TAG, "State changed to: " + state);
+ }
+
+ if (state == ScrimState.UNINITIALIZED) {
+ throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+ }
+
+ if (mCallback != null) {
+ mCallback.onCancelled();
+ }
+ mCallback = callback;
+
+ state.prepare(mState);
+ mScreenBlankingCallbackCalled = false;
+ mAnimationDelay = 0;
+ mBlankScreen = state.getBlanksScreen();
+ mAnimateChange = state.getAnimateChange();
+ mAnimationDuration = state.getAnimationDuration();
+ mCurrentInFrontTint = state.getFrontTint();
+ mCurrentBehindTint = state.getBehindTint();
+ mCurrentInFrontAlpha = state.getFrontAlpha();
+ mCurrentBehindAlpha = state.getBehindAlpha();
+
+ // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+ // to do the same when you're just showing the brightness mirror.
+ mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+ if (mKeyguardFadeoutAnimation != null) {
+ mKeyguardFadeoutAnimation.cancel();
+ }
+
+ mState = state;
+
+ // Do not let the device sleep until we're done with all animations
+ if (!mWakeLockHeld) {
+ if (mWakeLock != null) {
+ mWakeLockHeld = true;
+ mWakeLock.acquire();
+ } else {
+ Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+ }
+ }
+
+ if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+ scheduleUpdate();
+ } else {
+ // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+ // with too many things at this case, in order to not skip the initial frames.
+ mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+ }
+ }
+
+ public ScrimState getState() {
+ return mState;
}
protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
float scrimBehindAlphaUnlocking) {
mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ ScrimState[] states = ScrimState.values();
+ for (int i = 0; i < states.length; i++) {
+ states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+ }
scheduleUpdate();
}
@@ -186,131 +274,59 @@
mTracking = false;
}
+ /**
+ * Current state of the shade expansion when pulling it from the top.
+ * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+ *
+ * The expansion fraction is tied to the scrim opacity.
+ *
+ * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+ */
public void setPanelExpansion(float fraction) {
if (mFraction != fraction) {
mFraction = fraction;
- scheduleUpdate();
+
+ if (mState == ScrimState.UNLOCKED) {
+ // Darken scrim as you pull down the shade when unlocked
+ float behindFraction = getInterpolatedFraction();
+ behindFraction = (float) Math.pow(behindFraction, 0.8f);
+ mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ } else if (mState == ScrimState.KEYGUARD) {
+ if (mUpdatePending) {
+ return;
+ }
+
+ // Either darken of make the scrim transparent when you
+ // pull down the shade
+ float interpolatedFract = getInterpolatedFraction();
+ if (mDarkenWhileDragging) {
+ mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+ mScrimBehindAlphaKeyguard, interpolatedFract);
+ mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+ } else {
+ mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+ interpolatedFract);
+ mCurrentInFrontAlpha = 0;
+ }
+ } else {
+ Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+ return;
+ }
+
if (mPinnedHeadsUpCount != 0) {
updateHeadsUpScrim(false);
}
- if (mKeyguardFadeoutAnimation != null && mTracking) {
- mKeyguardFadeoutAnimation.cancel();
- }
+
+ updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+ updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
}
}
- public void setBouncerShowing(boolean showing) {
- mBouncerShowing = showing;
- mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
- scheduleUpdate();
- }
-
- /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
- public void prepareWakeUpFromAod() {
- if (mWakingUpFromAodInProgress) {
- return;
- }
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = true;
- mAnimateChange = false;
- scheduleUpdate();
- onPreDraw();
- }
-
- /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
- public void wakeUpFromAod() {
- if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
- // Wake and unlocking has a separate transition that must not be interfered with.
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- return;
- }
- if (mWakingUpFromAodStarting) {
- mWakingUpFromAodInProgress = true;
- mWakingUpFromAodStarting = false;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
-
- public void setWakeAndUnlocking() {
- mWakeAndUnlocking = true;
- mAnimatingDozeUnlock = true;
- mWakingUpFromAodStarting = false;
- mWakingUpFromAodInProgress = false;
- scheduleUpdate();
- }
-
- public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
- boolean skipFirstFrame) {
- mWakeAndUnlocking = false;
- mAnimateKeyguardFadingOut = true;
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- mSkipFirstFrame = skipFirstFrame;
- mOnAnimationFinished = onAnimationFinished;
-
- if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
- scheduleUpdate();
-
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
- } else {
-
- // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
- // with too many things in this case, in order to not skip the initial frames.
- mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
- }
- }
-
- public void abortKeyguardFadingOut() {
- if (mAnimateKeyguardFadingOut) {
- endAnimateKeyguardFadingOut(true /* force */);
- }
- }
-
- public void animateKeyguardUnoccluding(long duration) {
- mAnimateChange = false;
- setScrimBehindAlpha(0f);
- mAnimateChange = true;
- scheduleUpdate();
- mDurationOverride = duration;
- }
-
- public void animateGoingToFullShade(long delay, long duration) {
- mDurationOverride = duration;
- mAnimationDelay = delay;
- mAnimateChange = true;
- scheduleUpdate();
- }
-
- public void setDozing(boolean dozing) {
- if (mDozing != dozing) {
- mDozing = dozing;
- scheduleUpdate();
- }
- }
-
- public void setDozeInFrontAlpha(float alpha) {
- mDozeInFrontAlpha = alpha;
- updateScrimColor(mScrimInFront);
- }
-
- public void setDozeBehindAlpha(float alpha) {
- mDozeBehindAlpha = alpha;
- updateScrimColor(mScrimBehind);
- }
-
- public float getDozeBehindAlpha() {
- return mDozeBehindAlpha;
- }
-
- public float getDozeInFrontAlpha() {
- return mDozeInFrontAlpha;
- }
-
+ /**
+ * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+ * @param notificationCount Number of visible notifications.
+ */
public void setNotificationCount(int notificationCount) {
final float maxNotificationDensity = 3;
float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
notificationDensity);
if (mScrimBehindAlphaKeyguard != newAlpha) {
mScrimBehindAlphaKeyguard = newAlpha;
- mAnimateChange = true;
- scheduleUpdate();
- }
- }
- private float getScrimInFrontAlpha() {
- return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
- ? SCRIM_IN_FRONT_ALPHA_LOCKED
- : SCRIM_IN_FRONT_ALPHA;
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+ scheduleUpdate();
+ }
+ }
}
/**
@@ -352,7 +364,7 @@
if (mNeedsDrawableColorUpdate) {
mNeedsDrawableColorUpdate = false;
final GradientColors currentScrimColors;
- if (mKeyguardShowing) {
+ if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
// Always animate color changes if we're seeing the keyguard
mScrimInFront.setColors(mLockColors, true /* animated */);
mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
mLightBarController.setScrimColor(mScrimInFront.getColors());
}
- if (mAnimateKeyguardFadingOut || mForceHideScrims) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(0f);
- } else if (mWakeAndUnlocking) {
- // During wake and unlock, we first hide everything behind a black scrim, which then
- // gets faded out from animateKeyguardFadingOut. This must never be animated.
- mAnimateChange = false;
- if (mDozing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(1f);
- } else {
- setScrimInFrontAlpha(1f);
- setScrimBehindAlpha(0f);
- }
- } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
- updateScrimNormal();
- setScrimInFrontAlpha(0);
- } else {
- updateScrimKeyguard();
- }
- mAnimateChange = false;
+ setScrimInFrontAlpha(mCurrentInFrontAlpha);
+ setScrimBehindAlpha(mCurrentBehindAlpha);
+
dispatchScrimsVisible();
}
private void dispatchScrimsVisible() {
boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
- if (mScrimsVisble != scrimsVisible) {
- mScrimsVisble = scrimsVisible;
+ if (mScrimsVisible != scrimsVisible) {
+ mScrimsVisible = scrimsVisible;
mScrimVisibleListener.accept(scrimsVisible);
}
}
- private void updateScrimKeyguard() {
- if (mTracking && mDarkenWhileDragging) {
- float behindFraction = Math.max(0, Math.min(mFraction, 1));
- float fraction = 1 - behindFraction;
- fraction = (float) Math.pow(fraction, 0.8f);
- behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
- setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
- } else if (mBouncerShowing && !mBouncerIsKeyguard) {
- setScrimInFrontAlpha(getScrimInFrontAlpha());
- updateScrimNormal();
- } else if (mBouncerShowing) {
- setScrimInFrontAlpha(0f);
- setScrimBehindAlpha(mScrimBehindAlpha);
- } else {
- float fraction = Math.max(0, Math.min(mFraction, 1));
- if (mWakingUpFromAodStarting) {
- setScrimInFrontAlpha(1f);
- } else {
- setScrimInFrontAlpha(0f);
- }
- setScrimBehindAlpha(fraction
- * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
- + mScrimBehindAlphaUnlocking);
- }
- }
-
- private void updateScrimNormal() {
+ private float getInterpolatedFraction() {
float frac = mFraction;
// let's start this 20% of the way down the screen
frac = frac * 1.2f - 0.2f;
if (frac <= 0) {
- setScrimBehindAlpha(0);
+ return 0;
} else {
// woo, special effects
- final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
- setScrimBehindAlpha(k * mScrimBehindAlpha);
+ return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
}
}
@@ -455,102 +421,76 @@
private void setScrimInFrontAlpha(float alpha) {
setScrimAlpha(mScrimInFront, alpha);
- if (alpha == 0f) {
- mScrimInFront.setClickable(false);
- } else {
- // Eat touch events (unless dozing).
- mScrimInFront.setClickable(!mDozing);
- }
}
private void setScrimAlpha(View scrim, float alpha) {
- updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
- }
-
- protected float getDozeAlpha(View scrim) {
- return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
- }
-
- protected float getCurrentScrimAlpha(View scrim) {
- return scrim == mScrimBehind ? mCurrentBehindAlpha
- : scrim == mScrimInFront ? mCurrentInFrontAlpha
- : mCurrentHeadsUpAlpha;
- }
-
- private void setCurrentScrimAlpha(View scrim, float alpha) {
- if (scrim == mScrimBehind) {
- mCurrentBehindAlpha = alpha;
- mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
- } else if (scrim == mScrimInFront) {
- mCurrentInFrontAlpha = alpha;
+ if (alpha == 0f) {
+ scrim.setClickable(false);
} else {
- alpha = Math.max(0.0f, Math.min(1.0f, alpha));
- mCurrentHeadsUpAlpha = alpha;
+ // Eat touch events (unless dozing).
+ scrim.setClickable(!(mState == ScrimState.AOD));
}
+ updateScrim(mAnimateChange, scrim, alpha);
}
- private void updateScrimColor(View scrim) {
- float alpha1 = getCurrentScrimAlpha(scrim);
+ private void updateScrimColor(View scrim, float alpha, int tint) {
+ alpha = Math.max(0, Math.min(1.0f, alpha));
if (scrim instanceof ScrimView) {
ScrimView scrimView = (ScrimView) scrim;
- float dozeAlpha = getDozeAlpha(scrim);
- float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
- alpha = Math.max(0, Math.min(1.0f, alpha));
- scrimView.setViewAlpha(alpha);
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
(int) (alpha * 255));
- int dozeTint = Color.TRANSPARENT;
-
- boolean dozing = mAnimatingDozeUnlock || mDozing;
- boolean frontScrimDozing = mWakingUpFromAodInProgress;
- if (dozing || frontScrimDozing && scrim == mScrimInFront) {
- dozeTint = Color.BLACK;
- }
Trace.traceCounter(Trace.TRACE_TAG_APP,
scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
- dozeTint == Color.BLACK ? 1 : 0);
+ Color.alpha(tint));
- scrimView.setTint(dozeTint);
+ scrimView.setTint(tint);
+ scrimView.setViewAlpha(alpha);
} else {
- scrim.setAlpha(alpha1);
+ scrim.setAlpha(alpha);
}
dispatchScrimsVisible();
}
- private void startScrimAnimation(final View scrim, float target) {
- float current = getCurrentScrimAlpha(scrim);
- ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+ private int getCurrentScrimTint(View scrim) {
+ return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+ }
+
+ private void startScrimAnimation(final View scrim, float current, float target) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+ Color.TRANSPARENT;
anim.addUpdateListener(animation -> {
- float alpha = (float) animation.getAnimatedValue();
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ final float animAmount = (float) animation.getAnimatedValue();
+ final int finalScrimTint = scrim == mScrimInFront ?
+ mCurrentInFrontTint : mCurrentBehindTint;
+ float alpha = MathUtils.lerp(current, target, animAmount);
+ int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+ updateScrimColor(scrim, alpha, tint);
dispatchScrimsVisible();
});
anim.setInterpolator(getInterpolator());
anim.setStartDelay(mAnimationDelay);
- anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+ anim.setDuration(mAnimationDuration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ if (mKeyguardFadingOutInProgress) {
+ mKeyguardFadeoutAnimation = null;
+ mKeyguardFadingOutInProgress = false;
+ }
+ onFinished();
+
+ scrim.setTag(TAG_KEY_ANIM, null);
+ scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+ dispatchScrimsVisible();
+
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
mOnAnimationFinished.run();
mOnAnimationFinished = null;
}
- if (mKeyguardFadingOutInProgress) {
- mKeyguardFadeoutAnimation = null;
- mKeyguardFadingOutInProgress = false;
- mAnimatingDozeUnlock = false;
- }
- if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
- mWakingUpFromAodAnimationRunning = false;
- mWakingUpFromAodInProgress = false;
- }
- scrim.setTag(TAG_KEY_ANIM, null);
- scrim.setTag(TAG_KEY_ANIM_TARGET, null);
- dispatchScrimsVisible();
}
});
anim.start();
@@ -558,12 +498,6 @@
mKeyguardFadingOutInProgress = true;
mKeyguardFadeoutAnimation = anim;
}
- if (mWakingUpFromAodInProgress) {
- mWakingUpFromAodAnimationRunning = true;
- }
- if (mSkipFirstFrame) {
- anim.setCurrentPlayTime(16);
- }
scrim.setTag(TAG_KEY_ANIM, anim);
scrim.setTag(TAG_KEY_ANIM_TARGET, target);
}
@@ -582,19 +516,33 @@
public boolean onPreDraw() {
mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
mUpdatePending = false;
- if (mDontAnimateBouncerChanges) {
- mDontAnimateBouncerChanges = false;
+ if (mCallback != null) {
+ mCallback.onStart();
}
updateScrims();
- mDurationOverride = -1;
- mAnimationDelay = 0;
- mSkipFirstFrame = false;
// Make sure that we always call the listener even if we didn't start an animation.
endAnimateKeyguardFadingOut(false /* force */);
return true;
}
+ private void onFinished() {
+ if (mWakeLockHeld) {
+ mWakeLock.release();
+ mWakeLockHeld = false;
+ }
+ if (mCallback != null) {
+ mCallback.onFinished();
+ mCallback = null;
+ }
+ // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+ // At the end of the animation we need to remove the tint.
+ if (mState == ScrimState.UNLOCKED) {
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ }
+ }
+
private void endAnimateKeyguardFadingOut(boolean force) {
mAnimateKeyguardFadingOut = false;
if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
mOnAnimationFinished = null;
}
mKeyguardFadingOutInProgress = false;
- if (!mWakeAndUnlocking || force)
- mAnimatingDozeUnlock = false;
}
}
@@ -641,16 +587,19 @@
}
private void updateHeadsUpScrim(boolean animate) {
- updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+ updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
}
- private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
- if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
- return;
- }
+ @VisibleForTesting
+ void setOnAnimationFinished(Runnable onAnimationFinished) {
+ mOnAnimationFinished = onAnimationFinished;
+ }
- ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
- TAG_KEY_ANIM);
+ private void updateScrim(boolean animate, View scrim, float alpha) {
+ final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+ : scrim.getAlpha();
+
+ ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
float animEndValue = -1;
if (previousAnimator != null) {
if (animate || alpha == currentAlpha) {
@@ -664,9 +613,32 @@
animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
}
}
- if (alpha != currentAlpha && alpha != animEndValue) {
+
+ final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+ if (mBlankScreen || blankingInProgress) {
+ if (!blankingInProgress) {
+ blankDisplay();
+ }
+ return;
+ } else if (!mScreenBlankingCallbackCalled) {
+ // Not blanking the screen. Letting the callback know that we're ready
+ // to replace what was on the screen before.
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ }
+
+ final ScrimView scrimView = scrim instanceof ScrimView ? (ScrimView) scrim : null;
+ final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+ final boolean wantsTintUpdate = scrimView != null
+ && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+ if (wantsAlphaUpdate || wantsTintUpdate) {
if (animate) {
- startScrimAnimation(scrim, alpha);
+ final float fromAlpha = scrimView == null ? scrim.getAlpha()
+ : scrimView.getViewAlpha();
+ startScrimAnimation(scrim, fromAlpha, alpha);
scrim.setTag(TAG_START_ALPHA, currentAlpha);
scrim.setTag(TAG_END_ALPHA, alpha);
} else {
@@ -685,13 +657,62 @@
previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
} else {
// update the alpha directly
- setCurrentScrimAlpha(scrim, alpha);
- updateScrimColor(scrim);
+ updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+ onFinished();
}
}
+ } else {
+ onFinished();
}
}
+ private void blankDisplay() {
+ final float initialAlpha = mScrimInFront.getViewAlpha();
+ final int initialTint = mScrimInFront.getTint();
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+ anim.addUpdateListener(animation -> {
+ final float amount = (float) animation.getAnimatedValue();
+ float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+ int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+ updateScrimColor(mScrimInFront, animAlpha, animTint);
+ dispatchScrimsVisible();
+ });
+ anim.setInterpolator(getInterpolator());
+ anim.setDuration(mDozeParameters.getPulseInDuration());
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCallback != null) {
+ mCallback.onDisplayBlanked();
+ mScreenBlankingCallbackCalled = true;
+ }
+ Runnable blankingCallback = () -> {
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+ mBlankScreen = false;
+ // Try again.
+ updateScrims();
+ };
+
+ // Setting power states can happen after we push out the frame. Make sure we
+ // stay fully opaque until the power state request reaches the lower levels.
+ getHandler().postDelayed(blankingCallback, 100);
+
+ }
+ });
+ anim.start();
+ mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+ // Finish animation if we're already at its final state
+ if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+ anim.end();
+ }
+ }
+
+ @VisibleForTesting
+ protected Handler getHandler() {
+ return Handler.getMain();
+ }
+
/**
* Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
* the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +740,13 @@
return alpha * expandFactor;
}
- public void forceHideScrims(boolean hide, boolean animated) {
- mForceHideScrims = hide;
- mAnimateChange = animated;
- scheduleUpdate();
- }
-
- public void dontAnimateBouncerChangesUntilNextFrame() {
- mDontAnimateBouncerChanges = true;
- }
-
public void setExcludedBackgroundArea(Rect area) {
mScrimBehind.setExcludedArea(area);
}
public int getBackgroundColor() {
int color = mLockColors.getMainColor();
- return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+ return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
Color.red(color), Color.green(color), Color.blue(color));
}
@@ -764,27 +775,41 @@
}
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
- ColorExtractor.TYPE_DARK, mKeyguardShowing);
+ ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
mNeedsDrawableColorUpdate = true;
scheduleUpdate();
}
}
- public void dump(PrintWriter pw) {
- pw.println(" ScrimController:");
+ @VisibleForTesting
+ protected WakeLock createWakeLock() {
+ return new DelayedWakeLock(getHandler(),
+ WakeLock.createPartial(mContext, "Doze"));
+ }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(" ScrimController:");
+ pw.print(" state:"); pw.println(mState);
pw.print(" frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
pw.print(" backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
- pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
- pw.print(" mBouncerShowing="); pw.println(mBouncerShowing);
pw.print(" mTracking="); pw.println(mTracking);
- pw.print(" mForceHideScrims="); pw.println(mForceHideScrims);
+ }
+
+ public interface Callback {
+ default void onStart() {
+ }
+ default void onDisplayBlanked() {
+ }
+ default void onFinished() {
+ }
+ default void onCancelled() {
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+ /**
+ * Initial state.
+ */
+ UNINITIALIZED,
+
+ /**
+ * On the lock screen.
+ */
+ KEYGUARD {
+
+ @Override
+ public void prepare(ScrimState previousState) {
+ // DisplayPowerManager will blank the screen, we'll just
+ // set our scrim to black in this frame to avoid flickering and
+ // fade it out afterwards.
+ mBlankScreen = previousState == ScrimState.AOD;
+ if (previousState == ScrimState.AOD) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Showing password challenge.
+ */
+ BOUNCER {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+ mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
+ },
+
+ /**
+ * Changing screen brightness from quick settings.
+ */
+ BRIGHTNESS_MIRROR {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ }
+ },
+
+ /**
+ * Always on display or screen off.
+ */
+ AOD {
+ @Override
+ public void prepare(ScrimState previousState) {
+ if (previousState == ScrimState.PULSING) {
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+ mBlankScreen = previousState == ScrimState.PULSING;
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ // DisplayPowerManager will blank the screen for us, we just need
+ // to set our state.
+ mAnimateChange = false;
+ }
+ },
+
+ /**
+ * When phone wakes up because you received a notification.
+ */
+ PULSING {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 1;
+ mCurrentInFrontAlpha = 0;
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ }
+ },
+
+ /**
+ * Unlocked on top of an app (launcher or any other activity.)
+ */
+ UNLOCKED {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mCurrentBehindAlpha = 0;
+ mCurrentInFrontAlpha = 0;
+ mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+ if (previousState == ScrimState.AOD) {
+ // Fade from black to transparent when coming directly from AOD
+ updateScrimColor(mScrimInFront, 1, Color.BLACK);
+ updateScrimColor(mScrimBehind, 1, Color.BLACK);
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.BLACK;
+ mCurrentBehindTint = Color.BLACK;
+ mBlankScreen = true;
+ } else {
+ // Scrims should still be black at the end of the transition.
+ mCurrentInFrontTint = Color.TRANSPARENT;
+ mCurrentBehindTint = Color.TRANSPARENT;
+ mBlankScreen = false;
+ }
+ }
+ };
+
+ boolean mBlankScreen = false;
+ long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+ int mCurrentInFrontTint = Color.TRANSPARENT;
+ int mCurrentBehindTint = Color.TRANSPARENT;
+ boolean mAnimateChange = true;
+ float mCurrentInFrontAlpha;
+ float mCurrentBehindAlpha;
+ float mAodFrontScrimAlpha;
+ float mScrimBehindAlphaKeyguard;
+ ScrimView mScrimInFront;
+ ScrimView mScrimBehind;
+ DozeParameters mDozeParameters;
+
+ public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+ mScrimInFront = scrimInFront;
+ mScrimBehind = scrimBehind;
+ mDozeParameters = dozeParameters;
+ }
+
+ public void prepare(ScrimState previousState) {
+ }
+
+ public float getFrontAlpha() {
+ return mCurrentInFrontAlpha;
+ }
+
+ public float getBehindAlpha() {
+ return mCurrentBehindAlpha;
+ }
+
+ public int getFrontTint() {
+ return mCurrentInFrontTint;
+ }
+
+ public int getBehindTint() {
+ return mCurrentBehindTint;
+ }
+
+ public long getAnimationDuration() {
+ return mAnimationDuration;
+ }
+
+ public boolean getBlanksScreen() {
+ return mBlankScreen;
+ }
+
+ public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+ (int) (alpha * 255));
+
+ Trace.traceCounter(Trace.TRACE_TAG_APP,
+ scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+ Color.alpha(tint));
+
+ scrim.setTint(tint);
+ scrim.setViewAlpha(alpha);
+ }
+
+ public boolean getAnimateChange() {
+ return mAnimateChange;
+ }
+
+ public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+ mAodFrontScrimAlpha = aodFrontScrimAlpha;
+ }
+
+ public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 43cc0f7..dc8100f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -238,6 +238,7 @@
.OnChildLocationsChangedListener;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
import com.android.systemui.volume.VolumeComponent;
import java.io.FileDescriptor;
@@ -386,6 +387,7 @@
private VolumeComponent mVolumeComponent;
private BrightnessMirrorController mBrightnessMirrorController;
+ private boolean mBrightnessMirrorVisible;
protected FingerprintUnlockController mFingerprintUnlockController;
private LightBarController mLightBarController;
protected LockscreenWallpaper mLockscreenWallpaper;
@@ -646,6 +648,31 @@
}
};
+ // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+ // this animation is tied to the scrim for historic reasons.
+ // TODO: notify when keyguard has faded away instead of the scrim.
+ private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+ .Callback() {
+ @Override
+ public void onFinished() {
+ notifyKeyguardState();
+ }
+
+ @Override
+ public void onCancelled() {
+ notifyKeyguardState();
+ }
+
+ private void notifyKeyguardState() {
+ if (mStatusBarKeyguardViewManager == null) {
+ Log.w(TAG, "Tried to notify keyguard visibility when "
+ + "mStatusBarKeyguardViewManager was null");
+ return;
+ }
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ };
+
private NotificationMessagingUtil mMessagingUtil;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private UserSwitcherController mUserSwitcherController;
@@ -1045,7 +1072,7 @@
if (mStatusBarWindowManager != null) {
mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
}
- });
+ }, new DozeParameters(mContext));
if (mScrimSrcModeEnabled) {
Runnable runnable = () -> {
boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1081,7 +1108,10 @@
final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
mIconController);
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
- mScrimController);
+ (visible) -> {
+ mBrightnessMirrorVisible = visible;
+ updateScrimController();
+ });
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
@@ -1457,8 +1487,7 @@
mDozeScrimController, keyguardViewMediator,
mScrimController, this, UnlockMethodCache.getInstance(mContext));
mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
- getBouncerContainer(), mScrimController,
- mFingerprintUnlockController);
+ getBouncerContainer(), mFingerprintUnlockController);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -2640,7 +2669,7 @@
}
public boolean isPulsing() {
- return mDozeScrimController.isPulsing();
+ return mDozeScrimController != null && mDozeScrimController.isPulsing();
}
@Override
@@ -3348,7 +3377,7 @@
}
if (mScrimController != null) {
- mScrimController.dump(pw);
+ mScrimController.dump(fd, pw, args);
}
if (DUMPTRUCK) {
@@ -3413,7 +3442,19 @@
private void addStatusBarWindow() {
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
- mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+ mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+ public void setRemoteInputActive(NotificationData.Entry entry,
+ boolean remoteInputActive) {
+ mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+ }
+ public void lockScrollTo(NotificationData.Entry entry) {
+ mStackScroller.lockScrollTo(entry.row);
+ }
+ public void requestDisallowLongPressAndDismiss() {
+ mStackScroller.requestDisallowLongPress();
+ mStackScroller.requestDisallowDismiss();
+ }
+ });
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
@@ -4081,7 +4122,6 @@
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mLaunchTransitionFadingAway = false;
- mScrimController.forceHideScrims(false /* hide */, false /* animated */);
updateMediaMetaData(true /* metaDataChanged */, true);
}
@@ -4114,7 +4154,7 @@
if (beforeFading != null) {
beforeFading.run();
}
- mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+ updateScrimController();
updateMediaMetaData(false, true);
mNotificationPanel.setAlpha(1);
mStackScroller.setParentNotFullyVisible(true);
@@ -4145,6 +4185,13 @@
.setStartDelay(0)
.setDuration(FADE_KEYGUARD_DURATION_PULSING)
.setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }
+ })
.start();
}
@@ -4152,7 +4199,6 @@
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mScrimController.animateKeyguardUnoccluding(500);
mNotificationPanel.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -4340,11 +4386,6 @@
mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
}
}
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
- mScrimController.setKeyguardShowing(true);
- } else {
- mScrimController.setKeyguardShowing(false);
- }
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
updateTheme();
updateDozingState();
@@ -4352,6 +4393,7 @@
updateStackScrollerState(goingToFullShade, fromShadeLocked);
updateNotifications();
checkBarModes();
+ updateScrimController();
updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
mUnlockMethodCache.isMethodSecure(),
@@ -4415,11 +4457,10 @@
boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
mNotificationPanel.setDozing(mDozing, animate);
mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
- mScrimController.setDozing(mDozing);
+ mDozeScrimController.setDozing(mDozing);
mKeyguardIndicationController.setDozing(mDozing);
mNotificationPanel.setDark(mDozing, animate);
updateQsExpansionEnabled();
- mDozeScrimController.setDozing(mDozing, animate);
updateRowStates();
Trace.endSection();
}
@@ -4914,6 +4955,7 @@
if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
updateHideIconsForBouncer(true /* animate */);
recomputeDisableFlags(true /* animate */);
+ updateScrimController();
}
public void cancelCurrentTouch() {
@@ -4965,12 +5007,10 @@
mStackScroller.setAnimationsEnabled(true);
mVisualStabilityManager.setScreenOn(true);
mNotificationPanel.setTouchDisabled(false);
-
- maybePrepareWakeUpFromAod();
-
mDozeServiceHost.stopDozing();
updateVisibleToUser();
updateIsKeyguard();
+ updateScrimController();
}
};
@@ -4980,18 +5020,16 @@
mFalsingManager.onScreenTurningOn();
mNotificationPanel.onScreenTurningOn();
- maybePrepareWakeUpFromAod();
-
if (mLaunchCameraOnScreenTurningOn) {
mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
mLaunchCameraOnScreenTurningOn = false;
}
+
+ updateScrimController();
}
@Override
public void onScreenTurnedOn() {
- mScrimController.wakeUpFromAod();
- mDozeScrimController.onScreenTurnedOn();
}
@Override
@@ -5009,13 +5047,6 @@
return mWakefulnessLifecycle.getWakefulness();
}
- private void maybePrepareWakeUpFromAod() {
- int wakefulness = mWakefulnessLifecycle.getWakefulness();
- if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
- mScrimController.prepareWakeUpFromAod();
- }
- }
-
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5098,12 +5129,12 @@
if (!mDeviceInteractive) {
// Avoid flickering of the scrim when we instant launch the camera and the bouncer
// comes on.
- mScrimController.dontAnimateBouncerChangesUntilNextFrame();
mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
}
if (isScreenTurningOnOrOn()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
// we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5145,15 +5176,16 @@
private void updateDozing() {
Trace.beginSection("StatusBar#updateDozing");
// When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
- mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+ boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
|| mFingerprintUnlockController.getMode()
== FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to mState
// but we still should not be dozing, manually set to false.
if (mFingerprintUnlockController.getMode() ==
FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
- mDozing = false;
+ dozing = false;
}
+ mDozing = dozing;
mStatusBarWindowManager.setDozing(mDozing);
mStatusBarKeyguardViewManager.setDozing(mDozing);
if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5163,6 +5195,24 @@
Trace.endSection();
}
+ public void updateScrimController() {
+ if (mBouncerShowing) {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ } else if (mBrightnessMirrorVisible) {
+ mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+ } else if (isPulsing()) {
+ // Handled in DozeScrimController#setPulsing
+ } else if (mDozing) {
+ mScrimController.transitionTo(ScrimState.AOD);
+ } else if (mIsKeyguard) {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ } else {
+ mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+ }
+ }
+
public boolean isKeyguardShowing() {
if (mStatusBarKeyguardViewManager == null) {
Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5222,7 +5272,6 @@
}
mDozeScrimController.pulse(new PulseCallback() {
-
@Override
public void onPulseStarted() {
callback.onPulseStarted();
@@ -5307,11 +5356,6 @@
}
@Override
- public void abortPulsing() {
- mDozeScrimController.abortPulsing();
- }
-
- @Override
public void extendPulse() {
mDozeScrimController.extendPulse();
}
@@ -5347,7 +5391,7 @@
@Override
public void setAodDimmingScrim(float scrimOpacity) {
- mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+ ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
}
public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bcde556..ef05bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.os.Bundle;
import android.os.SystemClock;
-import android.os.Trace;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -71,17 +70,14 @@
protected final Context mContext;
private final StatusBarWindowManager mStatusBarWindowManager;
- private final boolean mDisplayBlanksAfterDoze;
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private ScrimController mScrimController;
private FingerprintUnlockController mFingerprintUnlockController;
private ViewGroup mContainer;
- private boolean mScreenTurnedOn;
protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
@@ -95,12 +91,10 @@
private boolean mLastBouncerDismissible;
protected boolean mLastRemoteInputActive;
private boolean mLastDozing;
- private boolean mLastDeferScrimFadeOut;
private int mLastFpMode;
private OnDismissAction mAfterKeyguardGoneAction;
private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
- private boolean mDeferScrimFadeOut;
// Dismiss action to be launched when we stop dozing or the keyguard is gone.
private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
mLockPatternUtils = lockPatternUtils;
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
- mDisplayBlanksAfterDoze = context.getResources().getBoolean(
- com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- ScrimController scrimController,
FingerprintUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry) {
mStatusBar = statusBar;
mContainer = container;
- mScrimController = scrimController;
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
- mScrimController.abortKeyguardFadingOut();
reset(true /* hideBouncerWhenShowing */);
}
@@ -253,15 +242,7 @@
}
public void onScreenTurnedOn() {
- Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
- mScreenTurnedOn = true;
- if (mDeferScrimFadeOut) {
- mDeferScrimFadeOut = false;
- animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- updateStates();
- }
- Trace.endSection();
+ // TODO: remove
}
@Override
@@ -285,7 +266,7 @@
}
public void onScreenTurnedOff() {
- mScreenTurnedOn = false;
+ // TODO: remove
}
public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
mStatusBarWindowManager.setKeyguardFadingAway(true);
hideBouncer(true /* destroyView */);
updateStates();
- mScrimController.animateKeyguardFadingOut(
- StatusBar.FADE_KEYGUARD_START_DELAY,
- StatusBar.FADE_KEYGUARD_DURATION, null,
- false /* skipFirstFrame */);
}
}, new Runnable() {
@Override
@@ -400,36 +377,16 @@
mFingerprintUnlockController.startKeyguardFadingAway();
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
- mStatusBarWindowManager.setKeyguardFadingAway(true);
mStatusBar.fadeKeyguardWhilePulsing();
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+ wakeAndUnlockDejank();
} else {
mFingerprintUnlockController.startKeyguardFadingAway();
mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
boolean staying = mStatusBar.hideKeyguard();
if (!staying) {
mStatusBarWindowManager.setKeyguardFadingAway(true);
- if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
- boolean turnedOnSinceAuth =
- mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
- if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
- // Not ready to animate yet; either because the screen is not on yet,
- // or it is on but will turn off before waking out of doze.
- mDeferScrimFadeOut = true;
- } else {
-
- // Screen is already on, don't defer with fading out.
- animateScrimControllerKeyguardFadingOut(0,
- WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
- true /* skipFirstFrame */);
- }
- } else {
- animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
- false /* skipFirstFrame */);
- }
+ wakeAndUnlockDejank();
} else {
- mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
mStatusBar.finishKeyguardFadingAway();
mFingerprintUnlockController.finishKeyguardFadingAway();
}
@@ -449,30 +406,17 @@
mBouncer.prepare();
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- boolean skipFirstFrame) {
- animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
- skipFirstFrame);
+ public void onKeyguardFadedAway() {
+ mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+ 100);
+ mStatusBar.finishKeyguardFadingAway();
+ mFingerprintUnlockController.finishKeyguardFadingAway();
+ WindowManagerGlobal.getInstance().trimMemory(
+ ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
}
- private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
- final Runnable endRunnable, boolean skipFirstFrame) {
- Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
- @Override
- public void run() {
- if (endRunnable != null) {
- endRunnable.run();
- }
- mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
- 100);
- mStatusBar.finishKeyguardFadingAway();
- mFingerprintUnlockController.finishKeyguardFadingAway();
- WindowManagerGlobal.getInstance().trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
- Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
- }
- }, skipFirstFrame);
+ private void wakeAndUnlockDejank() {
if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
&& LatencyTracker.isEnabled(mContext)) {
DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
mStatusBar.setBouncerShowing(bouncerShowing);
- mScrimController.setBouncerShowing(bouncerShowing);
}
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
mLastBouncerDismissible = bouncerDismissible;
mLastRemoteInputActive = remoteInputActive;
mLastDozing = mDozing;
- mLastDeferScrimFadeOut = mDeferScrimFadeOut;
mLastFpMode = mFingerprintUnlockController.getMode();
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
@@ -624,7 +566,7 @@
boolean keyguardShowing = mShowing && !mOccluded;
boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive) && !mDeferScrimFadeOut;
+ || mRemoteInputActive);
}
/**
@@ -634,7 +576,7 @@
boolean keyguardShowing = mLastShowing && !mLastOccluded;
boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+ || mLastRemoteInputActive);
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index b0553d7..a011952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.policy;
+import android.annotation.NonNull;
import android.util.ArraySet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -26,46 +27,47 @@
import com.android.internal.util.Preconditions;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import java.util.function.Consumer;
+
/**
* Controls showing and hiding of the brightness mirror.
*/
public class BrightnessMirrorController
implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
- private final NotificationStackScrollLayout mStackScroller;
- public long TRANSITION_DURATION_OUT = 150;
- public long TRANSITION_DURATION_IN = 200;
+ private final static long TRANSITION_DURATION_OUT = 150;
+ private final static long TRANSITION_DURATION_IN = 200;
private final StatusBarWindowView mStatusBarWindow;
- private final ScrimController mScrimController;
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Consumer<Boolean> mVisibilityCallback;
private final View mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
- ScrimController scrimController) {
+ @NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
- mScrimController = scrimController;
+ mVisibilityCallback = visibilityCallback;
}
public void showMirror() {
mBrightnessMirror.setVisibility(View.VISIBLE);
mStackScroller.setFadingOut(true);
- mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+ mVisibilityCallback.accept(true);
outAnimation(mNotificationPanel.animate())
.withLayer();
}
public void hideMirror() {
- mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+ mVisibilityCallback.accept(false);
inAnimation(mNotificationPanel.animate())
.withLayer()
.withEndAction(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de4..4fc50442 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
import com.android.systemui.statusbar.stack.StackStateAnimator;
/**
@@ -82,8 +80,6 @@
private NotificationData.Entry mEntry;
- private ScrollContainer mScrollContainer;
- private View mScrollContainerChild;
private boolean mRemoved;
private int mRevealCx;
@@ -347,41 +343,16 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- findScrollContainer();
- if (mScrollContainer != null) {
- mScrollContainer.requestDisallowLongPress();
- mScrollContainer.requestDisallowDismiss();
- }
+ mController.requestDisallowLongPressAndDismiss();
}
return super.onInterceptTouchEvent(ev);
}
public boolean requestScrollTo() {
- findScrollContainer();
- mScrollContainer.lockScrollTo(mScrollContainerChild);
+ mController.lockScrollTo(mEntry);
return true;
}
- private void findScrollContainer() {
- if (mScrollContainer == null) {
- mScrollContainerChild = null;
- ViewParent p = this;
- while (p != null) {
- if (mScrollContainerChild == null && p instanceof ExpandableView) {
- mScrollContainerChild = (View) p;
- }
- if (p.getParent() instanceof ScrollContainer) {
- mScrollContainer = (ScrollContainer) p.getParent();
- if (mScrollContainerChild == null) {
- mScrollContainerChild = (View) p;
- }
- break;
- }
- p = p.getParent();
- }
- }
- }
-
public boolean isActive() {
return mEditText.isFocused() && mEditText.isEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf..f5ae88b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@
avatar = new UserIconDrawable(avatarSize)
.setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
} else {
- avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+ avatar = UserIcons.getDefaultUserIcon(
+ context.getResources(),
+ isGuest? UserHandle.USER_NULL : userId,
lightIcon);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 0f498bc..7006d38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@
if (item.isAddUser) {
return context.getDrawable(R.drawable.ic_add_circle_qs);
}
- Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+ Drawable icon = UserIcons.getDefaultUserIcon(
+ context.getResources(), item.resolveId(), /* light= */ false);
if (item.isGuest) {
icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
Mode.SRC_IN);
@@ -959,7 +960,7 @@
}
int id = user.id;
Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
- id, /* light= */ false));
+ mContext.getResources(), id, /* light= */ false));
mUserManager.setUserIcon(id, icon);
switchToUserId(id);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d..2f6e658 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@
* @return an animation filter for this animation.
*/
public AnimationFilter getAnimationFilter() {
- return new AnimationFilter();
+ return new AnimationFilter() {
+ @Override
+ public boolean shouldAnimateProperty(Property property) {
+ return true;
+ }
+ };
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104..c0241e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@
public boolean isUserLocked() {
return mUserLocked;
}
+
+ public void setCurrentBottomRoundness(float currentBottomRoundness) {
+ boolean last = true;
+ for (int i = mChildren.size() - 1; i >= 0; i--) {
+ ExpandableNotificationRow child = mChildren.get(i);
+ if (child.getVisibility() == View.GONE) {
+ continue;
+ }
+ float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+ child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+ last = false;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049a..ebebfac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
- NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
- VisibilityLocationProvider {
+ NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@
* Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
+ private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+ "sidePaddings",
+ NotificationStackScrollLayout::setCurrentSidePadding,
+ NotificationStackScrollLayout::getCurrentSidePadding,
+ R.id.side_padding_animator_tag,
+ R.id.side_padding_animator_end_tag,
+ R.id.side_padding_animator_start_tag);
+ private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private ExpandHelper mExpandHelper;
private NotificationSwipeHelper mSwipeHelper;
private boolean mSwipingInProgress;
private int mCurrentStackHeight = Integer.MAX_VALUE;
private final Paint mBackgroundPaint = new Paint();
+ private final Path mBackgroundPath = new Path();
+ private final float[] mBackgroundRadii = new float[8];
private final boolean mShouldDrawNotificationBackground;
private float mExpandedHeight;
@@ -157,6 +171,7 @@
private int mTopPadding;
private int mBottomMargin;
private int mBottomInset = 0;
+ private float mCurrentSidePadding;
/**
* The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@
private int mCachedBackgroundColor;
private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
private Runnable mAnimateScroll = this::animateScroll;
+ private int mCornerRadius;
+ private int mLockscreenSidePaddings;
+ private int mSidePaddings;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -419,6 +437,8 @@
res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
updateWillNotDraw();
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
if (DEBUG) {
mDebugPaint = new Paint();
mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
&& mCurrentBounds.top < mCurrentBounds.bottom) {
- canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
- mBackgroundPaint);
+ canvas.drawPath(mBackgroundPath, mBackgroundPaint);
}
if (DEBUG) {
@@ -520,8 +539,12 @@
R.dimen.min_top_overscroll_to_qs);
mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mLockscreenSidePaddings = res.getDimensionPixelSize(
+ R.dimen.notification_lockscreen_side_paddings);
mMinInteractionHeight = res.getDimensionPixelSize(
R.dimen.notification_min_interaction_height);
+ mCornerRadius = res.getDimensionPixelSize(
+ Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
}
public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@
mScrollingEnabled = enable;
}
- @Override
public void lockScrollTo(View v) {
if (mForcedScroll == v) {
return;
@@ -1204,7 +1226,6 @@
scrollTo(v);
}
- @Override
public boolean scrollTo(View v) {
ExpandableView expandableView = (ExpandableView) v;
int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@
mScrimController.setExcludedBackgroundArea(
mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
: mCurrentBounds);
+ updateBackgroundPath();
invalidate();
}
+ private void updateBackgroundPath() {
+ mBackgroundPath.reset();
+ float topRoundness = 0;
+ if (mFirstVisibleBackgroundChild != null) {
+ topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+ }
+ topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+ float bottomRoundNess = mCornerRadius;
+ mBackgroundRadii[0] = topRoundness;
+ mBackgroundRadii[1] = topRoundness;
+ mBackgroundRadii[2] = topRoundness;
+ mBackgroundRadii[3] = topRoundness;
+ mBackgroundRadii[4] = bottomRoundNess;
+ mBackgroundRadii[5] = bottomRoundNess;
+ mBackgroundRadii[6] = bottomRoundNess;
+ mBackgroundRadii[7] = bottomRoundNess;
+ mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+ getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+ Path.Direction.CCW);
+ }
+
/**
* Update the background bounds to the new desired bounds
*/
@@ -2236,6 +2279,8 @@
mBackgroundBounds.left = mTempInt2[0];
mBackgroundBounds.right = mTempInt2[0] + getWidth();
}
+ mBackgroundBounds.left += mCurrentSidePadding;
+ mBackgroundBounds.right -= mCurrentSidePadding;
if (!mIsExpanded) {
mBackgroundBounds.top = 0;
mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@
private void updateFirstAndLastBackgroundViews() {
ActivatableNotificationView firstChild = getFirstChildWithBackground();
ActivatableNotificationView lastChild = getLastChildWithBackground();
+ boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+ boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
if (mAnimationsEnabled && mIsExpanded) {
- mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
- mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+ mAnimateNextBackgroundTop = firstChanged;
+ mAnimateNextBackgroundBottom = lastChanged;
} else {
mAnimateNextBackgroundTop = false;
mAnimateNextBackgroundBottom = false;
}
+ if (firstChanged && mFirstVisibleBackgroundChild != null
+ && !mFirstVisibleBackgroundChild.isRemoved()) {
+ mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+ mFirstVisibleBackgroundChild.isShown());
+ }
+ if (lastChanged && mLastVisibleBackgroundChild != null
+ && !mLastVisibleBackgroundChild.isRemoved()) {
+ mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+ mLastVisibleBackgroundChild.isShown());
+ }
mFirstVisibleBackgroundChild = firstChild;
mLastVisibleBackgroundChild = lastChild;
mAmbientState.setLastVisibleBackgroundChild(lastChild);
+ applyRoundedNess();
+ }
+
+ private void applyRoundedNess() {
+ if (mFirstVisibleBackgroundChild != null) {
+ mFirstVisibleBackgroundChild.setTopRoundness(
+ mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+ mFirstVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+ }
+ if (mLastVisibleBackgroundChild != null) {
+ mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+ mLastVisibleBackgroundChild.isShown()
+ && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+ }
+ updateBackgroundPath();
+ invalidate();
}
private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@
generateAddAnimation(child, false /* fromMoreCard */);
updateAnimationState(child);
updateChronometerForChild(child);
+ updateCurrentSidePaddings(child);
}
private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@
}
}
- @Override
public void requestDisallowLongPress() {
cancelLongPress();
}
- @Override
public void requestDisallowDismiss() {
mDisallowDismissInThisMotion = true;
}
@@ -4285,6 +4358,43 @@
public void setStatusBarState(int statusBarState) {
mStatusBarState = statusBarState;
mAmbientState.setStatusBarState(statusBarState);
+ applyRoundedNess();
+ updateSidePaddings();
+ }
+
+ private void updateSidePaddings() {
+ int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+ if (sidePaddings != mSidePaddings) {
+ boolean animate = isShown();
+ mSidePaddings = sidePaddings;
+ PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+ SIDE_PADDING_PROPERTIES, animate);
+ }
+ }
+
+ protected void setCurrentSidePadding(float sidePadding) {
+ mCurrentSidePadding = sidePadding;
+ updateBackground();
+ applySidePaddingsToChildren();
+ }
+
+ private void applySidePaddingsToChildren() {
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ updateCurrentSidePaddings(view);
+ }
+ }
+
+ private void updateCurrentSidePaddings(View view) {
+ if (!(view instanceof ExpandableOutlineView)) {
+ return;
+ }
+ ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+ outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+ }
+
+ protected float getCurrentSidePadding() {
+ return mCurrentSidePadding;
}
public void setExpandingVelocity(float expandingVelocity) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
- /**
- * Request that the view does not perform long press for the current touch.
- */
- void requestDisallowLongPress();
-
- /**
- * Request that the view is made visible by scrolling to it.
- * Return true if it scrolls.
- */
- boolean scrollTo(View v);
-
- /**
- * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
- * scrolls, or {@param v} loses focus or is detached.
- */
- void lockScrollTo(View v);
-
- /**
- * Request that the view does not dismiss for the current touch.
- */
- void requestDisallowDismiss();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 27b730cd..682b849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
-import android.app.Notification;
import android.util.Property;
import android.view.View;
import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -64,8 +63,8 @@
private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
- private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_X_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -88,8 +87,8 @@
}
};
- private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
- = new PropertyAnimator.AnimatableProperty() {
+ private static final AnimatableProperty SCALE_Y_PROPERTY
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
@@ -251,7 +250,7 @@
return getChildTag(view, tag) != null;
}
- public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+ public static boolean isAnimating(View view, AnimatableProperty property) {
return getChildTag(view, property.getAnimatorTag()) != null;
}
@@ -403,7 +402,7 @@
startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
}
- private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+ private void updateAnimation(View view, AnimatableProperty property,
float endValue) {
PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 0d41e20..383d327 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -441,7 +441,8 @@
.withEndAction(() -> mDialog.dismiss())
.setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
.start();
- if (mAccessibilityMgr.isEnabled()) {
+ if (mAccessibilityMgr.isObservedEventType(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
event.setPackageName(mContext.getPackageName());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index b0c9f328..1c104cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -19,12 +19,13 @@
import android.annotation.NonNull;
import android.app.PendingIntent;
+import com.android.systemui.util.wakelock.WakeLock;
+
/**
* A rudimentary fake for DozeHost.
*/
class DozeHostFake implements DozeHost {
Callback callback;
- boolean pulseAborted;
boolean pulseExtended;
boolean animateWakeup;
boolean dozing;
@@ -92,11 +93,6 @@
}
@Override
- public void abortPulsing() {
- pulseAborted = true;
- }
-
- @Override
public void extendPulse() {
pulseExtended = true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 004ff29..1c9c794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -49,6 +49,7 @@
import com.android.systemui.qs.QSTileHost;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@@ -108,6 +109,7 @@
}
@Test
+ @Ignore("flaky")
public void testStaleTimeout() throws InterruptedException {
when(mTile.getStaleTimeout()).thenReturn(5l);
clearInvocations(mTile);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
index 6e59d10..2ff86c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationCustomViewWrapperTest.java
@@ -39,9 +39,8 @@
private ExpandableNotificationRow mRow;
@Before
- @UiThreadTest
- public void setUp() {
- mRow = new ExpandableNotificationRow(mContext, null);
+ public void setUp() throws Exception {
+ mRow = new NotificationTestHelper(mContext).createRow();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
index 4c3bf10..42dad11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ScrimViewTest.java
@@ -86,9 +86,9 @@
@Test
public void testSetViewAlpha_propagatesToDrawable() {
- float alpha = 0.5f;
+ final float alpha = 0.5f;
mView.setViewAlpha(alpha);
- assertEquals(mView.getViewAlpha(), alpha);
+ assertEquals("View alpha did not propagate to drawable", alpha, mView.getViewAlpha());
}
@Test
@@ -97,7 +97,7 @@
Canvas canvas = mock(Canvas.class);
mView.onDraw(canvas);
// One time for each rect side
- verify(canvas, times(4)).clipRect(anyInt(), anyInt(), anyInt(), anyInt());
+ verify(canvas, times(8)).clipRect(anyInt(), anyInt(), anyInt(), anyInt());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
index eaa073c..a153140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java
@@ -35,7 +35,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.stack.AnimationFilter;
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ViewState;
@@ -63,8 +62,8 @@
return mValue;
}
};
- private PropertyAnimator.AnimatableProperty mProperty
- = new PropertyAnimator.AnimatableProperty() {
+ private AnimatableProperty mProperty
+ = new AnimatableProperty() {
@Override
public int getAnimationStartTag() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
new file mode 100644
index 0000000..ca2f713
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.Debug;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.DozeHost;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class DozeScrimControllerTest extends SysuiTestCase {
+
+ private ScrimController mScrimController;
+ private DozeScrimController mDozeScrimController;
+
+ @Before
+ public void setup() {
+ mScrimController = mock(ScrimController.class);
+ // Make sure callbacks will be invoked to complete the lifecycle.
+ doAnswer(invocationOnMock -> {
+ ScrimController.Callback callback = invocationOnMock.getArgument(1);
+ callback.onStart();
+ callback.onDisplayBlanked();
+ callback.onFinished();
+ return null;
+ }).when(mScrimController).transitionTo(any(ScrimState.class),
+ any(ScrimController.Callback.class));
+
+ mDozeScrimController = new DozeScrimController(mScrimController, getContext());
+ mDozeScrimController.setDozing(true);
+ }
+
+ @Test
+ public void changesScrimControllerState() {
+ mDozeScrimController.pulse(mock(DozeHost.PulseCallback.class), 0);
+ verify(mScrimController).transitionTo(eq(ScrimState.PULSING),
+ any(ScrimController.Callback.class));
+ }
+
+ @Test
+ public void callsPulseCallback() {
+ DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback, 0);
+
+ verify(callback).onPulseStarted();
+ mDozeScrimController.pulseOutNow();
+ verify(callback).onPulseFinished();
+ }
+
+ @Test
+ public void secondPulseIsSuppressed() {
+ DozeHost.PulseCallback callback1 = mock(DozeHost.PulseCallback.class);
+ DozeHost.PulseCallback callback2 = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback1, 0);
+ mDozeScrimController.pulse(callback2, 0);
+
+ verify(callback1, never()).onPulseFinished();
+ verify(callback2).onPulseFinished();
+ }
+
+ @Test
+ public void suppressesPulseIfNotDozing() {
+ mDozeScrimController.setDozing(false);
+ DozeHost.PulseCallback callback = mock(DozeHost.PulseCallback.class);
+ mDozeScrimController.pulse(callback, 0);
+
+ verify(callback).onPulseFinished();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
new file mode 100644
index 0000000..b9f695b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.util.wakelock.WakeLock;
+import com.android.systemui.utils.os.FakeHandler;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ScrimControllerTest extends SysuiTestCase {
+
+ private SynchronousScrimController mScrimController;
+ private ScrimView mScrimBehind;
+ private ScrimView mScrimInFront;
+ private View mHeadsUpScrim;
+ private Consumer<Boolean> mScrimVisibilityCallback;
+ private Boolean mScrimVisibile;
+ private LightBarController mLightBarController;
+ private DozeParameters mDozeParamenters;
+ private WakeLock mWakeLock;
+ private boolean mAlwaysOnEnabled;
+
+ @Before
+ public void setup() {
+ mLightBarController = mock(LightBarController.class);
+ mScrimBehind = new ScrimView(getContext());
+ mScrimInFront = new ScrimView(getContext());
+ mHeadsUpScrim = mock(View.class);
+ mWakeLock = mock(WakeLock.class);
+ mAlwaysOnEnabled = true;
+ mScrimVisibilityCallback = (Boolean visible) -> mScrimVisibile = visible;
+ mDozeParamenters = mock(DozeParameters.class);
+ when(mDozeParamenters.getAlwaysOn()).thenAnswer(invocation -> mAlwaysOnEnabled);
+ mScrimController = new SynchronousScrimController(mLightBarController, mScrimBehind,
+ mScrimInFront, mHeadsUpScrim, mScrimVisibilityCallback, mDozeParamenters);
+ }
+
+ @Test
+ public void initialState() {
+ Assert.assertEquals("ScrimController should start initialized",
+ mScrimController.getState(), ScrimState.UNINITIALIZED);
+ }
+
+ @Test
+ public void transitionToKeyguard() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible without tint
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void transitionToAod() {
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ }
+
+ @Test
+ public void transitionToPulsing() {
+ mScrimController.transitionTo(ScrimState.PULSING);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ // Pulse callback should have been invoked
+ assertScrimVisibility(false /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ }
+
+ @Test
+ public void transitionToBouncer() {
+ mScrimController.transitionTo(ScrimState.BOUNCER);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible without tint
+ assertScrimVisibility(true /* front */, true /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void transitionToUnlocked() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be transparent
+ assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ assertScrimTint(mScrimInFront, false /* tinted */);
+
+ // Back scrim should be visible after start dragging
+ mScrimController.setPanelExpansion(0.5f);
+ assertScrimVisibility(false /* front */, true /* behind */);
+ }
+
+ @Test
+ public void transitionToUnlockedFromAod() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ // Immediately tinted after the transition starts
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be transparent
+ // Neither scrims should be tinted anymore after the animation.
+ assertScrimVisibility(false /* front */, false /* behind */);
+ assertScrimTint(mScrimInFront, false /* tinted */);
+ assertScrimTint(mScrimBehind, false /* tinted */);
+ }
+
+ @Test
+ public void scrimBlanksBeforeLeavingAoD() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED,
+ new ScrimController.Callback() {
+ @Override
+ public void onDisplayBlanked() {
+ // Front scrim should be black in the middle of the transition
+ Assert.assertTrue("Scrim should be visible during transition. Alpha: "
+ + mScrimInFront.getViewAlpha(), mScrimInFront.getViewAlpha() > 0);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ Assert.assertTrue("Scrim should be visible during transition.",
+ mScrimVisibile);
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ }
+
+ @Test
+ public void testScrimCallback() {
+ int[] callOrder = {0, 0, 0};
+ int[] currentCall = {0};
+ mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ @Override
+ public void onStart() {
+ callOrder[0] = ++currentCall[0];
+ }
+
+ @Override
+ public void onDisplayBlanked() {
+ callOrder[1] = ++currentCall[0];
+ }
+
+ @Override
+ public void onFinished() {
+ callOrder[2] = ++currentCall[0];
+ }
+ });
+ mScrimController.finishAnimationsImmediately();
+ Assert.assertEquals("onStart called in wrong order", 1, callOrder[0]);
+ Assert.assertEquals("onDisplayBlanked called in wrong order", 2, callOrder[1]);
+ Assert.assertEquals("onFinished called in wrong order", 3, callOrder[2]);
+ }
+
+ @Test
+ public void testScrimCallbacksWithoutAmbientDisplay() {
+ mAlwaysOnEnabled = false;
+ testScrimCallback();
+ }
+
+ @Test
+ public void testScrimCallbackCancelled() {
+ boolean[] cancelledCalled = {false};
+ mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() {
+ @Override
+ public void onCancelled() {
+ cancelledCalled[0] = true;
+ }
+ });
+ mScrimController.transitionTo(ScrimState.PULSING);
+ Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]);
+ }
+
+ @Test
+ public void testHoldsWakeLock() {
+ mScrimController.transitionTo(ScrimState.AOD);
+ verify(mWakeLock, times(1)).acquire();
+ verify(mWakeLock, never()).release();
+ mScrimController.finishAnimationsImmediately();
+ verify(mWakeLock, times(1)).release();
+ }
+
+ private void assertScrimTint(ScrimView scrimView, boolean tinted) {
+ final boolean viewIsTinted = scrimView.getTint() != Color.TRANSPARENT;
+ final String name = scrimView == mScrimInFront ? "front" : "back";
+ Assert.assertEquals("Tint test failed at state " + mScrimController.getState()
+ +" with scrim: " + name + " and tint: " + Integer.toHexString(scrimView.getTint()),
+ tinted, viewIsTinted);
+ }
+
+ private void assertScrimVisibility(boolean inFront, boolean behind) {
+ Assert.assertEquals("Unexpected front scrim visibility. Alpha is "
+ + mScrimInFront.getViewAlpha(), inFront, mScrimInFront.getViewAlpha() > 0);
+ Assert.assertEquals("Unexpected back scrim visibility. Alpha is "
+ + mScrimBehind.getViewAlpha(), behind, mScrimBehind.getViewAlpha() > 0);
+ Assert.assertEquals("Invalid visibility.", inFront || behind, mScrimVisibile);
+ }
+
+ /**
+ * Special version of ScrimController where animations have 0 duration for test purposes.
+ */
+ private class SynchronousScrimController extends ScrimController {
+
+ private FakeHandler mHandler;
+
+ public SynchronousScrimController(LightBarController lightBarController,
+ ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
+ Consumer<Boolean> scrimVisibleListener, DozeParameters dozeParameters) {
+ super(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
+ scrimVisibleListener, dozeParameters);
+ mHandler = new FakeHandler(Looper.myLooper());
+ }
+
+ public void finishAnimationsImmediately() {
+ boolean[] animationFinished = {false};
+ setOnAnimationFinished(()-> animationFinished[0] = true);
+
+ // Execute code that will trigger animations.
+ onPreDraw();
+
+ // Force finish screen blanking.
+ endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK);
+ mHandler.dispatchQueuedMessages();
+ // Force finish all animations.
+ endAnimation(mScrimBehind, TAG_KEY_ANIM);
+ endAnimation(mScrimInFront, TAG_KEY_ANIM);
+
+ if (!animationFinished[0]) {
+ throw new IllegalStateException("Animation never finished");
+ }
+ }
+
+ private void endAnimation(ScrimView scrimView, int tag) {
+ Animator animator = (Animator) scrimView.getTag(tag);
+ if (animator != null) {
+ animator.end();
+ }
+ }
+
+ @Override
+ protected Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ protected WakeLock createWakeLock() {
+ return mWakeLock;
+ }
+ }
+
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
index 7e94d7b..22d922b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -20,9 +20,7 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.annotation.NonNull;
@@ -49,7 +47,6 @@
import android.view.View;
import android.view.accessibility.AccessibilityCache;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -65,6 +62,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -106,7 +104,7 @@
int mFeedbackType;
- Set<String> mPackageNames = new HashSet<>();
+ final Set<String> mPackageNames = new HashSet<>();
boolean mIsDefault;
@@ -284,40 +282,98 @@
return true;
}
- public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
- mEventTypes = info.eventTypes;
- mFeedbackType = info.feedbackType;
- String[] packageNames = info.packageNames;
- if (packageNames != null) {
- mPackageNames.addAll(Arrays.asList(packageNames));
+ boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+ boolean somethingChanged = false;
+
+ if (mEventTypes != info.eventTypes) {
+ mEventTypes = info.eventTypes;
+ somethingChanged = true;
}
- mNotificationTimeout = info.notificationTimeout;
- mIsDefault = (info.flags & DEFAULT) != 0;
+
+ if (mFeedbackType != info.feedbackType) {
+ mFeedbackType = info.feedbackType;
+ somethingChanged = true;
+ }
+
+ final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]);
+ if (!Arrays.equals(oldPackageNames, info.packageNames)) {
+ mPackageNames.clear();
+ if (info.packageNames != null) {
+ Collections.addAll(mPackageNames, info.packageNames);
+ }
+ somethingChanged = true;
+ }
+
+ if (mNotificationTimeout != info.notificationTimeout) {
+ mNotificationTimeout = info.notificationTimeout;
+ somethingChanged = true;
+ }
+
+ final boolean newIsDefault = (info.flags & DEFAULT) != 0;
+ if (mIsDefault != newIsDefault) {
+ mIsDefault = newIsDefault;
+ somethingChanged = true;
+ }
if (supportsFlagForNotImportantViews(info)) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+ }
+
+ somethingChanged |= updateFetchFlag(info.flags,
+ AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS);
+
+ final boolean newRequestTouchExplorationMode = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+ if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) {
+ mRequestTouchExplorationMode = newRequestTouchExplorationMode;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestFilterKeyEvents = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+ if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) {
+ mRequestFilterKeyEvents = newRequestFilterKeyEvents;
+ somethingChanged = true;
+ }
+
+ final boolean newRetrieveInteractiveWindows = (info.flags
+ & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+ if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) {
+ mRetrieveInteractiveWindows = newRetrieveInteractiveWindows;
+ somethingChanged = true;
+ }
+
+ final boolean newCaptureFingerprintGestures = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+ if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) {
+ mCaptureFingerprintGestures = newCaptureFingerprintGestures;
+ somethingChanged = true;
+ }
+
+ final boolean newRequestAccessibilityButton = (info.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (mRequestAccessibilityButton != newRequestAccessibilityButton) {
+ mRequestAccessibilityButton = newRequestAccessibilityButton;
+ somethingChanged = true;
+ }
+
+ return somethingChanged;
+ }
+
+ private boolean updateFetchFlag(int allFlags, int flagToUpdate) {
+ if ((allFlags & flagToUpdate) != 0) {
+ if ((mFetchFlags & flagToUpdate) == 0) {
+ mFetchFlags |= flagToUpdate;
+ return true;
+ }
+ } else {
+ if ((mFetchFlags & flagToUpdate) != 0) {
+ mFetchFlags &= ~flagToUpdate;
+ return true;
}
}
-
- if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
- mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- } else {
- mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
- }
-
- mRequestTouchExplorationMode = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
- mRequestFilterKeyEvents = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
- mRetrieveInteractiveWindows = (info.flags
- & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
- mCaptureFingerprintGestures = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
- mRequestAccessibilityButton = (info.flags
- & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ return false;
}
protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -349,14 +405,15 @@
// If the XML manifest had data to configure the service its info
// should be already set. In such a case update only the dynamically
// configurable properties.
+ final boolean serviceInfoChanged;
AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
if (oldInfo != null) {
oldInfo.updateDynamicallyConfigurableProperties(info);
- setDynamicallyConfigurableProperties(oldInfo);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo);
} else {
- setDynamicallyConfigurableProperties(info);
+ serviceInfoChanged = setDynamicallyConfigurableProperties(info);
}
- mSystemSupport.onClientChange(true);
+ mSystemSupport.onClientChange(serviceInfoChanged);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3554448..8b5c85a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2400,7 +2400,8 @@
private void announceNewUserIfNeeded() {
synchronized (mLock) {
UserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEvents()) {
+ if (userState.isHandlingAccessibilityEvents()
+ && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
@@ -3157,13 +3158,21 @@
if (mWindowsForAccessibilityCallback == null) {
return;
}
+ final int userId;
+ synchronized (mLock) {
+ userId = mCurrentUserId;
+ final UserState userState = getUserStateLocked(userId);
+ if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) {
+ return;
+ }
+ }
final long identity = Binder.clearCallingIdentity();
try {
// Let the client know the windows changed.
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_WINDOWS_CHANGED);
event.setEventTime(SystemClock.uptimeMillis());
- sendAccessibilityEvent(event, mCurrentUserId);
+ sendAccessibilityEvent(event, userId);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -3368,6 +3377,10 @@
mUserId = userId;
}
+ public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+ return (mLastSentRelevantEventTypes & type) != 0;
+ }
+
public int getClientState() {
int clientState = 0;
final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index 3419b80..62017e8 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -791,7 +791,7 @@
*/
private void sendAccessibilityEvent(int type) {
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
- if (accessibilityManager.isEnabled()) {
+ if (accessibilityManager.isObservedEventType(type)) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 3904fc9..ca15249 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -81,6 +80,7 @@
import java.util.Random;
import java.util.TimeZone;
import java.util.TreeSet;
+import java.util.function.Predicate;
import static android.app.AlarmManager.RTC_WAKEUP;
import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@
import static android.app.AlarmManager.ELAPSED_REALTIME;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
class AlarmManagerService extends SystemService {
private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@
final LocalLog mLog = new LocalLog(TAG);
AppOpsManager mAppOps;
- IAppOpsService mAppOpsService;
DeviceIdleController.LocalService mLocalDeviceIdleController;
final Object mLock = new Object();
- ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
- SparseBooleanArray mForegroundUids = new SparseBooleanArray();
// List of alarms per uid deferred due to user applied background restrictions on the source app
SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
long mNativeData;
@@ -184,12 +187,6 @@
int mSystemUiUid;
/**
- * The current set of user whitelisted apps for device idle mode, meaning these are allowed
- * to freely schedule alarms.
- */
- int[] mDeviceIdleUserWhitelist = new int[0];
-
- /**
* For each uid, this is the last time we dispatched an "allow while idle" alarm,
* used to determine the earliest we can dispatch the next such alarm. Times are in the
* 'elapsed' timebase.
@@ -223,6 +220,8 @@
private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
new SparseArray<>();
+ private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@
public AlarmManagerService(Context context) {
super(context);
mConstants = new Constants(mHandler);
+
+ mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+ mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
}
static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@
deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
- void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+ /**
+ * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+ * restricted.
+ *
+ * This is only called when the global "force all apps-standby" flag changes or when the
+ * power save whitelist changes, so it's okay to be slow.
+ */
+ void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
- for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
- final int uid = mPendingBackgroundAlarms.keyAt(i);
- final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
- if (UserHandle.getAppId(uid) == appId) {
- alarmsToDeliver.addAll(alarmsForUid);
- mPendingBackgroundAlarms.removeAt(i);
+
+ findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+ if (alarmsToDeliver.size() > 0) {
+ deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+ }
+ }
+
+ @VisibleForTesting
+ static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+ SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+ Predicate<Alarm> isBackgroundRestricted) {
+
+ for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+ final int uid = pendingAlarms.keyAt(uidIndex);
+ final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+ for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+ final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+ if (isBackgroundRestricted.test(alarm)) {
+ continue;
+ }
+
+ unrestrictedAlarms.add(alarm);
+ alarmsForUid.remove(alarmIndex);
+ }
+
+ if (alarmsForUid.size() == 0) {
+ pendingAlarms.removeAt(uidIndex);
}
}
- deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
}
private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@
} catch (RemoteException e) {
// ignored; both services live in system_server
}
- mAppOpsService = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
publishBinderService(Context.ALARM_SERVICE, mService);
- publishLocalService(LocalService.class, new LocalService());
+ mForceAppStandbyTracker.start();
}
@Override
@@ -1247,13 +1278,6 @@
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
- try {
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
- new AppOpsWatcher());
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(TAG, "AppOps service not reachable", rexc);
- }
}
}
@@ -1582,8 +1606,7 @@
// timing restrictions.
} else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
|| callingUid == mSystemUiUid
- || Arrays.binarySearch(mDeviceIdleUserWhitelist,
- UserHandle.getAppId(callingUid)) >= 0)) {
+ || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
}
@@ -1660,24 +1683,14 @@
}
};
- public final class LocalService {
- public void setDeviceIdleUserWhitelist(int[] appids) {
- setDeviceIdleUserWhitelistImpl(appids);
- }
- }
-
void dumpImpl(PrintWriter pw) {
synchronized (mLock) {
pw.println("Current Alarm Manager state:");
mConstants.dump(pw);
pw.println();
- pw.print(" Foreground uids: [");
- for (int i = 0; i < mForegroundUids.size(); i++) {
- if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
- }
- pw.println("]");
- pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages);
+ mForceAppStandbyTracker.dump(pw, " ");
+
final long nowRTC = System.currentTimeMillis();
final long nowELAPSED = SystemClock.elapsedRealtime();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@
pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
pw.println();
pw.print(" Num time change events: "); pw.println(mNumTimeChanged);
- pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
pw.println();
pw.println(" Next alarm clock information: ");
@@ -1990,15 +2002,8 @@
mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
- final int foregroundUidsSize = mForegroundUids.size();
- for (int i = 0; i < foregroundUidsSize; i++) {
- if (mForegroundUids.valueAt(i)) {
- proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
- }
- }
- for (String pkg : mForcedAppStandbyPackages) {
- proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
- }
+ mForceAppStandbyTracker.dumpProto(proto,
+ AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
if (!mInteractive) {
@@ -2022,9 +2027,6 @@
proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
nowElapsed - mLastWakeupSet);
proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
- for (int i : mDeviceIdleUserWhitelist) {
- proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
- }
final TreeSet<Integer> users = new TreeSet<>();
final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@
}
}
- void setDeviceIdleUserWhitelistImpl(int[] appids) {
- synchronized (mLock) {
- // appids are sorted, just send pending alarms for any new appids added to the whitelist
- int i = 0, j = 0;
- while (i < appids.length) {
- while (j < mDeviceIdleUserWhitelist.length
- && mDeviceIdleUserWhitelist[j] < appids[i]) {
- j++;
- }
- if (j < mDeviceIdleUserWhitelist.length
- && appids[i] != mDeviceIdleUserWhitelist[j]) {
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
- }
- sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
- }
- i++;
- }
- mDeviceIdleUserWhitelist = appids;
- }
- }
-
AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
synchronized (mLock) {
return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@
final String sourcePackage =
(alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
final int sourceUid = alarm.creatorUid;
- return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
- && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
- < 0;
+ return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
}
private native long init();
@@ -2859,7 +2837,8 @@
}
}
- private static class Alarm {
+ @VisibleForTesting
+ static class Alarm {
public final int type;
public final long origWhen;
public final boolean wakeup;
@@ -3073,6 +3052,11 @@
for (int i=0; i<triggerList.size(); i++) {
Alarm alarm = triggerList.get(i);
final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+ if (alarm.wakeup) {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+ } else {
+ Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+ }
try {
if (localLOGV) {
Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@
} catch (RuntimeException e) {
Slog.w(TAG, "Failure sending alarm.", e);
}
+ Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
}
@@ -3476,17 +3461,10 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@Override public void onUidActive(int uid) {
- synchronized (mLock) {
- if (!mForegroundUids.get(uid)) {
- mForegroundUids.put(uid, true);
- sendPendingBackgroundAlarmsLocked(uid, null);
- }
- }
}
@Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@
if (disabled) {
removeForStoppedLocked(uid);
}
- mForegroundUids.delete(uid);
}
}
@@ -3502,27 +3479,29 @@
}
};
- private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+ private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ public void unblockAllUnrestrictedAlarms() {
synchronized (mLock) {
- final int mode = mAppOpsService.checkOperation(op, uid, packageName);
- if (DEBUG_BG_LIMIT) {
- Slog.d(TAG,
- "Appop changed for " + uid + ", " + packageName + " to " + mode);
- }
- final boolean changed;
- if (mode != AppOpsManager.MODE_ALLOWED) {
- changed = mForcedAppStandbyPackages.add(packageName);
- } else {
- changed = mForcedAppStandbyPackages.remove(packageName);
- }
- if (changed && mode == AppOpsManager.MODE_ALLOWED) {
- sendPendingBackgroundAlarmsLocked(uid, packageName);
- }
+ sendAllUnrestrictedPendingBackgroundAlarmsLocked();
}
}
- }
+
+ @Override
+ public void unblockAlarmsForUid(int uid) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, null);
+ }
+ }
+
+ @Override
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
+ synchronized (mLock) {
+ sendPendingBackgroundAlarmsLocked(uid, packageName);
+ }
+ }
+ };
private final BroadcastStats getStatsLocked(PendingIntent pi) {
String pkg = pi.getCreatorPackage();
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index c34c30c..04279a3 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -2150,31 +2150,26 @@
(int)((onDuration / (1000 * 60)) % 60),
(int)((onDuration / 1000) % 60),
(int)(onDuration % 1000));
- writer.println(" time since enabled: " + onDurationString + "\n");
+ writer.println(" time since enabled: " + onDurationString);
}
if (mActiveLogs.size() == 0) {
- writer.println("Bluetooth never enabled!");
+ writer.println("\nBluetooth never enabled!");
} else {
- writer.println("Enable log:");
+ writer.println("\nEnable log:");
for (ActiveLog log : mActiveLogs) {
writer.println(" " + log);
}
}
- writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+ writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
for (Long time : mCrashTimestamps) {
writer.println(" " + timeToLog(time.longValue()));
}
- String bleAppString = "No BLE Apps registered.";
- if (mBleApps.size() == 1) {
- bleAppString = "1 BLE App registered:";
- } else if (mBleApps.size() > 1) {
- bleAppString = mBleApps.size() + " BLE Apps registered:";
- }
- writer.println("\n" + bleAppString);
+ writer.println("\n" + mBleApps.size() + " BLE app" +
+ (mBleApps.size() == 1 ? "" : "s") + "registered");
for (ClientDeathRecipient app : mBleApps.values()) {
writer.println(" " + app.getPackageName());
}
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private ConnectivityService mConnectivityService;
- private AlarmManagerService.LocalService mLocalAlarmManager;
private INetworkPolicyManager mNetworkPolicyManager;
private SensorManager mSensorManager;
private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
- mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
+ passWhiteListToForceAppStandbyTrackerLocked();
updateInteractivityLocked();
}
updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
}
mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
}
- if (mLocalAlarmManager != null) {
- if (DEBUG) {
- Slog.d(TAG, "Setting alarm whitelist to "
- + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
- }
- mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
- }
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
}
mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
}
+ passWhiteListToForceAppStandbyTrackerLocked();
}
private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
}
+ private void passWhiteListToForceAppStandbyTrackerLocked() {
+ ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+ mPowerSaveWhitelistAllAppIdArray,
+ mTempWhitelistAppIdArray);
+ }
+
void readConfigFileLocked() {
if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
mPowerSaveWhitelistUserApps.clear();
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 5dd3ee0..61d3833 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -16,13 +16,18 @@
package com.android.server;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
@@ -30,21 +35,36 @@
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.List;
/**
- * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby".
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
*
- * TODO Clean up cache when a user is deleted.
- * TODO Add unit tests. b/68769804.
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ * each callback. For example, even when an UID comes into the foreground, if it wasn't
+ * originally restricted, then there's no need to send an event.
+ * Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
*/
public class ForceAppStandbyTracker {
private static final String TAG = "ForceAppStandbyTracker";
@@ -55,22 +75,32 @@
private final Object mLock = new Object();
private final Context mContext;
+ @VisibleForTesting
+ static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+ IActivityManager mIActivityManager;
AppOpsManager mAppOpsManager;
IAppOpsService mAppOpsService;
PowerManagerInternal mPowerManagerInternal;
- private final Handler mCallbackHandler;
+ private final MyHandler mHandler;
/**
* Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
*/
@GuardedBy("mLock")
- final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>();
+ final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
@GuardedBy("mLock")
final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
@GuardedBy("mLock")
+ private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+ @GuardedBy("mLock")
+ private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+ @GuardedBy("mLock")
final ArraySet<Listener> mListeners = new ArraySet<>();
@GuardedBy("mLock")
@@ -80,16 +110,116 @@
boolean mForceAllAppsStandby;
public static abstract class Listener {
- public void onRestrictionChanged(int uid, @Nullable String packageName) {
+ /**
+ * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+ */
+ private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+ int uid, @NonNull String packageName) {
+ updateJobsForUidPackage(uid, packageName);
+
+ if (!sender.areAlarmsRestricted(uid, packageName)) {
+ unblockAlarmsForUidPackage(uid, packageName);
+ }
}
- public void onGlobalRestrictionChanged() {
+ /**
+ * This is called when the foreground state changed for a UID.
+ */
+ private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+ updateJobsForUid(uid);
+
+ if (sender.isInForeground(uid)) {
+ unblockAlarmsForUid(uid);
+ }
+ }
+
+ /**
+ * This is called when an app-id(s) is removed from the power save whitelist.
+ */
+ private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ unblockAllUnrestrictedAlarms();
+ }
+
+ /**
+ * This is called when the power save whitelist changes, excluding the
+ * {@link #onPowerSaveUnwhitelisted} case.
+ */
+ private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the temp whitelist changes.
+ */
+ private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+ // TODO This case happens rather frequently; consider optimizing and update jobs
+ // only for affected app-ids.
+
+ updateAllJobs();
+ }
+
+ /**
+ * This is called when the global "force all apps standby" flag changes.
+ */
+ private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+ updateAllJobs();
+
+ if (!sender.isForceAllAppsStandbyEnabled()) {
+ unblockAllUnrestrictedAlarms();
+ }
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateAllJobs() {
+ }
+
+ /**
+ * Called when the job restrictions for a UID might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUid(int uid) {
+ }
+
+ /**
+ * Called when the job restrictions for a UID - package might have changed, so the job
+ * scheduler should re-evaluate all restrictions for all jobs.
+ */
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ }
+
+ /**
+ * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+ * manager should re-evaluate all restrictions for all blocked jobs.
+ */
+ public void unblockAllUnrestrictedAlarms() {
+ }
+
+ /**
+ * Called when all jobs for a specific UID are unblocked.
+ */
+ public void unblockAlarmsForUid(int uid) {
+ }
+
+ /**
+ * Called when all alarms for a specific UID - package are unblocked.
+ */
+ public void unblockAlarmsForUidPackage(int uid, String packageName) {
}
}
- private ForceAppStandbyTracker(Context context) {
+ @VisibleForTesting
+ ForceAppStandbyTracker(Context context, Looper looper) {
mContext = context;
- mCallbackHandler = FgThread.getHandler();
+ mHandler = new MyHandler(looper);
+ }
+
+ private ForceAppStandbyTracker(Context context) {
+ this(context, FgThread.get().getLooper());
}
/**
@@ -112,45 +242,65 @@
}
mStarted = true;
- mAppOpsManager = Preconditions.checkNotNull(
- mContext.getSystemService(AppOpsManager.class));
- mAppOpsService = Preconditions.checkNotNull(
- IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE)));
- mPowerManagerInternal = Preconditions.checkNotNull(
- LocalServices.getService(PowerManagerInternal.class));
+ mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+ mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+ mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+ mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
try {
- ActivityManager.getService().registerUidObserver(new UidObserver(),
+ mIActivityManager.registerUidObserver(new UidObserver(),
ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
| ActivityManager.UID_OBSERVER_ACTIVE,
ActivityManager.PROCESS_STATE_UNKNOWN, null);
- mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
+ mAppOpsService.startWatchingMode(TARGET_OP, null,
new AppOpsWatcher());
} catch (RemoteException e) {
// shouldn't happen.
}
- mPowerManagerInternal.registerLowPowerModeObserver(
- ServiceType.FORCE_ALL_APPS_STANDBY,
- state -> updateForceAllAppsStandby(state.batterySaverEnabled));
-
- updateForceAllAppsStandby(
- mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY)
- .batterySaverEnabled);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiver(new MyReceiver(), filter);
refreshForcedAppStandbyUidPackagesLocked();
+
+ mPowerManagerInternal.registerLowPowerModeObserver(
+ ServiceType.FORCE_ALL_APPS_STANDBY,
+ (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+ updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+ ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
}
}
+ @VisibleForTesting
+ AppOpsManager injectAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+
+ @VisibleForTesting
+ IAppOpsService injectIAppOpsService() {
+ return IAppOpsService.Stub.asInterface(
+ ServiceManager.getService(Context.APP_OPS_SERVICE));
+ }
+
+ @VisibleForTesting
+ IActivityManager injectIActivityManager() {
+ return ActivityManager.getService();
+ }
+
+ @VisibleForTesting
+ PowerManagerInternal injectPowerManagerInternal() {
+ return LocalServices.getService(PowerManagerInternal.class);
+ }
+
/**
- * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state.
+ * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
*/
private void refreshForcedAppStandbyUidPackagesLocked() {
- final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
-
- mForcedAppStandbyUidPackages.clear();
- final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op});
+ mRunAnyRestrictedPackages.clear();
+ final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+ new int[] {TARGET_OP});
if (ops == null) {
return;
@@ -162,31 +312,38 @@
for (int j = 0; j < entries.size(); j++) {
AppOpsManager.OpEntry ent = entries.get(j);
- if (ent.getOp() != op) {
+ if (ent.getOp() != TARGET_OP) {
continue;
}
if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
- mForcedAppStandbyUidPackages.add(Pair.create(
+ mRunAnyRestrictedPackages.add(Pair.create(
pkg.getUid(), pkg.getPackageName()));
}
}
}
}
- boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) {
- try {
- return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
- uid, packageName) != AppOpsManager.MODE_ALLOWED;
- } catch (RemoteException e) {
- return false; // shouldn't happen.
+ /**
+ * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+ */
+ void updateForceAllAppsStandby(boolean enable) {
+ synchronized (mLock) {
+ if (enable == mForceAllAppsStandby) {
+ return;
+ }
+ mForceAllAppsStandby = enable;
+
+ mHandler.notifyForceAllAppsStandbyChanged();
}
}
private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
- // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big.
- final int size = mForcedAppStandbyUidPackages.size();
+ final int size = mRunAnyRestrictedPackages.size();
+ if (size > 8) {
+ return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+ }
for (int i = 0; i < size; i++) {
- final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i);
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
if ((pair.first == uid) && packageName.equals(pair.second)) {
return i;
@@ -196,13 +353,16 @@
}
/**
- * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages.
+ * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
*/
- boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) {
+ boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
}
- boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName,
+ /**
+ * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+ */
+ boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
boolean restricted) {
final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
final boolean wasRestricted = index >= 0;
@@ -210,13 +370,16 @@
return false;
}
if (restricted) {
- mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName));
+ mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
} else {
- mForcedAppStandbyUidPackages.removeAt(index);
+ mRunAnyRestrictedPackages.removeAt(index);
}
return true;
}
+ /**
+ * Puts a UID to {@link #mForegroundUids}.
+ */
void uidToForeground(int uid) {
synchronized (mLock) {
if (!UserHandle.isApp(uid)) {
@@ -228,10 +391,13 @@
return;
}
mForegroundUids.put(uid, true);
- notifyForUidPackage(uid, null);
+ mHandler.notifyUidForegroundStateChanged(uid);
}
}
+ /**
+ * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+ */
void uidToBackground(int uid, boolean remove) {
synchronized (mLock) {
if (!UserHandle.isApp(uid)) {
@@ -247,13 +413,11 @@
} else {
mForegroundUids.put(uid, false);
}
- notifyForUidPackage(uid, null);
+ mHandler.notifyUidForegroundStateChanged(uid);
}
}
- // Event handlers
-
- final class UidObserver extends IUidObserver.Stub {
+ private final class UidObserver extends IUidObserver.Stub {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
}
@@ -277,11 +441,28 @@
private final class AppOpsWatcher extends IAppOpsCallback.Stub {
@Override
public void opChanged(int op, int uid, String packageName) throws RemoteException {
+ boolean restricted = false;
+ try {
+ restricted = mAppOpsService.checkOperation(TARGET_OP,
+ uid, packageName) != AppOpsManager.MODE_ALLOWED;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
synchronized (mLock) {
- final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName);
+ if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+ mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+ }
+ }
+ }
+ }
- if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) {
- notifyForUidPackage(uid, packageName);
+ private final class MyReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+ if (userId > 0) {
+ mHandler.doUserRemoved(userId);
}
}
}
@@ -293,31 +474,183 @@
}
}
- void notifyForUidPackage(int uid, String packageName) {
- mCallbackHandler.post(() -> {
- for (Listener l : cloneListeners()) {
- l.onRestrictionChanged(uid, packageName);
- }
- });
- }
+ private class MyHandler extends Handler {
+ private static final int MSG_UID_STATE_CHANGED = 1;
+ private static final int MSG_RUN_ANY_CHANGED = 2;
+ private static final int MSG_ALL_UNWHITELISTED = 3;
+ private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+ private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+ private static final int MSG_FORCE_ALL_CHANGED = 6;
- void notifyGlobal() {
- mCallbackHandler.post(() -> {
- for (Listener l : cloneListeners()) {
- l.onGlobalRestrictionChanged();
- }
- });
- }
+ private static final int MSG_USER_REMOVED = 7;
- void updateForceAllAppsStandby(boolean forceAllAppsStandby) {
- synchronized (mLock) {
- if (mForceAllAppsStandby == forceAllAppsStandby) {
- return;
- }
- mForceAllAppsStandby = forceAllAppsStandby;
- Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby);
- notifyGlobal();
+ public MyHandler(Looper looper) {
+ super(looper);
}
+
+ public void notifyUidForegroundStateChanged(int uid) {
+ obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+ }
+ public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+ obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+ }
+
+ public void notifyAllUnwhitelisted() {
+ obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+ }
+
+ public void notifyAllWhitelistChanged() {
+ obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyTempWhitelistChanged() {
+ obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+ }
+
+ public void notifyForceAllAppsStandbyChanged() {
+ obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+ }
+
+ public void doUserRemoved(int userId) {
+ obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+ }
+
+ @Override
+ public void dispatchMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+
+ // Only notify the listeners when started.
+ synchronized (mLock) {
+ if (!mStarted) {
+ return;
+ }
+ }
+ final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+ switch (msg.what) {
+ case MSG_UID_STATE_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onUidForegroundStateChanged(sender, msg.arg1);
+ }
+ return;
+ case MSG_RUN_ANY_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+ }
+ return;
+ case MSG_ALL_UNWHITELISTED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveUnwhitelisted(sender);
+ }
+ return;
+ case MSG_ALL_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onPowerSaveWhitelistedChanged(sender);
+ }
+ return;
+ case MSG_TEMP_WHITELIST_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onTempPowerSaveWhitelistChanged(sender);
+ }
+ return;
+ case MSG_FORCE_ALL_CHANGED:
+ for (Listener l : cloneListeners()) {
+ l.onForceAllAppsStandbyChanged(sender);
+ }
+ return;
+ case MSG_USER_REMOVED:
+ handleUserRemoved(msg.arg1);
+ return;
+ }
+ }
+ }
+
+ void handleUserRemoved(int removedUserId) {
+ synchronized (mLock) {
+ for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+ final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+ final int uid = pair.first;
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mRunAnyRestrictedPackages.removeAt(i);
+ }
+ }
+ for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+ final int uid = mForegroundUids.keyAt(i);
+ final int userId = UserHandle.getUserId(uid);
+
+ if (userId == removedUserId) {
+ mForegroundUids.removeAt(i);
+ }
+ }
+ }
+ }
+
+ /**
+ * Called by device idle controller to update the power save whitelists.
+ */
+ public void setPowerSaveWhitelistAppIds(
+ int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+ synchronized (mLock) {
+ final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+ final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+ mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+ mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+ if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllUnwhitelisted();
+ } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+ mHandler.notifyAllWhitelistChanged();
+ }
+
+ if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+ mHandler.notifyTempWhitelistChanged();
+ }
+
+ }
+ }
+
+ /**
+ * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+ * that's not in a sorted app-id array {@code newArray}.
+ */
+ @VisibleForTesting
+ static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+ int i1 = 0;
+ int i2 = 0;
+ boolean prevFinished;
+ boolean newFinished;
+
+ for (;;) {
+ prevFinished = i1 >= prevArray.length;
+ newFinished = i2 >= newArray.length;
+ if (prevFinished || newFinished) {
+ break;
+ }
+ int a1 = prevArray[i1];
+ int a2 = newArray[i2];
+
+ if (a1 == a2) {
+ i1++;
+ i2++;
+ continue;
+ }
+ if (a1 < a2) {
+ // prevArray has an element that's not in a2.
+ return true;
+ }
+ i2++;
+ }
+ if (prevFinished) {
+ return false;
+ }
+ return newFinished;
}
// Public interface.
@@ -332,21 +665,51 @@
}
/**
- * Whether force-app-standby is effective for a UID package-name.
+ * @return whether alarms should be restricted for a UID package-name.
*/
- public boolean isRestricted(int uid, @NonNull String packageName) {
+ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+ }
+
+ /**
+ * @return whether jobs should be restricted for a UID package-name.
+ */
+ public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+ return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+ }
+
+ /**
+ * @return whether force-app-standby is effective for a UID package-name.
+ */
+ private boolean isRestricted(int uid, @NonNull String packageName,
+ boolean useTempWhitelistToo) {
if (isInForeground(uid)) {
return false;
}
synchronized (mLock) {
+ // Whitelisted?
+ final int appId = UserHandle.getAppId(uid);
+ if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+ return false;
+ }
+ if (useTempWhitelistToo &&
+ ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+ return false;
+ }
+
if (mForceAllAppsStandby) {
return true;
}
- return isUidPackageRestrictedLocked(uid, packageName);
+
+ return isRunAnyRestrictedLocked(uid, packageName);
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
+ /**
+ * @return whether a UID is in the foreground or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
public boolean isInForeground(int uid) {
if (!UserHandle.isApp(uid)) {
return true;
@@ -356,31 +719,120 @@
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public boolean isForceAllAppsStandbyEnabled() {
+ /**
+ * @return whether force all apps standby is enabled or not.
+ *
+ * Note clients normally shouldn't need to access it.
+ */
+ boolean isForceAllAppsStandbyEnabled() {
synchronized (mLock) {
return mForceAllAppsStandby;
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
+ /**
+ * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
synchronized (mLock) {
- return !isUidPackageRestrictedLocked(uid, packageName);
+ return !isRunAnyRestrictedLocked(uid, packageName);
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public SparseBooleanArray getForegroudUids() {
+ /**
+ * @return whether a UID is in the user / system defined power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidPowerSaveWhitelisted(int uid) {
synchronized (mLock) {
- return mForegroundUids.clone();
+ return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
}
}
- /** For dumpsys -- otherwise the callers don't need to know it. */
- public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() {
+ /**
+ * @return whether a UID is in the temp power-save whitelist or not.
+ *
+ * Note clients normally shouldn't need to access it. It's only for dumpsys.
+ */
+ public boolean isUidTempPowerSaveWhitelisted(int uid) {
synchronized (mLock) {
- return new ArraySet(mForcedAppStandbyUidPackages);
+ return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+ }
+ }
+
+ public void dump(PrintWriter pw, String indent) {
+ synchronized (mLock) {
+ pw.print(indent);
+ pw.print("Force all apps standby: ");
+ pw.println(isForceAllAppsStandbyEnabled());
+
+ pw.print(indent);
+ pw.print("Foreground uids: [");
+
+ String sep = "";
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ pw.print(sep);
+ pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+ sep = " ";
+ }
+ }
+ pw.println("]");
+
+ pw.print(indent);
+ pw.print("Whitelist appids: ");
+ pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+ pw.print(indent);
+ pw.print("Temp whitelist appids: ");
+ pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+ pw.print(indent);
+ pw.println("Restricted packages:");
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ pw.print(indent);
+ pw.print(" ");
+ pw.print(UserHandle.formatUid(uidAndPackage.first));
+ pw.print(" ");
+ pw.print(uidAndPackage.second);
+ pw.println();
+ }
+ }
+ }
+
+ public void dumpProto(ProtoOutputStream proto, long fieldId) {
+ synchronized (mLock) {
+ final long token = proto.start(fieldId);
+
+ proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+ for (int i = 0; i < mForegroundUids.size(); i++) {
+ if (mForegroundUids.valueAt(i)) {
+ proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+ mForegroundUids.keyAt(i));
+ }
+ }
+
+ for (int appId : mPowerWhitelistedAllAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (int appId : mTempWhitelistedAppIds) {
+ proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+ }
+
+ for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+ final long token2 = proto.start(
+ ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+ proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+ proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+ uidAndPackage.second);
+ proto.end(token2);
+ }
+ proto.end(token);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index 3dbee42..b11b16e1 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -399,6 +399,16 @@
otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
}
} finally {
+ if (mHomeStack != null && !isTopStack(mHomeStack)) {
+ // Whenever split-screen is dismissed we want the home stack directly behind the
+ // currently top stack so it shows up when the top stack is finished.
+ final ActivityStack topStack = getTopStack();
+ // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+ // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+ // once we have that.
+ mHomeStack.moveToFront("onSplitScreenModeDismissed");
+ topStack.moveToFront("onSplitScreenModeDismissed");
+ }
mSupervisor.mWindowManager.continueSurfaceLayout();
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9ac241e..fe992da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -246,6 +246,7 @@
import android.app.assist.AssistContent;
import android.app.assist.AssistStructure;
import android.app.backup.IBackupManager;
+import android.app.servertransaction.ConfigurationChangeItem;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
@@ -317,6 +318,7 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
import android.os.RemoteCallbackList;
@@ -631,6 +633,8 @@
final ActivityStarter mActivityStarter;
+ final ClientLifecycleManager mLifecycleManager;
+
final TaskChangeNotificationController mTaskChangeNotificationController;
final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
@@ -1778,6 +1782,11 @@
final boolean mPermissionReviewRequired;
+ /**
+ * Whether to force background check on all apps (for battery saver) or not.
+ */
+ boolean mForceBackgroundCheck;
+
private static String sTheRealBuildSerial = Build.UNKNOWN;
/**
@@ -2693,6 +2702,7 @@
mUserController = null;
mVrController = null;
mLockTaskController = null;
+ mLifecycleManager = null;
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2795,6 +2805,7 @@
mRecentTasks = createRecentTasks();
mStackSupervisor.setRecentTasks(mRecentTasks);
mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
+ mLifecycleManager = new ClientLifecycleManager();
mProcessCpuThread = new Thread("CpuTracker") {
@Override
@@ -5128,7 +5139,8 @@
// because we don't support returning them across task boundaries. Also, to
// keep backwards compatibility we remove the task from recents when finishing
// task with root activity.
- res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+ res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+ finishWithRootActivity, "finish-activity");
if (!res) {
Slog.i(TAG, "Removing task failed to finish activity");
}
@@ -8614,6 +8626,16 @@
}
switch (appop) {
case AppOpsManager.MODE_ALLOWED:
+ // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+ if (mForceBackgroundCheck &&
+ UserHandle.isApp(uid) &&
+ !isOnDeviceIdleWhitelistLocked(uid)) {
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check: " +
+ uid + "/" + packageName + " restricted");
+ }
+ return ActivityManager.APP_START_MODE_DELAYED;
+ }
return ActivityManager.APP_START_MODE_NORMAL;
case AppOpsManager.MODE_IGNORED:
return ActivityManager.APP_START_MODE_DELAYED;
@@ -8713,6 +8735,9 @@
return ActivityManager.APP_START_MODE_NORMAL;
}
+ /**
+ * @return whether a UID is in the system, user or temp doze whitelist.
+ */
boolean isOnDeviceIdleWhitelistLocked(int uid) {
final int appId = UserHandle.getAppId(uid);
return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -10306,7 +10331,8 @@
synchronized (this) {
final long ident = Binder.clearCallingIdentity();
try {
- return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+ return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+ "remove-task");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10496,12 +10522,12 @@
+ " non-standard task " + taskId + " to windowing mode="
+ windowingMode);
}
- final ActivityDisplay display = task.getStack().getDisplay();
- final ActivityStack stack = display.getOrCreateStack(windowingMode,
- task.getStack().getActivityType(), toTop);
- // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
- task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
- "moveTaskToStack");
+
+ final ActivityStack stack = task.getStack();
+ if (toTop) {
+ stack.moveToFront("setTaskWindowingMode", task);
+ }
+ stack.setWindowingMode(windowingMode);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -14169,6 +14195,16 @@
readGrantedUriPermissionsLocked();
}
+ final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+ if (pmi != null) {
+ pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+ state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+ updateForceBackgroundCheck(
+ pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+ } else {
+ Slog.wtf(TAG, "PowerManagerInternal not found.");
+ }
+
if (goingCallback != null) goingCallback.run();
traceLog.traceBegin("ActivityManagerStartApps");
mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14267,6 +14303,23 @@
}
}
+ private void updateForceBackgroundCheck(boolean enabled) {
+ synchronized (this) {
+ if (mForceBackgroundCheck != enabled) {
+ mForceBackgroundCheck = enabled;
+
+ if (DEBUG_BACKGROUND_CHECK) {
+ Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+ }
+
+ if (mForceBackgroundCheck) {
+ // Stop background services for idle UIDs.
+ doStopUidForIdleUidsLocked();
+ }
+ }
+ }
+ }
+
void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
synchronized (this) {
mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -15618,7 +15671,6 @@
void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
boolean needSep = false;
- boolean printedAnything = false;
int numPers = 0;
pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15636,7 +15688,6 @@
if (!needSep) {
pw.println(" All known processes:");
needSep = true;
- printedAnything = true;
}
pw.print(r.persistent ? " *PERS*" : " *APP*");
pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15661,7 +15712,6 @@
pw.println();
}
pw.println(" Isolated process list (sorted by uid):");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15683,7 +15733,6 @@
pw.println();
}
pw.println(" Active instrumentation:");
- printedAnything = true;
printed = true;
needSep = true;
}
@@ -15695,14 +15744,14 @@
if (mActiveUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
- printedAnything = needSep = true;
+ needSep = true;
}
}
if (dumpAll) {
if (mValidateUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
needSep)) {
- printedAnything = needSep = true;
+ needSep = true;
}
}
}
@@ -15719,7 +15768,6 @@
pw.println("):");
dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage);
needSep = true;
- printedAnything = true;
}
if (dumpAll || dumpPackage != null) {
@@ -15735,7 +15783,6 @@
needSep = true;
pw.println(" PID mappings:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i));
pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15758,7 +15805,6 @@
needSep = true;
pw.println(" Foreground Processes:");
printed = true;
- printedAnything = true;
}
pw.print(" PID #"); pw.print(mImportantProcesses.keyAt(i));
pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15769,7 +15815,6 @@
if (mPersistentStartingProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Persisent processes that are starting:");
dumpProcessList(pw, this, mPersistentStartingProcesses, " ",
"Starting Norm", "Restarting PERS", dumpPackage);
@@ -15778,7 +15823,6 @@
if (mRemovedProcesses.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are being removed:");
dumpProcessList(pw, this, mRemovedProcesses, " ",
"Removed Norm", "Removed PERS", dumpPackage);
@@ -15787,7 +15831,6 @@
if (mProcessesOnHold.size() > 0) {
if (needSep) pw.println();
needSep = true;
- printedAnything = true;
pw.println(" Processes that are on old until the system is ready:");
dumpProcessList(pw, this, mProcessesOnHold, " ",
"OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15796,9 +15839,6 @@
needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
- if (needSep) {
- printedAnything = true;
- }
if (dumpPackage == null) {
pw.println();
@@ -16070,10 +16110,7 @@
pw.println();
}
}
-
- if (!printedAnything) {
- pw.println(" (nothing)");
- }
+ pw.println(" mForceBackgroundCheck=" + mForceBackgroundCheck);
}
boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -20562,9 +20599,11 @@
if (app.thread != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
+ app.processName + " new config " + configCopy);
- app.thread.scheduleConfigurationChanged(configCopy);
+ mLifecycleManager.scheduleTransaction(app.thread,
+ new ConfigurationChangeItem(configCopy));
}
} catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
}
}
@@ -23332,6 +23371,24 @@
}
}
+ /**
+ * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+ */
+ void doStopUidForIdleUidsLocked() {
+ final int size = mActiveUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mActiveUids.keyAt(i);
+ if (!UserHandle.isApp(uid)) {
+ continue;
+ }
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (!uidRec.idle) {
+ continue;
+ }
+ doStopUidLocked(uidRec.uid, uidRec);
+ }
+ }
+
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 11590d6..a089e6c 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -131,6 +131,12 @@
import android.app.PendingIntent;
import android.app.PictureInPictureParams;
import android.app.ResultInfo;
+import android.app.servertransaction.MoveToDisplayItem;
+import android.app.servertransaction.MultiWindowModeChangeItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -611,8 +617,8 @@
"Reporting activity moved to display" + ", activityRecord=" + this
+ ", displayId=" + displayId + ", config=" + config);
- app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
- new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MoveToDisplayItem(displayId, config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -629,7 +635,8 @@
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
+ config);
- app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new ActivityConfigurationChangeItem(config));
} catch (RemoteException e) {
// If process died, whatever.
}
@@ -640,8 +647,13 @@
return;
}
+ if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+ // Don't do anything if we are currently deferring multi-window mode change.
+ return;
+ }
+
// An activity is considered to be in multi-window mode if its task isn't fullscreen.
- final boolean inMultiWindowMode = task.inMultiWindowMode();
+ final boolean inMultiWindowMode = inMultiWindowMode();
if (inMultiWindowMode != mLastReportedMultiWindowMode) {
mLastReportedMultiWindowMode = inMultiWindowMode;
scheduleMultiWindowModeChanged(getConfiguration());
@@ -650,8 +662,9 @@
private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
try {
- app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
- overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, I don't care.
}
@@ -667,7 +680,7 @@
// Picture-in-picture mode changes also trigger a multi-window mode change as well, so
// update that here in order
mLastReportedPictureInPictureMode = inPictureInPictureMode;
- mLastReportedMultiWindowMode = inPictureInPictureMode;
+ mLastReportedMultiWindowMode = inMultiWindowMode();
final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
targetStackBounds, null);
schedulePictureInPictureModeChanged(newConfig);
@@ -677,8 +690,9 @@
private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
try {
- app.thread.schedulePictureInPictureModeChanged(appToken,
- mLastReportedPictureInPictureMode, overrideConfig);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new PipModeChangeItem(mLastReportedPictureInPictureMode,
+ overrideConfig));
} catch (Exception e) {
// If process died, no one cares.
}
@@ -1365,8 +1379,8 @@
try {
ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
ar.add(rintent);
- app.thread.scheduleNewIntent(
- ar, appToken, state == PAUSED /* andPause */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new NewIntentItem(ar, state == PAUSED));
unsent = false;
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1588,7 +1602,8 @@
setVisible(true);
sleeping = false;
app.pendingUiClean = true;
- app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+ service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+ new WindowVisibilityItem(true /* showWindow */));
// The activity may be waiting for stop, but that is no longer appropriate for it.
mStackSupervisor.mStoppingActivities.remove(this);
mStackSupervisor.mGoingToSleepActivities.remove(this);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4816998..ced8621 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -104,6 +104,13 @@
import android.app.ResultInfo;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.DestroyActivityItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -487,7 +494,7 @@
// Need to make sure windowing mode is supported.
int windowingMode = display.resolveWindowingMode(
- null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+ null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
// Resolution to split-screen secondary for the primary split-screen stack means we want
// to go fullscreen.
@@ -1419,8 +1426,10 @@
prev.userId, System.identityHashCode(prev),
prev.shortComponentName);
mService.updateUsageStats(prev, false);
- prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
- userLeaving, prev.configChangeFlags, pauseImmediately);
+
+ mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
+ new PauseActivityItem(prev.finishing, userLeaving,
+ prev.configChangeFlags, pauseImmediately));
} catch (Exception e) {
// Ignore exception, if process died other code will cleanup.
Slog.w(TAG, "Exception thrown during pause", e);
@@ -2055,7 +2064,8 @@
if (r.app != null && r.app.thread != null) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
"Scheduling invisibility: " + r);
- r.app.thread.scheduleWindowVisibility(r.appToken, false);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new WindowVisibilityItem(false /* showWindow */));
}
// Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2575,13 +2585,15 @@
if (!next.finishing && N > 0) {
if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
"Delivering results to " + next + ": " + a);
- next.app.thread.scheduleSendResult(next.appToken, a);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new ActivityResultItem(a));
}
}
if (next.newIntents != null) {
- next.app.thread.scheduleNewIntent(
- next.newIntents, next.appToken, false /* andPause */);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+ next.appToken, new NewIntentItem(next.newIntents,
+ false /* andPause */));
}
// Well the app will no longer be stopped.
@@ -2598,8 +2610,9 @@
next.app.pendingUiClean = true;
next.app.forceProcessStateUpTo(mService.mTopProcessState);
next.clearOptionsLocked();
- next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
- mService.isNextTransitionForward(), resumeAnimOptions);
+ mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
+ new ResumeActivityItem(next.app.repProcState,
+ mService.isNextTransitionForward()));
if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+ next);
@@ -3249,7 +3262,8 @@
ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
list.add(new ResultInfo(resultWho, requestCode,
resultCode, data));
- r.app.thread.scheduleSendResult(r.appToken, list);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new ActivityResultItem(list));
return;
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3377,7 +3391,8 @@
}
EventLogTags.writeAmStopActivity(
r.userId, System.identityHashCode(r), r.shortComponentName);
- r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new StopActivityItem(r.visible, r.configChangeFlags));
if (shouldSleepOrShutDownActivities()) {
r.setSleeping(true);
}
@@ -4009,7 +4024,7 @@
// TODO: If the callers to removeTask() changes such that we have multiple places
// where we are destroying the task, move this back into removeTask()
mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
- !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
+ !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
}
// We must keep the task around until all activities are destroyed. The following
@@ -4174,8 +4189,8 @@
try {
if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
- r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
- r.configChangeFlags);
+ mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+ new DestroyActivityItem(r.finishing, r.configChangeFlags));
} catch (Exception e) {
// We can just ignore exceptions here... if the process
// has crashed, our death notification will clean things
@@ -4988,13 +5003,22 @@
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
boolean toTop) {
+ return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop,
+ null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+ IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+ boolean toTop, ActivityRecord activity, ActivityRecord source,
+ ActivityOptions options) {
final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
voiceInteractor);
// add the task to stack first, mTaskPositioner might need the stack association
addTask(task, toTop, "createTaskRecord");
final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
.isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
- if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
+ if (!mStackSupervisor.getLaunchingBoundsController()
+ .layoutTask(task, info.windowLayout, activity, source, options)
&& !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
task.updateOverrideConfiguration(getOverrideBounds());
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 5525cdb..445bf67 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -114,6 +114,7 @@
import android.app.WaitResult;
import android.app.WindowConfiguration.ActivityType;
import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.LaunchActivityItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1392,7 +1393,8 @@
r.setLastReportedConfiguration(mergedConfiguration);
logIfTransactionTooLarge(r.intent, r.icicle);
- app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+ mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken,
+ new LaunchActivityItem(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
@@ -1400,7 +1402,7 @@
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, !andResume,
- mService.isNextTransitionForward(), profilerInfo);
+ mService.isNextTransitionForward(), profilerInfo));
if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
// This may be a heavy-weight process! Note that the package
@@ -2737,7 +2739,7 @@
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "remove-stack");
}
}
}
@@ -2770,8 +2772,10 @@
/**
* See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
*/
- boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
- return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+ boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+ String reason) {
+ return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
+ reason);
}
/**
@@ -2785,10 +2789,10 @@
* @return Returns true if the given task was found and removed.
*/
boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
- boolean pauseImmediately) {
+ boolean pauseImmediately, String reason) {
final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
if (tr != null) {
- tr.removeTaskActivitiesLocked(pauseImmediately);
+ tr.removeTaskActivitiesLocked(pauseImmediately, reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
mService.mLockTaskController.clearLockedTask(tr);
if (tr.isPersistable) {
@@ -2922,7 +2926,12 @@
@Override
public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
- // TODO: Trim active task once b/68045330 is fixed
+ if (wasTrimmed) {
+ // Task was trimmed from the recent tasks list -- remove the active task record as well
+ // since the user won't really be able to go back to it
+ removeTaskByIdLocked(task.taskId, false /* killProcess */,
+ false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+ }
task.removedFromRecents();
}
@@ -4299,6 +4308,10 @@
final ActivityRecord r = task.mActivities.get(i);
if (r.app != null && r.app.thread != null) {
mPipModeChangedActivities.add(r);
+ // If we are scheduling pip change, then remove this activity from multi-window
+ // change list as the processing of pip change will make sure multi-window changed
+ // message is processed in the right order relative to pip changed.
+ mMultiWindowModeChangedActivities.remove(r);
}
}
mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 03162bb..27eb985 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1756,7 +1756,8 @@
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
- mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
+ mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
+ mOptions);
addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
updateBounds(mStartActivity.getTask(), mLaunchBounds);
@@ -1965,7 +1966,7 @@
final ActivityRecord prev = mTargetStack.getTopActivity();
final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
- mIntent, null, null, true);
+ mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
mTargetStack.positionChildWindowContainerAtTop(task);
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
@@ -2122,18 +2123,13 @@
return mReuseTask.getStack();
}
- final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
- ? mPreferredDisplayId : INVALID_DISPLAY;
- final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
- vrDisplayId);
-
- if (launchStack != null) {
- return launchStack;
- }
-
if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
|| mPreferredDisplayId != DEFAULT_DISPLAY) {
- return null;
+ // We don't pass in the default display id into the get launch stack call so it can do a
+ // full resolution.
+ final int candidateDisplay =
+ mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
}
// Otherwise handle adjacent launch.
@@ -2165,7 +2161,7 @@
mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
// There is a docked stack, but it isn't visible, so we can't launch into that.
- return null;
+ return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
} else {
return dockedStack;
}
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index 17626ea..ab86dbdb 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -61,7 +61,7 @@
try {
// We remove the task from recents to preserve backwards
if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
- REMOVE_FROM_RECENTS)) {
+ REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
}
} finally {
diff --git a/services/core/java/com/android/server/am/ClientLifecycleManager.java b/services/core/java/com/android/server/am/ClientLifecycleManager.java
new file mode 100644
index 0000000..c04d103
--- /dev/null
+++ b/services/core/java/com/android/server/am/ClientLifecycleManager.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
+ * and execute them as a single transaction.
+ *
+ * @see ClientTransaction
+ */
+class ClientLifecycleManager {
+ // TODO(lifecycler): Implement building transactions or global transaction.
+ // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+ /**
+ * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
+ * @param transaction A sequence of client transaction items.
+ * @throws RemoteException
+ *
+ * @see ClientTransaction
+ */
+ void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+ transaction.schedule();
+ }
+
+ /**
+ * Schedule a single lifecycle request or callback to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param stateRequest A request to move target activity to a desired lifecycle state.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
+ stateRequest);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client activity.
+ * @param client Target client.
+ * @param activityToken Target activity token.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken,
+ callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * Schedule a single callback delivery to client application.
+ * @param client Target client.
+ * @param callback A request to deliver a callback.
+ * @throws RemoteException
+ *
+ * @see ClientTransactionItem
+ */
+ void scheduleTransaction(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem callback) throws RemoteException {
+ final ClientTransaction clientTransaction = transactionWithCallback(client,
+ null /* activityToken */, callback);
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single lifecycle state request.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
+ @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.setLifecycleStateRequest(stateRequest);
+ return clientTransaction;
+ }
+
+ /**
+ * @return A new instance of {@link ClientTransaction} with a single callback invocation.
+ *
+ * @see ClientTransaction
+ * @see ClientTransactionItem
+ */
+ private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
+ IBinder activityToken, @NonNull ClientTransactionItem callback) {
+ final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+ clientTransaction.addCallback(callback);
+ return clientTransaction;
+ }
+}
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index 82019fd..65c4a42 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -58,10 +58,6 @@
public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
// Compatibility state: compatibility mode is enabled.
public static final int COMPAT_FLAG_ENABLED = 1<<1;
- // Unsupported zoom state: don't warn the user about unsupported zoom mode.
- public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
- // Unsupported compile SDK state: don't warn the user about unsupported compile SDK.
- public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3;
private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
@@ -235,14 +231,6 @@
return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
}
- public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
- return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
- }
-
- public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) {
- return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0;
- }
-
public void setFrontActivityAskCompatModeLocked(boolean ask) {
ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
if (r != null) {
@@ -254,14 +242,6 @@
setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
}
- public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
- setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify);
- }
-
- public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) {
- setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, notify);
- }
-
private void setPackageFlagLocked(String packageName, int flag, boolean set) {
final int curFlags = getPackageFlags(packageName);
final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
diff --git a/services/core/java/com/android/server/am/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java
index c762f7f..5aa7f58 100644
--- a/services/core/java/com/android/server/am/LaunchingBoundsController.java
+++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java
@@ -101,8 +101,12 @@
* @return {@code true} if bounds were set on the task. {@code false} otherwise.
*/
boolean layoutTask(TaskRecord task, WindowLayout layout) {
- calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
- mTmpRect);
+ return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
+ }
+
+ boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+ ActivityRecord source, ActivityOptions options) {
+ calculateBounds(task, layout, activity, source, options, mTmpRect);
if (mTmpRect.isEmpty()) {
return false;
diff --git a/services/core/java/com/android/server/am/LockTaskController.java b/services/core/java/com/android/server/am/LockTaskController.java
index e87b4e6..d77e1a2 100644
--- a/services/core/java/com/android/server/am/LockTaskController.java
+++ b/services/core/java/com/android/server/am/LockTaskController.java
@@ -250,7 +250,24 @@
}
/**
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+ * lockTaskMode="always" in the manifest).
+ */
+ boolean isTaskWhitelisted(TaskRecord task) {
+ switch(task.mLockTaskAuth) {
+ case LOCK_TASK_AUTH_WHITELISTED:
+ case LOCK_TASK_AUTH_LAUNCHABLE:
+ case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
+ return true;
+ case LOCK_TASK_AUTH_PINNABLE:
+ case LOCK_TASK_AUTH_DONT_LOCK:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task) {
return isLockTaskModeViolation(task, false);
@@ -258,7 +275,7 @@
/**
* @param isNewClearTask whether the task would be cleared as part of the operation.
- * @return whether the requested task is allowed to be launched.
+ * @return whether the requested task is disallowed to be launched.
*/
boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
@@ -275,21 +292,18 @@
// If the task is already at the top and won't be cleared, then allow the operation
return false;
}
- final int lockTaskAuth = task.mLockTaskAuth;
- switch (lockTaskAuth) {
- case LOCK_TASK_AUTH_DONT_LOCK:
- return !mLockTaskModeTasks.isEmpty();
- case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
- case LOCK_TASK_AUTH_LAUNCHABLE:
- case LOCK_TASK_AUTH_WHITELISTED:
- return false;
- case LOCK_TASK_AUTH_PINNABLE:
- // Pinnable tasks can't be launched on top of locktask tasks.
- return !mLockTaskModeTasks.isEmpty();
- default:
- Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
- return true;
+
+ // Allow recents activity if enabled by policy
+ if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) {
+ return false;
}
+
+ return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+ }
+
+ private boolean isRecentsAllowed(int userId) {
+ return (getLockTaskFeaturesForUser(userId)
+ & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
}
/**
@@ -491,6 +505,7 @@
}
if (mLockTaskModeTasks.isEmpty()) {
+ mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId);
// Start lock task on the handler thread
mHandler.post(() -> performStartLockTask(
task.intent.getComponent().getPackageName(),
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 1a19601..6fb3dbb 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@
/**
* Activity manager code dealing with processes.
*/
-final class ProcessList {
+public final class ProcessList {
private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
// The minimum time we allow between crashes, for us to consider this
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 6e6d7e9..abb296e 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -80,6 +80,20 @@
/**
* Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
* least recent.
+ *
+ * The trimming logic can be boiled down to the following. For recent task list with a number of
+ * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to
+ * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a
+ * sub-range are presented to the user, based on the device type, last task active time, or other
+ * task state. Tasks that are not in the visible range and are not returnable from the SystemUI
+ * (considering the back stack) are considered trimmable. If the device does not support recent
+ * tasks, then trimming is completely disabled.
+ *
+ * eg.
+ * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks
+ * [VVV VV VVVV V V V ] // Visible tasks
+ * [RRR RR XXXX X X X ] // Visible range tasks, eg. if the device only shows 5 tasks,
+ * // 'X' tasks are trimmed.
*/
class RecentTasks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
@@ -488,6 +502,18 @@
}
}
+ void onLockTaskModeStateChanged(int lockTaskModeState, int userId) {
+ if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) {
+ return;
+ }
+ for (int i = mTasks.size() - 1; i >= 0; --i) {
+ final TaskRecord tr = mTasks.get(i);
+ if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+ remove(tr);
+ }
+ }
+ }
+
void removeTasksByPackageName(String packageName, int userId) {
for (int i = mTasks.size() - 1; i >= 0; --i) {
final TaskRecord tr = mTasks.get(i);
@@ -496,7 +522,8 @@
if (tr.userId != userId) return;
if (!taskPackageName.equals(packageName)) return;
- mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+ mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS,
+ "remove-package-task");
}
}
@@ -513,7 +540,7 @@
&& (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
if (sameComponent) {
mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
- REMOVE_FROM_RECENTS);
+ REMOVE_FROM_RECENTS, "disabled-package");
}
}
}
@@ -1001,12 +1028,13 @@
continue;
} else {
numVisibleTasks++;
- if (isInVisibleRange(task, numVisibleTasks)) {
+ if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) {
// Keep visible tasks in range
i++;
continue;
} else {
- // Fall through to trim visible tasks that are no longer in range
+ // Fall through to trim visible tasks that are no longer in range and
+ // trimmable
if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
"Trimming out-of-range visible task=" + task);
}
@@ -1122,6 +1150,28 @@
}
/**
+ * @return whether the given task can be trimmed even if it is outside the visible range.
+ */
+ protected boolean isTrimmable(TaskRecord task) {
+ final ActivityStack stack = task.getStack();
+ final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack;
+
+ // No stack for task, just trim it
+ if (stack == null) {
+ return true;
+ }
+
+ // Ignore tasks from different displays
+ if (stack.getDisplay() != homeStack.getDisplay()) {
+ return false;
+ }
+
+ // Trim tasks that are in stacks that are behind the home stack
+ final ActivityDisplay display = stack.getDisplay();
+ return display.getIndexOf(stack) < display.getIndexOf(homeStack);
+ }
+
+ /**
* If needed, remove oldest existing entries in recents that are for the same kind
* of task as the given one.
*/
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 365990f..83965ee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1311,7 +1311,8 @@
* Completely remove all activities associated with an existing
* task starting at a specified index.
*/
- final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
+ final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
+ String reason) {
int numActivities = mActivities.size();
for ( ; activityNdx < numActivities; ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
@@ -1325,7 +1326,7 @@
--activityNdx;
--numActivities;
} else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
- "clear-task-index", false, pauseImmediately)) {
+ reason, false, pauseImmediately)) {
--activityNdx;
--numActivities;
}
@@ -1337,7 +1338,7 @@
*/
void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
+ performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
mReuseTask = false;
}
@@ -1408,9 +1409,9 @@
return null;
}
- void removeTaskActivitiesLocked(boolean pauseImmediately) {
+ void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
// Just remove the entire task.
- performClearTaskAtIndexLocked(0, pauseImmediately);
+ performClearTaskAtIndexLocked(0, pauseImmediately, reason);
}
String lockTaskAuthToString() {
@@ -1990,7 +1991,6 @@
final Rect currentBounds = getOverrideBounds();
mTmpConfig.setTo(getOverrideConfiguration());
- final boolean oldMatchParentBounds = matchParentBounds();
final Configuration newConfig = getOverrideConfiguration();
final boolean matchParentBounds = bounds == null || bounds.isEmpty();
@@ -2013,12 +2013,17 @@
mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
}
onOverrideConfigurationChanged(newConfig);
+ return !mTmpConfig.equals(newConfig);
+ }
- if (matchParentBounds != oldMatchParentBounds) {
+ @Override
+ public void onConfigurationChanged(Configuration newParentConfig) {
+ final boolean wasInMultiWindowMode = inMultiWindowMode();
+ super.onConfigurationChanged(newParentConfig);
+ if (wasInMultiWindowMode != inMultiWindowMode()) {
mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
-
- return !mTmpConfig.equals(newConfig);
+ // TODO: Should also take care of Pip mode changes here.
}
/** Clears passed config and fills it with new override values. */
diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
index 600589a..b6f6ae6 100644
--- a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
+++ b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -39,7 +39,7 @@
final PackageManager pm = context.getPackageManager();
final CharSequence label = appInfo.loadSafeLabel(pm);
final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
- label, appInfo.compileSdkVersionCodename);
+ label);
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setPositiveButton(R.string.ok, null)
diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 90888f0..2c6fe94 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -34,6 +34,7 @@
import android.os.BatteryManager;
import android.os.Environment;
import android.os.Handler;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -162,7 +163,10 @@
UserHandle.USER_CURRENT);
mSensorListener = new SensorListener();
- mInjector.registerSensorListener(mContext, mSensorListener);
+
+ if (mInjector.isInteractive(mContext)) {
+ mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
+ }
mSettingsObserver = new SettingsObserver(mBgHandler);
mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
@@ -170,6 +174,8 @@
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SHUTDOWN);
intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
mBroadcastReceiver = new Receiver();
mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
@@ -584,6 +590,11 @@
if (level != -1 && scale != 0) {
batteryLevelChanged(level, scale);
}
+ } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mInjector.unregisterSensorListener(mContext, mSensorListener);
+ } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mInjector.registerSensorListener(mContext, mSensorListener,
+ mInjector.getBackgroundHandler());
}
}
}
@@ -591,11 +602,11 @@
@VisibleForTesting
static class Injector {
public void registerSensorListener(Context context,
- SensorEventListener sensorListener) {
+ SensorEventListener sensorListener, Handler handler) {
SensorManager sensorManager = context.getSystemService(SensorManager.class);
Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
sensorManager.registerListener(sensorListener,
- lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
}
public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
@@ -675,5 +686,9 @@
public void cancelIdleJob(Context context) {
BrightnessIdleJob.cancelJob(context);
}
+
+ public boolean isInteractive(Context context) {
+ return context.getSystemService(PowerManager.class).isInteractive();
+ }
}
}
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index f67bd04..fc4015d 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,19 +16,12 @@
package com.android.server.job.controllers;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Pair;
import android.util.Slog;
-import android.util.SparseBooleanArray;
import com.android.internal.util.ArrayUtils;
import com.android.server.ForceAppStandbyTracker;
@@ -37,7 +30,6 @@
import com.android.server.job.JobStore;
import java.io.PrintWriter;
-import java.util.function.Predicate;
public final class BackgroundJobsController extends StateController {
@@ -51,9 +43,6 @@
private final JobSchedulerService mJobSchedulerService;
private final IDeviceIdleController mDeviceIdleController;
- private int[] mPowerWhitelistedUserAppIds;
- private int[] mTempWhitelistedAppIds;
-
private final ForceAppStandbyTracker mForceAppStandbyTracker;
@@ -67,28 +56,6 @@
}
}
- private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (mLock) {
- try {
- switch (intent.getAction()) {
- case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
- mPowerWhitelistedUserAppIds =
- mDeviceIdleController.getAppIdUserWhitelist();
- break;
- case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- break;
- }
- } catch (RemoteException rexc) {
- Slog.e(LOG_TAG, "Device idle controller not reachable");
- }
- updateAllJobRestrictionsLocked();
- }
- }
- };
-
private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
super(service, context, lock);
mJobSchedulerService = service;
@@ -97,19 +64,6 @@
mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
- try {
- mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
- mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
- } catch (RemoteException rexc) {
- // Shouldn't happen as they are in the same process.
- Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
- }
- IntentFilter powerWhitelistFilter = new IntentFilter();
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
- powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
- context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
- null, null);
-
mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
mForceAppStandbyTracker.start();
}
@@ -128,31 +82,7 @@
public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
pw.println("BackgroundJobsController");
- pw.print("Force all apps standby: ");
- pw.println(mForceAppStandbyTracker.isForceAllAppsStandbyEnabled());
-
- pw.print("Foreground uids: [");
- final SparseBooleanArray foregroundUids = mForceAppStandbyTracker.getForegroudUids();
-
- String sep = "";
- for (int i = 0; i < foregroundUids.size(); i++) {
- if (foregroundUids.valueAt(i)) {
- pw.print(sep);
- pw.print(UserHandle.formatUid(foregroundUids.keyAt(i)));
- sep = " ";
- }
- }
- pw.println("]");
-
- pw.println("Restricted packages:");
- for (Pair<Integer, String> uidAndPackage
- : mForceAppStandbyTracker.getRestrictedUidPackages()) {
- pw.print(" ");
- pw.print(UserHandle.formatUid(uidAndPackage.first));
- pw.print(" ");
- pw.print(uidAndPackage.second);
- pw.println();
- }
+ mForceAppStandbyTracker.dump(pw, "");
pw.println("Job state:");
mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -165,15 +95,17 @@
pw.print(" from ");
UserHandle.formatUid(pw, uid);
pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
- if (isWhitelistedLocked(uid)) {
+ if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+ mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
pw.print(", whitelisted");
}
pw.print(": ");
pw.print(jobStatus.getSourcePackageName());
- pw.print(" [background restrictions ");
+ pw.print(" [RUN_ANY_IN_BACKGROUND ");
pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
- jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) ? "off]" : "on]");
+ jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+ ? "allowed]" : "disallowed]");
if ((jobStatus.satisfiedConstraints
& JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
@@ -218,19 +150,12 @@
}
}
- private boolean isWhitelistedLocked(int uid) {
- final int appId = UserHandle.getAppId(uid);
- return ArrayUtils.contains(mTempWhitelistedAppIds, appId)
- || ArrayUtils.contains(mPowerWhitelistedUserAppIds, appId);
- }
-
boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
final int uid = jobStatus.getSourceUid();
final String packageName = jobStatus.getSourcePackageName();
- final boolean canRun = isWhitelistedLocked(uid)
- || !mForceAppStandbyTracker.isRestricted(uid, packageName);
+ final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
}
@@ -261,13 +186,18 @@
private final Listener mForceAppStandbyListener = new Listener() {
@Override
- public void onRestrictionChanged(int uid, String packageName) {
+ public void updateAllJobs() {
+ updateAllJobRestrictionsLocked();
+ }
+
+ @Override
+ public void updateJobsForUid(int uid) {
updateJobRestrictionsForUidLocked(uid);
}
@Override
- public void onGlobalRestrictionChanged() {
- updateAllJobRestrictionsLocked();
+ public void updateJobsForUidPackage(int uid, String packageName) {
+ updateJobRestrictionsForUidLocked(uid);
}
};
}
diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java
new file mode 100644
index 0000000..41d9feb
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
+ * notification callbacks. This class implements the IContextHubClient object, and the implemented
+ * APIs must be thread-safe.
+ *
+ * @hide
+ */
+public class ContextHubClientBroker extends IContextHubClient.Stub
+ implements IBinder.DeathRecipient {
+ private static final String TAG = "ContextHubClientBroker";
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub HAL.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * The manager that registered this client.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
+ * The ID of the hub that this client is attached to.
+ */
+ private final int mAttachedContextHubId;
+
+ /*
+ * The host end point ID of this client.
+ */
+ private final short mHostEndPointId;
+
+ /*
+ * The remote callback interface for this client.
+ */
+ private final IContextHubClientCallback mCallbackInterface;
+
+ /*
+ * false if the connection has been closed by the client, true otherwise.
+ */
+ private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+
+ /* package */ ContextHubClientBroker(
+ Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+ int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
+ mAttachedContextHubId = contextHubId;
+ mHostEndPointId = hostEndPointId;
+ mCallbackInterface = callback;
+ }
+
+ /**
+ * Attaches a death recipient for this client
+ *
+ * @throws RemoteException if the client has already died
+ */
+ /* package */ void attachDeathRecipient() throws RemoteException {
+ mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+ }
+
+ /**
+ * Sends from this client to a nanoapp.
+ *
+ * @param message the message to send
+ * @return the error code of sending the message
+ */
+ @ContextHubTransaction.Result
+ @Override
+ public int sendMessageToNanoApp(NanoAppMessage message) {
+ ContextHubServiceUtil.checkPermissions(mContext);
+
+ int result;
+ if (mConnectionOpen.get()) {
+ ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
+ mHostEndPointId, message);
+
+ try {
+ result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+ + mAttachedContextHubId + ")", e);
+ result = Result.UNKNOWN_FAILURE;
+ }
+ } else {
+ Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
+ result = Result.UNKNOWN_FAILURE;
+ }
+
+ return ContextHubServiceUtil.toTransactionResult(result);
+ }
+
+ /**
+ * Closes the connection for this client with the service.
+ */
+ @Override
+ public void close() {
+ if (mConnectionOpen.getAndSet(false)) {
+ mClientManager.unregisterClient(mHostEndPointId);
+ }
+ }
+
+ /**
+ * Invoked when the underlying binder of this broker has died at the client process.
+ */
+ public void binderDied() {
+ try {
+ IContextHubClient.Stub.asInterface(this).close();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while closing client on death", e);
+ }
+ }
+
+ /**
+ * @return the ID of the context hub this client is attached to
+ */
+ /* package */ int getAttachedContextHubId() {
+ return mAttachedContextHubId;
+ }
+
+ /**
+ * @return the host endpoint ID of this client
+ */
+ /* package */ short getHostEndPointId() {
+ return mHostEndPointId;
+ }
+
+ /**
+ * Sends a message to the client associated with this object.
+ *
+ * @param message the message that came from a nanoapp
+ */
+ /* package */ void sendMessageToClient(NanoAppMessage message) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onMessageFromNanoApp(message);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+ + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppLoaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(long nanoAppId) {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onNanoAppUnloaded(nanoAppId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client"
+ + " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+
+ /**
+ * Handles a hub reset for this client.
+ */
+ /* package */ void onHubReset() {
+ if (mConnectionOpen.get()) {
+ try {
+ mCallbackInterface.onHubReset();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException while calling onHubReset on client" +
+ " (host endpoint ID = " + mHostEndPointId + ")", e);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java
new file mode 100644
index 0000000..d58a746
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubClientManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubClientManager {
+ private static final String TAG = "ContextHubClientManager";
+
+ /*
+ * The maximum host endpoint ID value that a client can be assigned.
+ */
+ private static final int MAX_CLIENT_ID = 0x7fff;
+
+ /*
+ * Local flag to enable debug logging.
+ */
+ private static final boolean DEBUG_LOG_ENABLED = true;
+
+ /*
+ * The context of the service.
+ */
+ private final Context mContext;
+
+ /*
+ * The proxy to talk to the Context Hub.
+ */
+ private final IContexthub mContextHubProxy;
+
+ /*
+ * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
+ * A concurrent data structure is used since the registration/unregistration can occur in
+ * multiple threads.
+ */
+ private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap =
+ new ConcurrentHashMap<>();
+
+ /*
+ * The next host endpoint ID to start iterating for the next available host endpoint ID.
+ */
+ private int mNextHostEndpointId = 0;
+
+ /* package */ ContextHubClientManager(
+ Context context, IContexthub contextHubProxy) {
+ mContext = context;
+ mContextHubProxy = contextHubProxy;
+ }
+
+ /**
+ * Registers a new client with the service.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the client interface
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ /* package */ IContextHubClient registerClient(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
+
+ try {
+ broker.attachDeathRecipient();
+ } catch (RemoteException e) {
+ // The client process has died, so we close the connection and return null.
+ Log.e(TAG, "Failed to attach death recipient to client");
+ broker.close();
+ return null;
+ }
+
+ Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
+ return IContextHubClient.Stub.asInterface(broker);
+ }
+
+ /**
+ * Handles a message sent from a nanoapp.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+ NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
+
+ if (DEBUG_LOG_ENABLED) {
+ String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast";
+ Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x"
+ + Long.toHexString(clientMessage.getNanoAppId()));
+ }
+
+ if (clientMessage.isBroadcastMessage()) {
+ broadcastMessage(contextHubId, clientMessage);
+ } else {
+ ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
+ if (proxy != null) {
+ proxy.sendMessageToClient(clientMessage);
+ } else {
+ Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+ + message.hostEndPoint + ")");
+ }
+ }
+ }
+
+ /**
+ * Unregisters a client from the service.
+ *
+ * This method should be invoked as a result of a client calling the ContextHubClient.close(),
+ * or if the client process has died.
+ *
+ * @param hostEndPointId the host endpoint ID of the client that has died
+ */
+ /* package */ void unregisterClient(short hostEndPointId) {
+ if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
+ Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
+ } else {
+ Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+ + hostEndPointId);
+ }
+ }
+
+ /**
+ * Handles a nanoapp load event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was loaded.
+ * @param nanoAppId the ID of the nanoapp that was loaded.
+ */
+ /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
+ }
+
+ /**
+ * Handles a nanoapp unload event.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp was unloaded.
+ * @param nanoAppId the ID of the nanoapp that was unloaded.
+ */
+ /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
+ forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
+ }
+
+ /**
+ * Handles a hub reset.
+ *
+ * @param contextHubId the ID of the hub that has reset.
+ */
+ /* package */ void onHubReset(int contextHubId) {
+ forEachClientOfHub(contextHubId, client -> client.onHubReset());
+ }
+
+ /**
+ * Creates a new ContextHubClientBroker object for a client and registers it with the client
+ * manager.
+ *
+ * @param clientCallback the callback interface of the client to register
+ * @param contextHubId the ID of the hub this client is attached to
+ *
+ * @return the ContextHubClientBroker object
+ *
+ * @throws IllegalStateException if max number of clients have already registered
+ */
+ private synchronized ContextHubClientBroker createNewClientBroker(
+ IContextHubClientCallback clientCallback, int contextHubId) {
+ if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
+ throw new IllegalStateException("Could not register client - max limit exceeded");
+ }
+
+ ContextHubClientBroker broker = null;
+ int id = mNextHostEndpointId;
+ for (int i = 0; i <= MAX_CLIENT_ID; i++) {
+ if (!mHostEndPointIdToClientMap.containsKey(id)) {
+ broker = new ContextHubClientBroker(
+ mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
+ mHostEndPointIdToClientMap.put((short)id, broker);
+ mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ break;
+ }
+
+ id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+ }
+
+ return broker;
+ }
+
+ /**
+ * Broadcasts a message from a nanoapp to all clients attached to the associated hub.
+ *
+ * @param contextHubId the ID of the hub where the nanoapp sent the message from
+ * @param message the message send by a nanoapp
+ */
+ private void broadcastMessage(int contextHubId, NanoAppMessage message) {
+ forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
+ }
+
+ /**
+ * Runs a command for each client that is attached to a hub with the given ID.
+ *
+ * @param contextHubId the ID of the hub
+ * @param callback the command to invoke for the client
+ */
+ private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
+ for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+ if (broker.getAttachedContextHubId() == contextHubId) {
+ callback.accept(broker);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index da481a8..e08c659 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -16,12 +16,10 @@
package com.android.server.location;
-import android.Manifest;
import android.content.Context;
import android.hardware.contexthub.V1_0.AsyncEventType;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
-import android.hardware.contexthub.V1_0.HostEndPoint;
import android.hardware.contexthub.V1_0.HubAppInfo;
import android.hardware.contexthub.V1_0.IContexthub;
import android.hardware.contexthub.V1_0.IContexthubCallback;
@@ -29,13 +27,17 @@
import android.hardware.contexthub.V1_0.TransactionResult;
import android.hardware.location.ContextHubInfo;
import android.hardware.location.ContextHubMessage;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubCallback;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
import android.hardware.location.IContextHubService;
import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoApp;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppFilter;
import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -49,7 +51,9 @@
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
@@ -58,9 +62,6 @@
*/
public class ContextHubService extends IContextHubService.Stub {
private static final String TAG = "ContextHubService";
- private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
- private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
- + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
/*
* Constants for the type of transaction that is defined by ContextHubService.
@@ -104,6 +105,12 @@
// The manager for transaction queue
private final ContextHubTransactionManager mTransactionManager;
+ // The manager for sending messages to/from clients
+ private final ContextHubClientManager mClientManager;
+
+ // The default client for old API clients
+ private final Map<Integer, IContextHubClient> mDefaultClientMap;
+
/**
* Class extending the callback to register with a Context Hub.
*/
@@ -146,21 +153,34 @@
mContextHubProxy = getContextHubProxy();
if (mContextHubProxy == null) {
mTransactionManager = null;
+ mClientManager = null;
+ mDefaultClientMap = Collections.EMPTY_MAP;
mContextHubInfo = new ContextHubInfo[0];
return;
}
- mTransactionManager = new ContextHubTransactionManager(mContextHubProxy);
+ mClientManager = new ContextHubClientManager(mContext, mContextHubProxy);
+ mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager);
List<ContextHub> hubList;
try {
hubList = mContextHubProxy.getHubs();
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException while getting Context Hub info");
+ Log.e(TAG, "RemoteException while getting Context Hub info", e);
hubList = Collections.emptyList();
}
mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+ HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
+ for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+ int contextHubId = contextHubInfo.getId();
+
+ IContextHubClient client = mClientManager.registerClient(
+ createDefaultClientCallback(contextHubId), contextHubId);
+ defaultClientMap.put(contextHubId, client);
+ }
+ mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
+
for (ContextHubInfo contextHubInfo : mContextHubInfo) {
int contextHubId = contextHubInfo.getId();
try {
@@ -168,7 +188,7 @@
contextHubId, new ContextHubServiceCallback(contextHubId));
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
- + contextHubId + ")");
+ + contextHubId + ")", e);
}
}
@@ -185,6 +205,53 @@
}
/**
+ * Creates a default client callback for old API clients.
+ *
+ * @param contextHubId the ID of the hub to attach this client to
+ * @return the internal callback interface
+ */
+ private IContextHubClientCallback createDefaultClientCallback(int contextHubId) {
+ return new IContextHubClientCallback.Stub() {
+ @Override
+ public void onMessageFromNanoApp(NanoAppMessage message) {
+ int nanoAppInstanceId =
+ mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ?
+ mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1;
+
+ onMessageReceiptOldApi(
+ message.getMessageType(), contextHubId, nanoAppInstanceId,
+ message.getMessageBody());
+ }
+
+ @Override
+ public void onHubReset() {
+ byte[] data = {TransactionResult.SUCCESS};
+ onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+ }
+
+ @Override
+ public void onNanoAppAborted(long nanoAppId, int abortCode) {
+ }
+
+ @Override
+ public void onNanoAppLoaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppUnloaded(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppEnabled(long nanoAppId) {
+ }
+
+ @Override
+ public void onNanoAppDisabled(long nanoAppId) {
+ }
+ };
+ }
+
+ /**
* @return the IContexthub proxy interface
*/
private IContexthub getContextHubProxy() {
@@ -192,7 +259,7 @@
try {
proxy = IContexthub.getService(true /* retry */);
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy");
+ Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
} catch (NoSuchElementException e) {
Log.i(TAG, "Context Hub HAL service not found");
}
@@ -204,6 +271,7 @@
public int registerCallback(IContextHubCallback callback) throws RemoteException {
checkPermissions();
mCallbacksList.register(callback);
+
Log.d(TAG, "Added callback, total callbacks " +
mCallbacksList.getRegisteredCallbackCount());
return 0;
@@ -292,7 +360,7 @@
@Override
public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
byte[] data = {(byte) result};
- onMessageReceipt(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+ onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
}
};
}
@@ -449,41 +517,37 @@
return -1;
}
if (msg.getData() == null) {
- Log.w(TAG, "ContextHubMessage message body cannot be null");
+ Log.e(TAG, "ContextHubMessage message body cannot be null");
+ return -1;
+ }
+ if (!mDefaultClientMap.containsKey(hubHandle)) {
+ Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
return -1;
}
- int result;
+ boolean success = false;
if (nanoAppHandle == OS_APP_INSTANCE) {
if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
- result = queryNanoAppsInternal(hubHandle);
+ success = (queryNanoAppsInternal(hubHandle) == Result.OK);
} else {
Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
- result = Result.BAD_PARAMS;
}
} else {
NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
if (info != null) {
- ContextHubMsg hubMessage = new ContextHubMsg();
- hubMessage.appName = info.getAppId();
- hubMessage.msgType = msg.getMsgType();
- hubMessage.hostEndPoint = HostEndPoint.UNSPECIFIED;
- ContextHubServiceUtil.copyToByteArrayList(msg.getData(), hubMessage.msg);
+ NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
+ info.getAppId(), msg.getMsgType(), msg.getData());
- try {
- result = mContextHubProxy.sendMessageToHub(hubHandle, hubMessage);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to send nanoapp message - RemoteException");
- result = Result.UNKNOWN_FAILURE;
- }
+ IContextHubClient client = mDefaultClientMap.get(hubHandle);
+ success = (client.sendMessageToNanoApp(message) ==
+ ContextHubTransaction.TRANSACTION_SUCCESS);
} else {
Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
+ nanoAppHandle + " does not exist.");
- result = Result.BAD_PARAMS;
}
}
- return (result == Result.OK ? 0 : -1);
+ return success ? 0 : -1;
}
/**
@@ -493,12 +557,7 @@
* @param message the message contents
*/
private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
- // TODO(b/67734082): Send to new API clients
- byte[] data = ContextHubServiceUtil.createPrimitiveByteArray(message.msg);
-
- int nanoAppInstanceId = mNanoAppIdToInstanceMap.containsKey(message.appName) ?
- mNanoAppIdToInstanceMap.get(message.appName) : -1;
- onMessageReceipt(message.msgType, contextHubId, nanoAppInstanceId, data);
+ mClientManager.onMessageFromNanoApp(contextHubId, message);
}
/**
@@ -534,7 +593,7 @@
data[0] = (byte) result;
ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
- onMessageReceipt(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
}
/**
@@ -552,7 +611,7 @@
byte[] data = new byte[1];
data[0] = (byte) result;
- onMessageReceipt(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+ onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
}
/**
@@ -577,8 +636,7 @@
mTransactionManager.onHubReset();
queryNanoAppsInternal(contextHubId);
- byte[] data = {TransactionResult.SUCCESS};
- onMessageReceipt(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+ mClientManager.onHubReset(contextHubId);
} else {
Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
+ eventType + ")");
@@ -641,6 +699,45 @@
}
}
+ /**
+ * @param contextHubId the hub ID to validate
+ * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
+ */
+ private boolean isValidContextHubId(int contextHubId) {
+ for (ContextHubInfo hubInfo : mContextHubInfo) {
+ if (hubInfo.getId() == contextHubId) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Creates and registers a client at the service for the specified Context Hub.
+ *
+ * @param clientCallback the client interface to register with the service
+ * @param contextHubId the ID of the hub this client is attached to
+ * @return the generated client interface, null if registration was unsuccessful
+ *
+ * @throws IllegalArgumentException if contextHubId is not a valid ID
+ * @throws IllegalStateException if max number of clients have already registered
+ * @throws NullPointerException if clientCallback is null
+ */
+ @Override
+ public IContextHubClient createClient(
+ IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+ checkPermissions();
+ if (!isValidContextHubId(contextHubId)) {
+ throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
+ }
+ if (clientCallback == null) {
+ throw new NullPointerException("Cannot register client with null callback");
+ }
+
+ return mClientManager.registerClient(clientCallback, contextHubId);
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -664,10 +761,10 @@
}
private void checkPermissions() {
- mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ ContextHubServiceUtil.checkPermissions(mContext);
}
- private int onMessageReceipt(int msgType, int hubHandle, int appInstance, byte[] data) {
+ private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
if (data == null) {
return -1;
}
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index ddbaf86..6faeb72 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -16,11 +16,15 @@
package com.android.server.location;
+import android.Manifest;
+import android.content.Context;
import android.hardware.contexthub.V1_0.ContextHub;
import android.hardware.contexthub.V1_0.ContextHubMsg;
import android.hardware.contexthub.V1_0.HostEndPoint;
import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.Result;
import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubTransaction;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
@@ -34,6 +38,9 @@
*/
/* package */ class ContextHubServiceUtil {
private static final String TAG = "ContextHubServiceUtil";
+ private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+ private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+ + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
/**
* Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
@@ -165,4 +172,40 @@
message.appName, message.msgType, messageArray,
message.hostEndPoint == HostEndPoint.BROADCAST);
}
+
+ /**
+ * Checks for location hardware permissions.
+ *
+ * @param context the context of the service
+ */
+ /* package */
+ static void checkPermissions(Context context) {
+ context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ /**
+ * Helper function to convert from the HAL Result enum error code to the
+ * ContextHubTransaction.Result type.
+ *
+ * @param halResult the Result enum error code
+ * @return the ContextHubTransaction.Result equivalent
+ */
+ @ContextHubTransaction.Result
+ /* package */
+ static int toTransactionResult(int halResult) {
+ switch (halResult) {
+ case Result.OK:
+ return ContextHubTransaction.TRANSACTION_SUCCESS;
+ case Result.BAD_PARAMS:
+ return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+ case Result.NOT_INIT:
+ return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+ case Result.TRANSACTION_PENDING:
+ return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+ case Result.TRANSACTION_FAILED:
+ case Result.UNKNOWN_FAILURE:
+ default: /* fall through */
+ return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
index 898b76c..47d9d56 100644
--- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java
@@ -58,6 +58,11 @@
private final IContexthub mContextHubProxy;
/*
+ * The manager for all clients for the service.
+ */
+ private final ContextHubClientManager mClientManager;
+
+ /*
* A queue containing the current transactions
*/
private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
@@ -73,8 +78,10 @@
private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
private ScheduledFuture<?> mTimeoutFuture = null;
- /* package */ ContextHubTransactionManager(IContexthub contextHubProxy) {
+ /* package */ ContextHubTransactionManager(
+ IContexthub contextHubProxy, ContextHubClientManager clientManager) {
mContextHubProxy = contextHubProxy;
+ mClientManager = clientManager;
}
/**
@@ -113,6 +120,9 @@
/* package */ void onTransactionComplete(int result) {
try {
onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
+ }
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling client onTransactionComplete");
}
@@ -153,6 +163,9 @@
/* package */ void onTransactionComplete(int result) {
try {
onCompleteCallback.onTransactionComplete(result);
+ if (result == Result.OK) {
+ mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
+ }
} catch (RemoteException e) {
Log.e(TAG, "RemoteException while calling client onTransactionComplete");
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 0000000..2bd9cab
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * 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.net;
+
+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_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+ static final String TAG = "NetworkPolicy";
+
+ static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+ static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final int MAX_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+ private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+ ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+ private static final int EVENT_TYPE_GENERIC = 0;
+ private static final int EVENT_NETWORK_BLOCKED = 1;
+ private static final int EVENT_UID_STATE_CHANGED = 2;
+ private static final int EVENT_POLICIES_CHANGED = 3;
+ private static final int EVENT_METEREDNESS_CHANGED = 4;
+ private static final int EVENT_USER_STATE_REMOVED = 5;
+ private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+ private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+ private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+ private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+ private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+ private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+ private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+ static final int NTWK_BLOCKED_POWER = 0;
+ static final int NTWK_ALLOWED_NON_METERED = 1;
+ static final int NTWK_BLOCKED_BLACKLIST = 2;
+ static final int NTWK_ALLOWED_WHITELIST = 3;
+ static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+ static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+ static final int NTWK_ALLOWED_DEFAULT = 6;
+
+ private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+ private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+ private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+ private final Object mLock = new Object();
+
+ void networkBlocked(int uid, int reason) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+ mNetworkBlockedBuffer.networkBlocked(uid, reason);
+ }
+ }
+
+ void uidStateChanged(int uid, int procState, long procStateSeq) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG,
+ uid + " state changed to " + procState + " with seq=" + procStateSeq);
+ mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+ }
+ }
+
+ void event(String msg) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, msg);
+ mEventsBuffer.event(msg);
+ }
+ }
+
+ void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+ mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+ }
+ }
+
+ void meterednessChanged(int netId, boolean newMetered) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+ mEventsBuffer.meterednessChanged(netId, newMetered);
+ }
+ }
+
+ void removingUserState(int userId) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+ mEventsBuffer.userRemoved(userId);
+ }
+ }
+
+ void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG,
+ getRestrictBackgroundChangedLog(oldValue, newValue));
+ mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+ }
+ }
+
+ void deviceIdleModeEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+ mEventsBuffer.deviceIdleModeEnabled(enabled);
+ }
+ }
+
+ void appIdleStateChanged(int uid, boolean idle) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+ mEventsBuffer.appIdleStateChanged(uid, idle);
+ }
+ }
+
+ void paroleStateChanged(boolean paroleOn) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+ mEventsBuffer.paroleStateChanged(paroleOn);
+ }
+ }
+
+ void tempPowerSaveWlChanged(int appId, boolean added) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+ mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+ }
+ }
+
+ void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ synchronized (mLock) {
+ if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+ mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+ }
+ }
+
+ void firewallChainEnabled(int chain, boolean enabled) {
+ synchronized (mLock) {
+ if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+ mEventsBuffer.firewallChainEnabled(chain, enabled);
+ }
+ }
+
+ void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+ synchronized (mLock) {
+ final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+ + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+ if (LOGD) Slog.d(TAG, log);
+ mEventsBuffer.event(log);
+ }
+ }
+
+ void dumpLogs(IndentingPrintWriter pw) {
+ synchronized (mLock) {
+ pw.println();
+ pw.println("mEventLogs (most recent first):");
+ pw.increaseIndent();
+ mEventsBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mNetworkBlockedLogs (most recent first):");
+ pw.increaseIndent();
+ mNetworkBlockedBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("mUidStateChangeLogs (most recent first):");
+ pw.increaseIndent();
+ mUidStateChangeBuffer.reverseDump(pw);
+ pw.decreaseIndent();
+ }
+ }
+
+ private static String getBlockedReason(int reason) {
+ switch (reason) {
+ case NTWK_BLOCKED_POWER:
+ return "blocked by power restrictions";
+ case NTWK_ALLOWED_NON_METERED:
+ return "allowed on unmetered network";
+ case NTWK_BLOCKED_BLACKLIST:
+ return "blacklisted on metered network";
+ case NTWK_ALLOWED_WHITELIST:
+ return "whitelisted on metered network";
+ case NTWK_ALLOWED_TMP_WHITELIST:
+ return "temporary whitelisted on metered network";
+ case NTWK_BLOCKED_BG_RESTRICT:
+ return "blocked when background is restricted";
+ case NTWK_ALLOWED_DEFAULT:
+ return "allowed by default";
+ default:
+ return String.valueOf(reason);
+ }
+ }
+
+ private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+ return "Policy for " + uid + " changed from "
+ + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+ + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+ }
+
+ private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+ return "Meteredness of netId=" + netId + " changed to " + newMetered;
+ }
+
+ private static String getUserRemovedLog(int userId) {
+ return "Remove state for u" + userId;
+ }
+
+ private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+ return "Changed restrictBackground: " + oldValue + "->" + newValue;
+ }
+
+ private static String getDeviceIdleModeEnabled(boolean enabled) {
+ return "DeviceIdleMode enabled: " + enabled;
+ }
+
+ private static String getAppIdleChangedLog(int uid, boolean idle) {
+ return "App idle state of uid " + uid + ": " + idle;
+ }
+
+ private static String getParoleStateChanged(boolean paroleOn) {
+ return "Parole state: " + paroleOn;
+ }
+
+ private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+ return "temp-power-save whitelist for " + appId + " changed to: " + added;
+ }
+
+ private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+ return String.format("Firewall rule changed: %d-%s-%s",
+ uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+ }
+
+ private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+ return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+ }
+
+ private static String getFirewallChainName(int chain) {
+ switch (chain) {
+ case FIREWALL_CHAIN_DOZABLE:
+ return FIREWALL_CHAIN_NAME_DOZABLE;
+ case FIREWALL_CHAIN_STANDBY:
+ return FIREWALL_CHAIN_NAME_STANDBY;
+ case FIREWALL_CHAIN_POWERSAVE:
+ return FIREWALL_CHAIN_NAME_POWERSAVE;
+ default:
+ return String.valueOf(chain);
+ }
+ }
+
+ private static String getFirewallRuleName(int rule) {
+ switch (rule) {
+ case FIREWALL_RULE_DEFAULT:
+ return "default";
+ case FIREWALL_RULE_ALLOW:
+ return "allow";
+ case FIREWALL_RULE_DENY:
+ return "deny";
+ default:
+ return String.valueOf(rule);
+ }
+ }
+
+ private final static class LogBuffer extends RingBuffer<Data> {
+ private static final SimpleDateFormat sFormatter
+ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+ private static final Date sDate = new Date();
+
+ public LogBuffer(int capacity) {
+ super(Data.class, capacity);
+ }
+
+ public void uidStateChanged(int uid, int procState, long procStateSeq) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = procState;
+ data.lfield1 = procStateSeq;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void event(String msg) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TYPE_GENERIC;
+ data.sfield1 = msg;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void networkBlocked(int uid, int reason) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_NETWORK_BLOCKED;
+ data.ifield1 = uid;
+ data.ifield2 = reason;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_POLICIES_CHANGED;
+ data.ifield1 = uid;
+ data.ifield2 = oldPolicy;
+ data.ifield3 = newPolicy;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void meterednessChanged(int netId, boolean newMetered) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_METEREDNESS_CHANGED;
+ data.ifield1 = netId;
+ data.bfield1 = newMetered;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void userRemoved(int userId) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_USER_STATE_REMOVED;
+ data.ifield1 = userId;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_RESTRICT_BG_CHANGED;
+ data.bfield1 = oldValue;
+ data.bfield2 = newValue;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void deviceIdleModeEnabled(boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void appIdleStateChanged(int uid, boolean idle) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_APP_IDLE_STATE_CHANGED;
+ data.ifield1 = uid;
+ data.bfield1 = idle;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void paroleStateChanged(boolean paroleOn) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_PAROLE_STATE_CHANGED;
+ data.bfield1 = paroleOn;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void tempPowerSaveWlChanged(int appId, boolean added) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+ data.ifield1 = appId;
+ data.bfield1 = added;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+ data.ifield1 = chain;
+ data.ifield2 = uid;
+ data.ifield3 = rule;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void firewallChainEnabled(int chain, boolean enabled) {
+ final Data data = getNextSlot();
+ if (data == null) return;
+
+ data.reset();
+ data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+ data.ifield1 = chain;
+ data.bfield1 = enabled;
+ data.timeStamp = System.currentTimeMillis();
+ }
+
+ public void reverseDump(IndentingPrintWriter pw) {
+ final Data[] allData = toArray();
+ for (int i = allData.length - 1; i >= 0; --i) {
+ if (allData[i] == null) {
+ pw.println("NULL");
+ continue;
+ }
+ pw.print(formatDate(allData[i].timeStamp));
+ pw.print(" - ");
+ pw.println(getContent(allData[i]));
+ }
+ }
+
+ public String getContent(Data data) {
+ switch (data.type) {
+ case EVENT_TYPE_GENERIC:
+ return data.sfield1;
+ case EVENT_NETWORK_BLOCKED:
+ return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+ case EVENT_UID_STATE_CHANGED:
+ return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+ + "-" + data.lfield1;
+ case EVENT_POLICIES_CHANGED:
+ return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_METEREDNESS_CHANGED:
+ return getMeterednessChangedLog(data.ifield1, data.bfield1);
+ case EVENT_USER_STATE_REMOVED:
+ return getUserRemovedLog(data.ifield1);
+ case EVENT_RESTRICT_BG_CHANGED:
+ return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+ case EVENT_DEVICE_IDLE_MODE_ENABLED:
+ return getDeviceIdleModeEnabled(data.bfield1);
+ case EVENT_APP_IDLE_STATE_CHANGED:
+ return getAppIdleChangedLog(data.ifield1, data.bfield1);
+ case EVENT_PAROLE_STATE_CHANGED:
+ return getParoleStateChanged(data.bfield1);
+ case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+ return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+ case EVENT_UID_FIREWALL_RULE_CHANGED:
+ return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+ case EVENT_FIREWALL_CHAIN_ENABLED:
+ return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+ default:
+ return String.valueOf(data.type);
+ }
+ }
+
+ private String formatDate(long millis) {
+ sDate.setTime(millis);
+ return sFormatter.format(sDate);
+ }
+ }
+
+ public final static class Data {
+ int type;
+ long timeStamp;
+
+ int ifield1;
+ int ifield2;
+ int ifield3;
+ long lfield1;
+ boolean bfield1;
+ boolean bfield2;
+ String sfield1;
+
+ public void reset(){
+ sfield1 = null;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd4..fdfe241 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@
* </ul>
*/
public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
- static final String TAG = "NetworkPolicy";
- private static final boolean LOGD = false;
- private static final boolean LOGV = false;
+ static final String TAG = NetworkPolicyLogger.TAG;
+ private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+ private static final boolean LOGV = NetworkPolicyLogger.LOGV;
private static final int VERSION_INIT = 1;
private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@
private static final int VERSION_ADDED_CYCLE = 11;
private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
- /**
- * Max items written to {@link #ProcStateSeqHistory}.
- */
- @VisibleForTesting
- public static final int MAX_PROC_STATE_SEQ_HISTORY =
- ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
@VisibleForTesting
public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
@VisibleForTesting
@@ -471,13 +471,7 @@
private ActivityManagerInternal mActivityManagerInternal;
- /**
- * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
- * the uid and procStateSeq will be written to this and will be printed as part of dump.
- */
- @VisibleForTesting
- public ProcStateSeqHistory mObservedHistory
- = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+ private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
// TODO: keep whitelist of system-critical services that should never have
// rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@
.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+ mLogger.meterednessChanged(network.netId, newMetered);
mNetworkMetered.put(network.netId, newMetered);
updateNetworkRulesNL();
}
@@ -2148,6 +2143,7 @@
final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@
policy |= oldPolicy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2185,6 +2182,7 @@
policy = oldPolicy & ~policy;
if (oldPolicy != policy) {
setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+ mLogger.uidPolicyChanged(uid, oldPolicy, policy);
}
}
}
@@ -2264,7 +2262,7 @@
*/
boolean removeUserStateUL(int userId, boolean writePolicy) {
- if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+ mLogger.removingUserState(userId);
boolean changed = false;
// Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@
@Override
public void onTetheringChanged(String iface, boolean tethering) {
// No need to enforce permission because setRestrictBackground() will do it.
- if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
synchronized (mUidRulesFirstLock) {
if (mRestrictBackground && tethering) {
Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@
}
sendRestrictBackgroundChangedMsg();
+ mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@
return;
}
mDeviceIdleMode = enabled;
+ mLogger.deviceIdleModeEnabled(enabled);
if (mSystemReady) {
// Device idle change means we need to rebuild rules for all
// known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@
}
fout.decreaseIndent();
- fout.println("Observed uid state changes:");
- fout.increaseIndent();
- mObservedHistory.dumpUL(fout);
- fout.decreaseIndent();
+ mLogger.dumpLogs(fout);
}
}
}
@@ -3750,8 +3746,8 @@
try {
final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
- if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
synchronized (mUidRulesFirstLock) {
+ mLogger.appIdleStateChanged(uid, idle);
updateRuleForAppIdleUL(uid);
updateRulesForPowerRestrictionsUL(uid);
}
@@ -3762,6 +3758,7 @@
@Override
public void onParoleStateChanged(boolean isParoleOn) {
synchronized (mUidRulesFirstLock) {
+ mLogger.paroleStateChanged(isParoleOn);
updateRulesForAppIdleParoleUL();
}
}
@@ -3947,7 +3944,7 @@
synchronized (mUidRulesFirstLock) {
// We received a uid state change callback, add it to the history so that it
// will be useful for debugging.
- mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+ mLogger.uidStateChanged(uid, procState, procStateSeq);
// Now update the network policy rules as per the updated uid state.
updateUidStateUL(uid, procState);
// Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@
rules[index] = uidRules.valueAt(index);
}
mNetworkManager.setFirewallUidRules(chain, uids, rules);
+ mLogger.firewallRulesChanged(chain, uids, rules);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4107,6 +4105,7 @@
try {
mNetworkManager.setFirewallUidRule(chain, uid, rule);
+ mLogger.uidFirewallRuleChanged(chain, uid, rule);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem setting firewall uid rules", e);
} catch (RemoteException e) {
@@ -4129,6 +4128,7 @@
mFirewallChainStates.put(chain, enable);
try {
mNetworkManager.setFirewallChainEnabled(chain, enable);
+ mLogger.firewallChainEnabled(chain, enable);
} catch (IllegalStateException e) {
Log.wtf(TAG, "problem enable firewall chain", e);
} catch (RemoteException e) {
@@ -4305,30 +4305,30 @@
isBackgroundRestricted = mRestrictBackground;
}
if (hasRule(uidRules, RULE_REJECT_ALL)) {
- if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
return true;
}
if (!isNetworkMetered) {
- if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
return false;
}
if (hasRule(uidRules, RULE_REJECT_METERED)) {
- if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
return true;
}
if (hasRule(uidRules, RULE_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
return false;
}
if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
- if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
return false;
}
if (isBackgroundRestricted) {
- if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+ mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
return true;
}
- if (LOGV) logUidStatus(uid, "allowed by default");
+ mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
return false;
}
@@ -4379,6 +4379,7 @@
@Override
public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
synchronized (mUidRulesFirstLock) {
+ mLogger.tempPowerSaveWlChanged(appId, added);
if (added) {
mPowerSaveTempWhitelistAppIds.put(appId, true);
} else {
@@ -4393,80 +4394,6 @@
return (uidRules & rule) != 0;
}
- private static void logUidStatus(int uid, String descr) {
- Slog.d(TAG, String.format("uid %d is %s", uid, descr));
- }
-
- /**
- * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
- * (uid, procStateSeq) pairs.
- */
- @VisibleForTesting
- public static final class ProcStateSeqHistory {
- private static final int INVALID_UID = -1;
-
- /**
- * Denotes maximum number of items this history can hold.
- */
- private final int mMaxCapacity;
- /**
- * Used for storing the uid information.
- */
- private final int[] mUids;
- /**
- * Used for storing the sequence numbers associated with {@link #mUids}.
- */
- private final long[] mProcStateSeqs;
- /**
- * Points to the next available slot for writing (uid, procStateSeq) pair.
- */
- private int mHistoryNext;
-
- public ProcStateSeqHistory(int maxCapacity) {
- mMaxCapacity = maxCapacity;
- mUids = new int[mMaxCapacity];
- Arrays.fill(mUids, INVALID_UID);
- mProcStateSeqs = new long[mMaxCapacity];
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void addProcStateSeqUL(int uid, long procStateSeq) {
- mUids[mHistoryNext] = uid;
- mProcStateSeqs[mHistoryNext] = procStateSeq;
- mHistoryNext = increaseNext(mHistoryNext, 1);
- }
-
- @GuardedBy("mUidRulesFirstLock")
- public void dumpUL(IndentingPrintWriter fout) {
- if (mUids[0] == INVALID_UID) {
- fout.println("NONE");
- return;
- }
- int index = mHistoryNext;
- do {
- index = increaseNext(index, -1);
- if (mUids[index] == INVALID_UID) {
- break;
- }
- fout.println(getString(mUids[index], mProcStateSeqs[index]));
- } while (index != mHistoryNext);
- }
-
- public static String getString(int uid, long procStateSeq) {
- return "UID=" + uid + " Seq=" + procStateSeq;
- }
-
- private int increaseNext(int next, int increment) {
- next += increment;
- if (next >= mMaxCapacity) {
- next = 0;
- } else if (next < 0) {
- next = mMaxCapacity - 1;
- }
- return next;
- }
- }
-
private class NotificationId {
private final String mTag;
private final int mId;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index cf01400..bec6fc2 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4743,7 +4743,8 @@
}
void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
- if (!mAccessibilityManager.isEnabled()) {
+ if (!mAccessibilityManager.isObservedEventType(
+ AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
return;
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index b4e3583..5f067d4 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -808,11 +808,11 @@
case TYPE_INPUT_METHOD_DIALOG:
// on-screen keyboards and other such input method user interfaces go here.
return 15;
- case TYPE_STATUS_BAR_SUB_PANEL:
- return 17;
case TYPE_STATUS_BAR:
- return 18;
+ return 17;
case TYPE_STATUS_BAR_PANEL:
+ return 18;
+ case TYPE_STATUS_BAR_SUB_PANEL:
return 19;
case TYPE_KEYGUARD_DIALOG:
return 20;
diff --git a/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 87c9274..7c234f9 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -69,6 +69,7 @@
private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+ private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
@@ -190,6 +191,12 @@
private boolean mForceAllAppsStandby;
/**
+ * Whether to put all apps in the stand-by mode.
+ */
+ @GuardedBy("mLock")
+ private boolean mForceBackgroundCheck;
+
+ /**
* Weather to show non-essential sensors (e.g. edge sensors) or not.
*/
@GuardedBy("mLock")
@@ -326,6 +333,7 @@
mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+ mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
// Get default value from Settings.Secure
@@ -395,6 +403,12 @@
case ServiceType.VIBRATION:
return builder.setBatterySaverEnabled(mVibrationDisabled)
.build();
+ case ServiceType.FORCE_ALL_APPS_STANDBY:
+ return builder.setBatterySaverEnabled(mForceAllAppsStandby)
+ .build();
+ case ServiceType.FORCE_BACKGROUND_CHECK:
+ return builder.setBatterySaverEnabled(mForceBackgroundCheck)
+ .build();
case ServiceType.OPTIONAL_SENSORS:
return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
.build();
@@ -438,6 +452,7 @@
pw.println(" " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
pw.println(" " + KEY_GPS_MODE + "=" + mGpsMode);
pw.println(" " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+ pw.println(" " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
pw.println(" " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
pw.println();
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b07fe98..3792bc6 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -90,6 +90,13 @@
boolean showShutdownUi(boolean isReboot, String requestString);
+ /**
+ * Show a rotation suggestion that a user may approve to rotate the screen.
+ *
+ * @param rotation rotation suggestion
+ */
+ void onProposedRotationChanged(int rotation);
+
public interface GlobalActionsListener {
/**
* Called when sysui starts and connects its status bar, or when the status bar binder
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index c78a340..c7c03b4 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -406,6 +406,15 @@
}
return false;
}
+
+ @Override
+ public void onProposedRotationChanged(int rotation) {
+ if (mBar != null){
+ try {
+ mBar.onProposedRotationChanged(rotation);
+ } catch (RemoteException ex) {}
+ }
+ }
};
// ================================================================================
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 56e5922..41348ba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2969,227 +2969,30 @@
* Takes a snapshot of the display. In landscape mode this grabs the whole screen.
* In portrait mode, it grabs the full screenshot.
*
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- Bitmap screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
- Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
- frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
- if (bitmap == null) {
- return null;
- }
-
- if (DEBUG_SCREENSHOT) {
- // TEST IF IT's ALL BLACK
- int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
- bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
- bitmap.getHeight());
- boolean allBlack = true;
- final int firstColor = buffer[0];
- for (int i = 0; i < buffer.length; i++) {
- if (buffer[i] != firstColor) {
- allBlack = false;
- break;
- }
- }
- if (allBlack) {
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final int maxLayer = mScreenshotApplicationState.maxLayer;
- final int minLayer = mScreenshotApplicationState.minLayer;
- Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
- Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
- (appWin != null ?
- appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
- " minLayer=" + minLayer + " maxLayer=" + maxLayer);
- }
- }
-
- // Create a copy of the screenshot that is immutable and backed in ashmem.
- // This greatly reduces the overhead of passing the bitmap between processes.
- Bitmap ret = bitmap.createAshmemBitmap(config);
- bitmap.recycle();
- return ret;
- }
-
- GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor) {
- return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
- wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
- }
-
- private <E> E screenshotApplications(IBinder appToken, int width, int height,
- boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
- boolean includeDecor, Screenshoter<E> screenshoter) {
- int dw = mDisplayInfo.logicalWidth;
- int dh = mDisplayInfo.logicalHeight;
- if (dw == 0 || dh == 0) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. logical widthxheight=" + dw + "x" + dh);
- return null;
- }
-
- E bitmap;
-
- mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
- final Rect frame = new Rect();
- final Rect stackBounds = new Rect();
-
- final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
- * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
- final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
- synchronized(mService.mWindowMap) {
+ Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) {
+ synchronized (mService.mWindowMap) {
if (!mService.mPolicy.isScreenOn()) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
- + " was off.");
- return null;
- }
- // Figure out the part of the screen that is actually the app.
- mScreenshotApplicationState.appWin = null;
- forAllWindows(w -> {
- if (!w.mHasSurface) {
- return false;
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
}
- if (w.mLayer >= aboveAppLayer) {
- return false;
- }
- if (wallpaperOnly && !w.mIsWallpaper) {
- return false;
- }
- if (w.mIsImWindow) {
- return false;
- } else if (w.mIsWallpaper) {
- // If this is the wallpaper layer and we're only looking for the wallpaper layer
- // then the target window state is this one.
- if (wallpaperOnly) {
- mScreenshotApplicationState.appWin = w;
- }
-
- if (mScreenshotApplicationState.appWin == null) {
- // We have not ran across the target window yet, so it is probably behind
- // the wallpaper. This can happen when the keyguard is up and all windows
- // are moved behind the wallpaper. We don't want to include the wallpaper
- // layer in the screenshot as it will cover-up the layer of the target
- // window.
- return false;
- }
- // Fall through. The target window is in front of the wallpaper. For this
- // case we want to include the wallpaper layer in the screenshot because
- // the target window might have some transparent areas.
- } else if (appToken != null) {
- if (w.mAppToken == null || w.mAppToken.token != appToken) {
- // This app window is of no interest if it is not associated with the
- // screenshot app.
- return false;
- }
- mScreenshotApplicationState.appWin = w;
- }
-
- // Include this window.
-
- final WindowStateAnimator winAnim = w.mWinAnimator;
-
- // Don't include wallpaper in bounds calculation
- if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
- if (includeDecor) {
- final Task task = w.getTask();
- if (task != null) {
- task.getBounds(frame);
- } else {
-
- // No task bounds? Too bad! Ain't no screenshot then.
- return true;
- }
- } else {
- final Rect wf = w.mFrame;
- final Rect cr = w.mContentInsets;
- int left = wf.left + cr.left;
- int top = wf.top + cr.top;
- int right = wf.right - cr.right;
- int bottom = wf.bottom - cr.bottom;
- frame.union(left, top, right, bottom);
- w.getVisibleBounds(stackBounds);
- if (!Rect.intersects(frame, stackBounds)) {
- // Set frame empty if there's no intersection.
- frame.setEmpty();
- }
- }
- }
-
- final boolean foundTargetWs =
- (w.mAppToken != null && w.mAppToken.token == appToken)
- || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
- if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
- mScreenshotApplicationState.screenshotReady = true;
- }
-
- if (w.isObscuringDisplay()){
- return true;
- }
- return false;
- }, true /* traverseTopToBottom */);
-
- final WindowState appWin = mScreenshotApplicationState.appWin;
- final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
-
- if (appToken != null && appWin == null) {
- // Can't find a window to snapshot.
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
- "Screenshot: Couldn't find a surface matching " + appToken);
return null;
}
- if (!screenshotReady) {
- Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
- " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
- appWin.mWinAnimator.mDrawState)));
+ if (wallpaperOnly && !shouldScreenshotWallpaper()) {
return null;
}
- // Screenshot is ready to be taken. Everything from here below will continue
- // through the bottom of the loop and return a value. We only stay in the loop
- // because we don't want to release the mWindowMap lock until the screenshot is
- // taken.
+ int dw = mDisplayInfo.logicalWidth;
+ int dh = mDisplayInfo.logicalHeight;
-
- if (!mutableIncludeFullDisplay.value) {
- // Constrain frame to the screen size.
- if (!frame.intersect(0, 0, dw, dh)) {
- frame.setEmpty();
- }
- } else {
- // Caller just wants entire display.
- frame.set(0, 0, dw, dh);
- }
- if (frame.isEmpty()) {
+ if (dw <= 0 || dh <= 0) {
return null;
}
- if (width < 0) {
- width = (int) (frame.width() * frameScale);
- }
- if (height < 0) {
- height = (int) (frame.height() * frameScale);
- }
-
- // Tell surface flinger what part of the image to crop. Take the top
- // right part of the application, and crop the larger dimension to fit.
- Rect crop = new Rect(frame);
- if (width / (float) frame.width() < height / (float) frame.height()) {
- int cropWidth = (int)((float)width / (float)height * frame.height());
- crop.right = crop.left + cropWidth;
- } else {
- int cropHeight = (int)((float)height / (float)width * frame.width());
- crop.bottom = crop.top + cropHeight;
- }
+ final Rect frame = new Rect(0, 0, dw, dh);
// The screenshot API does not apply the current screen rotation.
int rot = mDisplay.getRotation();
@@ -3198,43 +3001,52 @@
rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
}
- // Surfaceflinger is not aware of orientation, so convert our logical
- // crop to surfaceflinger's portrait orientation.
- convertCropForSurfaceFlinger(crop, rot, dw, dh);
-
- if (DEBUG_SCREENSHOT) {
- forAllWindows(w -> {
- final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
- Slog.i(TAG_WM, w + ": " + w.mLayer
- + " animLayer=" + w.mWinAnimator.mAnimLayer
- + " surfaceLayer=" + ((controller == null)
- ? "null" : controller.getLayer()));
- }, false /* traverseTopToBottom */);
- }
+ // SurfaceFlinger is not aware of orientation, so convert our logical
+ // crop to SurfaceFlinger's portrait orientation.
+ convertCropForSurfaceFlinger(frame, rot, dw, dh);
final ScreenRotationAnimation screenRotationAnimation =
mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
final boolean inRotation = screenRotationAnimation != null &&
screenRotationAnimation.isAnimating();
- if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
- "Taking screenshot while rotating");
-
- // We force pending transactions to flush before taking
- // the screenshot by pushing an empty synchronous transaction.
- SurfaceControl.openTransaction();
- SurfaceControl.closeTransactionSync();
+ if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
// TODO(b/68392460): We should screenshot Task controls directly
// but it's difficult at the moment as the Task doesn't have the
// correct size set.
- bitmap = screenshoter.screenshot(crop, width, height, 0, 1,
- inRotation, rot);
+ final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot);
if (bitmap == null) {
Slog.w(TAG_WM, "Failed to take screenshot");
return null;
}
+
+ // Create a copy of the screenshot that is immutable and backed in ashmem.
+ // This greatly reduces the overhead of passing the bitmap between processes.
+ final Bitmap ret = bitmap.createAshmemBitmap(config);
+ bitmap.recycle();
+ return ret;
}
- return bitmap;
+ }
+
+ private boolean shouldScreenshotWallpaper() {
+ MutableBoolean screenshotReady = new MutableBoolean(false);
+
+ forAllWindows(w -> {
+ if (!w.mIsWallpaper) {
+ return false;
+ }
+
+ // Found the wallpaper window
+ final WindowStateAnimator winAnim = w.mWinAnimator;
+
+ if (winAnim.getShown() && winAnim.mLastAlpha > 0f) {
+ screenshotReady.value = true;
+ }
+
+ return true;
+ }, true /* traverseTopToBottom */);
+
+ return screenshotReady.value;
}
// TODO: Can this use createRotationMatrix()?
@@ -3848,15 +3660,6 @@
}
}
- /**
- * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
- */
- @FunctionalInterface
- private interface Screenshoter<E> {
- E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
- boolean useIdentityTransform, int rotation);
- }
-
SurfaceControl.Builder makeSurface(SurfaceSession s) {
return mService.makeSurfaceBuilder(s)
.setParent(mWindowingLayer);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index e644417..84e475a 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -18,12 +18,12 @@
import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.annotation.Nullable;
import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
import android.app.ActivityManager.TaskSnapshot;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
@@ -35,6 +35,7 @@
import android.util.Slog;
import android.view.DisplayListCanvas;
import android.view.RenderNode;
+import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import android.view.WindowManager.LayoutParams;
@@ -210,11 +211,28 @@
if (mainWindow == null) {
return null;
}
+ if (!mService.mPolicy.isScreenOn()) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+ }
+ return null;
+ }
+ if (task.getSurfaceControl() == null) {
+ return null;
+ }
+
final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
- final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
- -1, -1, false, scaleFraction, false, true);
+ final Rect taskFrame = new Rect();
+ task.getBounds(taskFrame);
+
+ final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+ task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+
if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+ if (DEBUG_SCREENSHOT) {
+ Slog.w(TAG_WM, "Failed to take screenshot");
+ }
return null;
}
return new TaskSnapshot(buffer, top.getConfiguration().orientation,
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 1cd4080..82f842c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3691,9 +3691,8 @@
}
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
- return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
- -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
- Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+ return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ true /* wallpaperOnly */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -3712,10 +3711,8 @@
}
FgThread.getHandler().post(() -> {
- Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
- -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
- 1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
- false /* includeDecor */);
+ Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+ false /* wallpaperOnly */);
try {
receiver.onHandleAssistScreenshot(bm);
} catch (RemoteException e) {
@@ -3749,29 +3746,21 @@
* In portrait mode, it grabs the full screenshot.
*
* @param displayId the Display to take a screenshot of.
- * @param width the width of the target bitmap
- * @param height the height of the target bitmap
- * @param includeFullDisplay true if the screen should not be cropped before capture
- * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
* @param config of the output bitmap
* @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
- * @param includeDecor whether to include window decors, like the status or navigation bar
- * background of the window
*/
- private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
- int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
- boolean wallpaperOnly, boolean includeDecor) {
+ private Bitmap screenshotApplications(int displayId, Bitmap.Config config,
+ boolean wallpaperOnly) {
final DisplayContent displayContent;
synchronized(mWindowMap) {
displayContent = mRoot.getDisplayContentOrCreate(displayId);
if (displayContent == null) {
- if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
- + ": returning null. No Display for displayId=" + displayId);
+ if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+ + "displayId=" + displayId);
return null;
}
}
- return displayContent.screenshotApplications(appToken, width, height,
- includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+ return displayContent.screenshotDisplay(config, wallpaperOnly);
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a2323a4..c2ac905 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4372,7 +4372,7 @@
@Override
SurfaceControl.Builder makeSurface() {
- return mToken.makeChildSurface(this);
+ return getParent().makeChildSurface(this);
}
private void applyDims(Dimmer dimmer) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d2b3748..4b5661f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -871,11 +871,9 @@
void computeShownFrameLocked() {
final boolean selfTransformation = mHasLocalTransformation;
- Transformation attachedTransformation =
- (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
- ? mParentWinAnimator.mTransformation : null;
Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
? mAppAnimator.transformation : null;
+ Transformation wallpaperTargetTransformation = null;
// Wallpapers are animated based on the "real" window they
// are currently targeting.
@@ -885,9 +883,9 @@
if (wallpaperAnimator.mHasLocalTransformation &&
wallpaperAnimator.mAnimation != null &&
!wallpaperAnimator.mAnimation.getDetachWallpaper()) {
- attachedTransformation = wallpaperAnimator.mTransformation;
- if (DEBUG_WALLPAPER && attachedTransformation != null) {
- Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+ wallpaperTargetTransformation = wallpaperAnimator.mTransformation;
+ if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) {
+ Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation);
}
}
final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
@@ -909,7 +907,7 @@
screenRotationAnimation != null && screenRotationAnimation.isAnimating();
mHasClipRect = false;
- if (selfTransformation || attachedTransformation != null
+ if (selfTransformation || wallpaperTargetTransformation != null
|| appTransformation != null || screenAnimation) {
// cache often used attributes locally
final Rect frame = mWin.mFrame;
@@ -939,18 +937,27 @@
if (selfTransformation) {
tmpMatrix.postConcat(mTransformation.getMatrix());
}
- if (attachedTransformation != null) {
- tmpMatrix.postConcat(attachedTransformation.getMatrix());
+
+ if (wallpaperTargetTransformation != null) {
+ tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix());
}
if (appTransformation != null) {
tmpMatrix.postConcat(appTransformation.getMatrix());
}
+ int left = frame.left;
+ int top = frame.top;
+ if (mWin.isChildWindow()) {
+ WindowState parent = mWin.getParentWindow();
+ left -= parent.mFrame.left;
+ top -= parent.mFrame.top;
+ }
+
// The translation that applies the position of the window needs to be applied at the
// end in case that other translations include scaling. Otherwise the scaling will
// affect this translation. But it needs to be set before the screen rotation animation
// so the pivot point is at the center of the screen for all windows.
- tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+ tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset);
if (screenAnimation) {
tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
}
@@ -985,8 +992,8 @@
if (selfTransformation) {
mShownAlpha *= mTransformation.getAlpha();
}
- if (attachedTransformation != null) {
- mShownAlpha *= attachedTransformation.getAlpha();
+ if (wallpaperTargetTransformation != null) {
+ mShownAlpha *= wallpaperTargetTransformation.getAlpha();
}
if (appTransformation != null) {
mShownAlpha *= appTransformation.getAlpha();
@@ -1017,8 +1024,8 @@
&& (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+ " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
- + " attached=" + (attachedTransformation == null ?
- "null" : attachedTransformation.getAlpha())
+ + " attached=" + (wallpaperTargetTransformation == null ?
+ "null" : wallpaperTargetTransformation.getAlpha())
+ " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+ " screen=" + (screenAnimation ?
screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 4a2da37..8b9cf4b 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -94,6 +94,7 @@
android.hardware.broadcastradio@1.2 \
android.hardware.contexthub@1.0 \
android.hardware.gnss@1.0 \
+ android.hardware.gnss@1.1 \
android.hardware.ir@1.0 \
android.hardware.light@2.0 \
android.hardware.power@1.0 \
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 11f508b..24f2014 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -65,28 +65,15 @@
int subClassID = deviceDesc->bDeviceSubClass;
// get the raw descriptors
- int fd = usb_device_get_fd(device);
- if (fd < 0) {
- ALOGE("usb_device_get_fd failed\n");
- usb_device_close(device);
- // TODO return an error code here?
- return 0;
- }
-
- // from android_hardware_UsbDeviceConnection_get_desc()
- jbyte rawdescriptors[MAX_DESCRIPTORS_LENGTH];
- lseek(fd, 0, SEEK_SET);
- int numBytes = read(fd, rawdescriptors, sizeof(rawdescriptors));
-
- usb_device_close(device);
-
+ int numBytes = usb_device_get_descriptors_length(device);
if (numBytes > 0) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
jobject thiz = (jobject)clientData;
jstring deviceAddress = env->NewStringUTF(devAddress);
jbyteArray descriptorsArray = env->NewByteArray(numBytes);
- env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawdescriptors);
+ const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
+ env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
env->CallBooleanMethod(thiz, method_usbDeviceAdded,
deviceAddress, classID, subClassID, descriptorsArray);
@@ -100,6 +87,8 @@
ALOGE("error reading descriptors\n");
}
+ usb_device_close(device);
+
return 0;
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index daf3f2f..4cbe64d 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -18,7 +18,7 @@
#define LOG_NDEBUG 0
-#include <android/hardware/gnss/1.0/IGnss.h>
+#include <android/hardware/gnss/1.1/IGnss.h>
#include <nativehelper/JNIHelp.h>
#include "jni.h"
@@ -78,6 +78,9 @@
using android::hardware::hidl_death_recipient;
using android::hidl::base::V1_0::IBase;
+using android::hardware::gnss::V1_0::GnssLocation;
+using android::hardware::gnss::V1_0::GnssLocationFlags;
+
using android::hardware::gnss::V1_0::IAGnss;
using android::hardware::gnss::V1_0::IAGnssCallback;
using android::hardware::gnss::V1_0::IAGnssCallback;
@@ -86,7 +89,6 @@
using android::hardware::gnss::V1_0::IGnss;
using android::hardware::gnss::V1_0::IGnssBatching;
using android::hardware::gnss::V1_0::IGnssBatchingCallback;
-using android::hardware::gnss::V1_0::IGnssCallback;
using android::hardware::gnss::V1_0::IGnssConfiguration;
using android::hardware::gnss::V1_0::IGnssDebug;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -100,11 +102,14 @@
using android::hardware::gnss::V1_0::IGnssXtra;
using android::hardware::gnss::V1_0::IGnssXtraCallback;
+using IGnssV1_1 = android::hardware::gnss::V1_1::IGnss;
+using android::hardware::gnss::V1_1::IGnssCallback;
+
struct GnssDeathRecipient : virtual public hidl_death_recipient
{
// hidl_death_recipient interface
virtual void serviceDied(uint64_t cookie, const wp<IBase>& who) override {
- // TODO(gomo): implement a better death recovery mechanism without
+ // TODO(b/37460011): implement a better death recovery mechanism without
// crashing system server process as described in go//treble-gnss-death
LOG_ALWAYS_FATAL("Abort due to IGNSS hidl service failure,"
" restarting system server");
@@ -113,6 +118,7 @@
sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
sp<IGnss> gnssHal = nullptr;
+sp<IGnssV1_1> gnssHalV1_1 = nullptr;
sp<IGnssXtra> gnssXtraIface = nullptr;
sp<IAGnssRil> agnssRilIface = nullptr;
sp<IGnssGeofencing> gnssGeofencingIface = nullptr;
@@ -303,33 +309,33 @@
return env;
}
-static jobject translateLocation(JNIEnv* env, const hardware::gnss::V1_0::GnssLocation& location) {
+static jobject translateLocation(JNIEnv* env, const GnssLocation& location) {
JavaObject object(env, "android/location/Location", "gps");
uint16_t flags = static_cast<uint32_t>(location.gnssLocationFlags);
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) {
+ if (flags & GnssLocationFlags::HAS_LAT_LONG) {
SET(Latitude, location.latitudeDegrees);
SET(Longitude, location.longitudeDegrees);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_ALTITUDE) {
+ if (flags & GnssLocationFlags::HAS_ALTITUDE) {
SET(Altitude, location.altitudeMeters);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED) {
+ if (flags & GnssLocationFlags::HAS_SPEED) {
SET(Speed, location.speedMetersPerSec);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING) {
+ if (flags & GnssLocationFlags::HAS_BEARING) {
SET(Bearing, location.bearingDegrees);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
+ if (flags & GnssLocationFlags::HAS_HORIZONTAL_ACCURACY) {
SET(Accuracy, location.horizontalAccuracyMeters);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
+ if (flags & GnssLocationFlags::HAS_VERTICAL_ACCURACY) {
SET(VerticalAccuracyMeters, location.verticalAccuracyMeters);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_SPEED_ACCURACY) {
+ if (flags & GnssLocationFlags::HAS_SPEED_ACCURACY) {
SET(SpeedAccuracyMetersPerSecond, location.speedAccuracyMetersPerSecond);
}
- if (flags & hardware::gnss::V1_0::GnssLocationFlags::HAS_BEARING_ACCURACY) {
+ if (flags & GnssLocationFlags::HAS_BEARING_ACCURACY) {
SET(BearingAccuracyDegrees, location.bearingAccuracyDegrees);
}
SET(Time, location.timestamp);
@@ -341,8 +347,7 @@
* GnssCallback class implements the callback methods for IGnss interface.
*/
struct GnssCallback : public IGnssCallback {
- Return<void> gnssLocationCb(
- const android::hardware::gnss::V1_0::GnssLocation& location) override;
+ Return<void> gnssLocationCb(const GnssLocation& location) override;
Return<void> gnssStatusCb(const IGnssCallback::GnssStatusValue status) override;
Return<void> gnssSvStatusCb(const IGnssCallback::GnssSvStatus& svStatus) override;
Return<void> gnssNmeaCb(int64_t timestamp, const android::hardware::hidl_string& nmea) override;
@@ -352,6 +357,9 @@
Return<void> gnssRequestTimeCb() override;
Return<void> gnssSetSystemInfoCb(const IGnssCallback::GnssSystemInfo& info) override;
+ // New in 1.1
+ Return<void> gnssNameCb(const android::hardware::hidl_string& name) override;
+
static GnssSvInfo sGnssSvList[static_cast<uint32_t>(
android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
static size_t sGnssSvListSize;
@@ -360,19 +368,30 @@
static size_t sNmeaStringLength;
};
+Return<void> GnssCallback::gnssNameCb(const android::hardware::hidl_string& name) {
+ ALOGD("%s: name=%s\n", __func__, name.c_str());
+
+ // TODO(b/38003769): build Java code to connect to below code
+ /*
+ JNIEnv* env = getJniEnv();
+ env->CallVoidMethod(mCallbacksObj, method_setGnssHardwareName, name);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
+ */
+ return Void();
+}
+
IGnssCallback::GnssSvInfo GnssCallback::sGnssSvList[static_cast<uint32_t>(
android::hardware::gnss::V1_0::GnssMax::SVS_COUNT)];
const char* GnssCallback::sNmeaString = nullptr;
size_t GnssCallback::sNmeaStringLength = 0;
size_t GnssCallback::sGnssSvListSize = 0;
-Return<void> GnssCallback::gnssLocationCb(
- const ::android::hardware::gnss::V1_0::GnssLocation& location) {
+Return<void> GnssCallback::gnssLocationCb(const GnssLocation& location) {
JNIEnv* env = getJniEnv();
jobject jLocation = translateLocation(env, location);
bool hasLatLong = (static_cast<uint32_t>(location.gnssLocationFlags) &
- hardware::gnss::V1_0::GnssLocationFlags::HAS_LAT_LONG) != 0;
+ GnssLocationFlags::HAS_LAT_LONG) != 0;
env->CallVoidMethod(mCallbacksObj,
method_reportLocation,
@@ -484,12 +503,12 @@
// Methods from ::android::hardware::gps::V1_0::IGnssGeofenceCallback follow.
Return<void> gnssGeofenceTransitionCb(
int32_t geofenceId,
- const android::hardware::gnss::V1_0::GnssLocation& location,
+ const GnssLocation& location,
GeofenceTransition transition,
hardware::gnss::V1_0::GnssUtcTime timestamp) override;
Return<void> gnssGeofenceStatusCb(
GeofenceAvailability status,
- const android::hardware::gnss::V1_0::GnssLocation& location) override;
+ const GnssLocation& location) override;
Return<void> gnssGeofenceAddCb(int32_t geofenceId,
GeofenceStatus status) override;
Return<void> gnssGeofenceRemoveCb(int32_t geofenceId,
@@ -502,7 +521,7 @@
Return<void> GnssGeofenceCallback::gnssGeofenceTransitionCb(
int32_t geofenceId,
- const android::hardware::gnss::V1_0::GnssLocation& location,
+ const GnssLocation& location,
GeofenceTransition transition,
hardware::gnss::V1_0::GnssUtcTime timestamp) {
JNIEnv* env = getJniEnv();
@@ -522,7 +541,7 @@
Return<void> GnssGeofenceCallback::gnssGeofenceStatusCb(
GeofenceAvailability status,
- const android::hardware::gnss::V1_0::GnssLocation& location) {
+ const GnssLocation& location) {
JNIEnv* env = getJniEnv();
jobject jLocation = translateLocation(env, location);
@@ -976,12 +995,12 @@
* follow.
*/
Return<void> gnssLocationBatchCb(
- const ::android::hardware::hidl_vec<hardware::gnss::V1_0::GnssLocation> & locations)
+ const ::android::hardware::hidl_vec<GnssLocation> & locations)
override;
};
Return<void> GnssBatchingCallback::gnssLocationBatchCb(
- const ::android::hardware::hidl_vec<hardware::gnss::V1_0::GnssLocation> & locations) {
+ const ::android::hardware::hidl_vec<GnssLocation> & locations) {
JNIEnv* env = getJniEnv();
jobjectArray jLocations = env->NewObjectArray(locations.size(),
@@ -1049,8 +1068,13 @@
LOG_ALWAYS_FATAL("Unable to get Java VM. Error: %d", jvmStatus);
}
- // TODO(b/31632518)
- gnssHal = IGnss::getService();
+ gnssHal = gnssHalV1_1 = IGnssV1_1::getService();
+
+ if (gnssHal == nullptr) {
+ ALOGD("gnssHal 1.1 was null, trying 1.0");
+ gnssHal = IGnss::getService();
+ }
+
if (gnssHal != nullptr) {
gnssHalDeathRecipient = new GnssDeathRecipient();
hardware::Return<bool> linked = gnssHal->linkToDeath(
@@ -1160,7 +1184,6 @@
if (!mCallbacksObj)
mCallbacksObj = env->NewGlobalRef(obj);
- sp<IGnssCallback> gnssCbIface = new GnssCallback();
/*
* Fail if the main interface fails to initialize
*/
@@ -1169,7 +1192,14 @@
return JNI_FALSE;
}
- auto result = gnssHal->setCallback(gnssCbIface);
+ sp<IGnssCallback> gnssCbIface = new GnssCallback();
+
+ Return<bool> result = false;
+ if (gnssHalV1_1 != nullptr) {
+ result = gnssHalV1_1->setCallback_1_1(gnssCbIface);
+ } else {
+ result = gnssHal->setCallback(gnssCbIface);
+ }
if (!result.isOk() || !result) {
ALOGE("SetCallback for Gnss Interface fails\n");
return JNI_FALSE;
@@ -1182,7 +1212,7 @@
result = gnssXtraIface->setCallback(gnssXtraCbIface);
if (!result.isOk() || !result) {
gnssXtraIface = nullptr;
- ALOGE("SetCallback for Gnss Xtra Interface fails\n");
+ ALOGI("SetCallback for Gnss Xtra Interface fails\n");
}
}
@@ -1190,21 +1220,21 @@
if (agnssIface != nullptr) {
agnssIface->setCallback(aGnssCbIface);
} else {
- ALOGE("Unable to Initialize AGnss interface\n");
+ ALOGI("Unable to Initialize AGnss interface\n");
}
sp<IGnssGeofenceCallback> gnssGeofencingCbIface = new GnssGeofenceCallback();
if (gnssGeofencingIface != nullptr) {
gnssGeofencingIface->setCallback(gnssGeofencingCbIface);
} else {
- ALOGE("Unable to initialize GNSS Geofencing interface\n");
+ ALOGI("Unable to initialize GNSS Geofencing interface\n");
}
sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback();
if (gnssNiIface != nullptr) {
gnssNiIface->setCallback(gnssNiCbIface);
} else {
- ALOGE("Unable to initialize GNSS NI interface\n");
+ ALOGI("Unable to initialize GNSS NI interface\n");
}
sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback();
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9a5ebed..0499bf0 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -58,6 +58,7 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
+ <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
<!-- Uses API introduced in O (26) -->
<uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
new file mode 100644
index 0000000..918807d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -0,0 +1,193 @@
+/*
+ * 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;
+
+import static android.app.AlarmManager.RTC;
+import static android.app.AlarmManager.RTC_WAKEUP;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.util.ObjectUtils;
+import com.android.server.AlarmManagerService.Alarm;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AlarmManagerServiceTest {
+ private SparseArray<ArrayList<Alarm>> addPendingAlarm(
+ SparseArray<ArrayList<Alarm>> all, int uid, String name, boolean removeIt) {
+ ArrayList<Alarm> uidAlarms = all.get(uid);
+ if (uidAlarms == null) {
+ all.put(uid, uidAlarms = new ArrayList<>());
+ }
+ // Details don't matter.
+ uidAlarms.add(new Alarm(
+ removeIt ? RTC : RTC_WAKEUP,
+ 0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name));
+ return all;
+ }
+
+ private static String toString(SparseArray<ArrayList<Alarm>> pendingAlarms) {
+ final StringBuilder sb = new StringBuilder();
+
+ String sep = "";
+ for (int i = 0; i < pendingAlarms.size(); i++) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append("[");
+ sb.append(pendingAlarms.keyAt(i));
+ sb.append(": ");
+ sb.append(toString(pendingAlarms.valueAt(i)));
+ sb.append("]");
+ }
+ return sb.toString();
+ }
+
+ private static String toString(ArrayList<Alarm> alarms) {
+ final StringBuilder sb = new StringBuilder();
+
+ alarms.sort((a, b) -> ObjectUtils.compare(a.packageName, b.packageName));
+
+ String sep = "";
+ for (Alarm a : alarms) {
+ sb.append(sep);
+ sep = ", ";
+ sb.append(a.packageName);
+ }
+ return sb.toString();
+ }
+
+ private void runCheckAllPendingAlarms(
+ SparseArray<ArrayList<Alarm>> pending, ArrayList<Alarm> alarmsToDeliver) {
+ // RTC_WAKEUP alarms are restricted.
+ AlarmManagerService.findAllUnrestrictedPendingBackgroundAlarmsLockedInner(pending,
+ alarmsToDeliver, alarm -> alarm.type == RTC_WAKEUP);
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_empty() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+ assertEquals("", toString(pending));
+ assertEquals("", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_remove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a1", false);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+ assertEquals("[100001: a1]", toString(pending));
+ assertEquals("", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_nonremove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a1", true);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("", toString(pending));
+ assertEquals("a1", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a11", false);
+ addPendingAlarm(pending, 100001, "a12", true);
+ addPendingAlarm(pending, 100001, "a13", false);
+ addPendingAlarm(pending, 100001, "a14", true);
+
+ addPendingAlarm(pending, 100002, "a21", false);
+
+ addPendingAlarm(pending, 100003, "a31", true);
+
+ addPendingAlarm(pending, 100004, "a41", false);
+ addPendingAlarm(pending, 100004, "a42", false);
+
+ addPendingAlarm(pending, 100005, "a51", true);
+ addPendingAlarm(pending, 100005, "a52", true);
+
+ addPendingAlarm(pending, 100006, "a61", true);
+ addPendingAlarm(pending, 100006, "a62", false);
+ addPendingAlarm(pending, 100006, "a63", true);
+ addPendingAlarm(pending, 100006, "a64", false);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("[100001: a11, a13], [100002: a21], [100004: a41, a42], [100006: a62, a64]",
+ toString(pending));
+ assertEquals("a12, a14, a31, a51, a52, a61, a63", toString(alarmsToDeliver));
+ }
+
+ @Test
+ public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex_allRemove() {
+ SparseArray<ArrayList<Alarm>> pending = new SparseArray<>();
+
+ addPendingAlarm(pending, 100001, "a11", true);
+ addPendingAlarm(pending, 100001, "a12", true);
+ addPendingAlarm(pending, 100001, "a13", true);
+ addPendingAlarm(pending, 100001, "a14", true);
+
+ addPendingAlarm(pending, 100002, "a21", true);
+
+ addPendingAlarm(pending, 100003, "a31", true);
+
+ addPendingAlarm(pending, 100004, "a41", true);
+ addPendingAlarm(pending, 100004, "a42", true);
+
+ addPendingAlarm(pending, 100005, "a51", true);
+ addPendingAlarm(pending, 100005, "a52", true);
+
+ addPendingAlarm(pending, 100006, "a61", true);
+ addPendingAlarm(pending, 100006, "a62", true);
+ addPendingAlarm(pending, 100006, "a63", true);
+ addPendingAlarm(pending, 100006, "a64", true);
+
+ final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
+ runCheckAllPendingAlarms(pending, alarmsToDeliver);
+
+
+ assertEquals("", toString(pending));
+ assertEquals("a11, a12, a13, a14, a21, a31, a41, a42, a51, a52, a61, a62, a63, a64",
+ toString(alarmsToDeliver));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
new file mode 100644
index 0000000..66d0da1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -0,0 +1,900 @@
+/*
+ * 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;
+
+import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.PowerSaveState;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.server.ForceAppStandbyTracker.Listener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ForceAppStandbyTrackerTest {
+
+ private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker {
+ ForceAppStandbyTrackerTestable() {
+ super(mMockContext, Looper.getMainLooper());
+ }
+
+ @Override
+ AppOpsManager injectAppOpsManager() {
+ return mMockAppOpsManager;
+ }
+
+ @Override
+ IAppOpsService injectIAppOpsService() {
+ return mMockIAppOpsService;
+ }
+
+ @Override
+ IActivityManager injectIActivityManager() {
+ return mMockIActivityManager;
+ }
+
+ @Override
+ PowerManagerInternal injectPowerManagerInternal() {
+ return mMockPowerManagerInternal;
+ }
+ }
+
+ private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1;
+ private static final int UID_2 = Process.FIRST_APPLICATION_UID + 2;
+ private static final int UID_3 = Process.FIRST_APPLICATION_UID + 3;
+ private static final int UID_10_1 = UserHandle.getUid(10, UID_1);
+ private static final int UID_10_2 = UserHandle.getUid(10, UID_2);
+ private static final int UID_10_3 = UserHandle.getUid(10, UID_3);
+ private static final String PACKAGE_1 = "package1";
+ private static final String PACKAGE_2 = "package2";
+ private static final String PACKAGE_3 = "package3";
+ private static final String PACKAGE_SYSTEM = "android";
+
+ private Handler mMainHandler;
+
+ @Mock
+ private Context mMockContext;
+
+ @Mock
+ private IActivityManager mMockIActivityManager;
+
+ @Mock
+ private AppOpsManager mMockAppOpsManager;
+
+ @Mock
+ private IAppOpsService mMockIAppOpsService;
+
+ @Mock
+ private PowerManagerInternal mMockPowerManagerInternal;
+
+ private IUidObserver mIUidObserver;
+ private IAppOpsCallback.Stub mAppOpsCallback;
+ private Consumer<PowerSaveState> mPowerSaveObserver;
+ private BroadcastReceiver mReceiver;
+
+ private boolean mPowerSaveMode;
+
+ private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet();
+
+ @Before
+ public void setUp() {
+ mMainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private void waitUntilMainHandlerDrain() throws Exception {
+ final CountDownLatch l = new CountDownLatch(1);
+ mMainHandler.post(() -> {
+ l.countDown();
+ });
+ assertTrue(l.await(5, TimeUnit.SECONDS));
+ }
+
+ private PowerSaveState getPowerSaveState() {
+ return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build();
+ }
+
+ private ForceAppStandbyTrackerTestable newInstance() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
+ .thenAnswer(inv -> {
+ return mRestrictedPackages.indexOf(
+ Pair.create(inv.getArgument(1), inv.getArgument(2))) >= 0 ?
+ AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
+ });
+
+ final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable();
+
+ return instance;
+ }
+
+ private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+
+ // Set up functions that start() calls.
+ when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
+ .thenAnswer(inv -> getPowerSaveState());
+ when(mMockAppOpsManager.getPackagesForOps(
+ any(int[].class)
+ )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>());
+
+ // Call start.
+ instance.start();
+
+ // Capture the listeners.
+ ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor =
+ ArgumentCaptor.forClass(IUidObserver.class);
+ ArgumentCaptor<IAppOpsCallback.Stub> appOpsCallbackCaptor =
+ ArgumentCaptor.forClass(IAppOpsCallback.Stub.class);
+ ArgumentCaptor<Consumer<PowerSaveState>> powerSaveObserverCaptor =
+ ArgumentCaptor.forClass(Consumer.class);
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ verify(mMockIActivityManager).registerUidObserver(
+ uidObserverArgumentCaptor.capture(),
+ eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+ | ActivityManager.UID_OBSERVER_ACTIVE),
+ eq(ActivityManager.PROCESS_STATE_UNKNOWN),
+ isNull());
+ verify(mMockIAppOpsService).startWatchingMode(
+ eq(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND),
+ isNull(),
+ appOpsCallbackCaptor.capture());
+ verify(mMockPowerManagerInternal).registerLowPowerModeObserver(
+ eq(ServiceType.FORCE_ALL_APPS_STANDBY),
+ powerSaveObserverCaptor.capture());
+
+ verify(mMockContext).registerReceiver(
+ receiverCaptor.capture(), any(IntentFilter.class));
+
+ mIUidObserver = uidObserverArgumentCaptor.getValue();
+ mAppOpsCallback = appOpsCallbackCaptor.getValue();
+ mPowerSaveObserver = powerSaveObserverCaptor.getValue();
+ mReceiver = receiverCaptor.getValue();
+
+ assertNotNull(mIUidObserver);
+ assertNotNull(mAppOpsCallback);
+ assertNotNull(mPowerSaveObserver);
+ assertNotNull(mReceiver);
+ }
+
+ private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException {
+ final Pair p = Pair.create(uid, packageName);
+ if (restrict) {
+ mRestrictedPackages.add(p);
+ } else {
+ mRestrictedPackages.remove(p);
+ }
+ if (mAppOpsCallback != null) {
+ mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName);
+ }
+ }
+
+ private static final int NONE = 0;
+ private static final int ALARMS_ONLY = 1 << 0;
+ private static final int JOBS_ONLY = 1 << 1;
+ private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
+
+ private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+ int restrictionTypes) {
+ assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
+ instance.areJobsRestricted(uid, packageName));
+ assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
+ instance.areAlarmsRestricted(uid, packageName));
+ }
+
+ @Test
+ public void testAll() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ assertFalse(instance.isForceAllAppsStandbyEnabled());
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertTrue(instance.isForceAllAppsStandbyEnabled());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Toggle the foreground state.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+ assertTrue(instance.isInForeground(Process.SYSTEM_UID));
+
+ mIUidObserver.onUidActive(UID_1);
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertTrue(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ mIUidObserver.onUidActive(UID_1);
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+ assertFalse(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_2));
+
+ // Toggle the app ops.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, NONE);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ setAppOps(UID_1, PACKAGE_1, true);
+ setAppOps(UID_10_2, PACKAGE_2, true);
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Toggle power saver, should still be the same.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, NONE);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Clear the app ops and update the whitelist.
+ setAppOps(UID_1, PACKAGE_1, false);
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_1, PACKAGE_1, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1}, new int[] {UID_2});
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ // Again, make sure toggling the global state doesn't change it.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ areRestricted(instance, UID_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_10_1, PACKAGE_1, NONE);
+ areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY);
+ areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS);
+ areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
+
+ assertTrue(instance.isUidPowerSaveWhitelisted(UID_1));
+ assertTrue(instance.isUidPowerSaveWhitelisted(UID_10_1));
+ assertFalse(instance.isUidPowerSaveWhitelisted(UID_2));
+ assertFalse(instance.isUidPowerSaveWhitelisted(UID_10_2));
+
+ assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_1));
+ assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_10_1));
+ assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_2));
+ assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_10_2));
+ }
+
+ public void loadPersistedAppOps() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+
+ final List<PackageOps> ops = new ArrayList<>();
+
+ //--------------------------------------------------
+ List<OpEntry> entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
+
+ //--------------------------------------------------
+ entries = new ArrayList<>();
+ entries.add(new AppOpsManager.OpEntry(
+ ForceAppStandbyTracker.TARGET_OP,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+ entries.add(new AppOpsManager.OpEntry(
+ AppOpsManager.OP_ACCESS_NOTIFICATIONS,
+ AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
+
+ ops.add(new PackageOps(PACKAGE_3, UID_10_3, entries));
+
+ callStart(instance);
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_3, PACKAGE_3));
+
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_3, PACKAGE_3));
+ }
+
+ private void assertNoCallbacks(Listener l) throws Exception {
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+ }
+
+ @Test
+ public void testPowerSaveListener() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+ instance.addListener(l);
+
+ // Power save on.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Power save off.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Power save on.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ assertNoCallbacks(l);
+ }
+
+ @Test
+ public void testAllListeners() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+ instance.addListener(l);
+
+ // -------------------------------------------------------------------------
+ // Test with apppops.
+
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+ reset(l);
+
+ setAppOps(UID_10_2, PACKAGE_2, false);
+
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+
+ // Unrestrict while battery saver is on. Shouldn't fire.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ // Note toggling appops while BS is on will suppress unblockAlarmsForUidPackage().
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2));
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Battery saver off.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // -------------------------------------------------------------------------
+ // Tests with system/user/temp whitelist.
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Update temp whitelist.
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Do the same thing with battery saver on. (Currently same callbacks are called.)
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(1)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Update temp whitelist.
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3});
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(1)).updateAllJobs();
+ verify(l, times(0)).updateJobsForUid(anyInt());
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+
+ // -------------------------------------------------------------------------
+ // Tests with proc state changes.
+
+ // With battery save.
+ mPowerSaveMode = true;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidGone(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidIdle(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ // Without battery save.
+ mPowerSaveMode = false;
+ mPowerSaveObserver.accept(getPowerSaveState());
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidGone(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidActive(UID_10_1);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+
+ mIUidObserver.onUidIdle(UID_10_1, true);
+
+ waitUntilMainHandlerDrain();
+ verify(l, times(0)).updateAllJobs();
+ verify(l, times(1)).updateJobsForUid(eq(UID_10_1));
+ verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString());
+
+ verify(l, times(0)).unblockAllUnrestrictedAlarms();
+ verify(l, times(0)).unblockAlarmsForUid(anyInt());
+ verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString());
+ reset(l);
+ }
+
+ @Test
+ public void testUserRemoved() throws Exception {
+ final ForceAppStandbyTrackerTestable instance = newInstance();
+ callStart(instance);
+
+ mIUidObserver.onUidActive(UID_1);
+ mIUidObserver.onUidActive(UID_10_1);
+
+ setAppOps(UID_2, PACKAGE_2, true);
+ setAppOps(UID_10_2, PACKAGE_2, true);
+
+ assertTrue(instance.isInForeground(UID_1));
+ assertTrue(instance.isInForeground(UID_10_1));
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+
+ final Intent intent = new Intent(Intent.ACTION_USER_REMOVED);
+ intent.putExtra(Intent.EXTRA_USER_HANDLE, 10);
+ mReceiver.onReceive(mMockContext, intent);
+
+ waitUntilMainHandlerDrain();
+
+ assertTrue(instance.isInForeground(UID_1));
+ assertFalse(instance.isInForeground(UID_10_1));
+
+ assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2));
+ assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2));
+ }
+
+ static int[] array(int... appIds) {
+ Arrays.sort(appIds);
+ return appIds;
+ }
+
+ private final Random mRandom = new Random();
+
+ int[] makeRandomArray() {
+ final ArrayList<Integer> list = new ArrayList<>();
+ for (int i = 0; i < 5; i++) {
+ if (mRandom.nextDouble() < 0.5) {
+ list.add(i);
+ }
+ }
+ return Arrays.stream(list.toArray(new Integer[list.size()]))
+ .mapToInt(Integer::intValue).toArray();
+ }
+
+ static boolean isAnyAppIdUnwhitelistedSlow(int[] prevArray, int[] newArray) {
+ Arrays.sort(newArray); // Just in case...
+ for (int p : prevArray) {
+ if (Arrays.binarySearch(newArray, p) < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) {
+ assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+ expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
+
+ // Also test isAnyAppIdUnwhitelistedSlow.
+ assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
+ expected, isAnyAppIdUnwhitelistedSlow(prevArray, newArray));
+ }
+
+ @Test
+ public void isAnyAppIdUnwhitelisted() {
+ checkAnyAppIdUnwhitelisted(array(), array(), false);
+
+ checkAnyAppIdUnwhitelisted(array(1), array(), true);
+ checkAnyAppIdUnwhitelisted(array(1), array(1), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+ checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false);
+
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2, 10), false);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(2, 10), true);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 1, 2, 4, 3, 10), false);
+ checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 0, 1, 2, 10), false);
+
+ // Random test
+ int trueCount = 0;
+ final int count = 10000;
+ for (int i = 0; i < count; i++) {
+ final int[] array1 = makeRandomArray();
+ final int[] array2 = makeRandomArray();
+
+ final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2);
+ final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2);
+
+ assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2),
+ expected, actual);
+ if (expected) {
+ trueCount++;
+ }
+ }
+
+ // Make sure makeRandomArray() didn't generate all same arrays by accident.
+ assertTrue(trueCount > 0);
+ assertTrue(trueCount < count);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 4078829..f093d57 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
@@ -37,7 +37,6 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.Time.TIMEZONE_UTC;
-import static com.android.server.net.NetworkPolicyManagerService.MAX_PROC_STATE_SEQ_HISTORY;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_LIMIT_SNOOZED;
import static com.android.server.net.NetworkPolicyManagerService.TYPE_WARNING;
@@ -61,7 +60,6 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -116,12 +114,10 @@
import android.util.TrustedTime;
import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.BroadcastInterceptingContext.FutureIntent;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.net.NetworkPolicyManagerService;
-import com.android.server.net.NetworkPolicyManagerService.ProcStateSeqHistory;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -142,12 +138,10 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -191,8 +185,6 @@
private static final String TEST_IFACE = "test0";
private static final String TEST_SSID = "AndroidAP";
- private static final String LINE_SEPARATOR = System.getProperty("line.separator");
-
private static NetworkTemplate sTemplateWifi = NetworkTemplate.buildTemplateWifi(TEST_SSID);
/**
@@ -1109,14 +1101,6 @@
final long procStateSeq = 222;
callOnUidStateChanged(UID_A, ActivityManager.PROCESS_STATE_SERVICE, procStateSeq);
verify(mActivityManagerInternal).notifyNetworkPolicyRulesUpdated(UID_A, procStateSeq);
-
- final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- final IndentingPrintWriter writer = new IndentingPrintWriter(
- new PrintWriter(outputStream), " ");
- mService.mObservedHistory.dumpUL(writer);
- writer.flush();
- assertEquals(ProcStateSeqHistory.getString(UID_A, procStateSeq),
- outputStream.toString().trim());
}
private void callOnUidStateChanged(int uid, int procState, long procStateSeq)
@@ -1129,59 +1113,6 @@
latch.await(2, TimeUnit.SECONDS);
}
- @Test
- public void testProcStateHistory() {
- // Verify dump works correctly with no elements added.
- verifyProcStateHistoryDump(0);
-
- // Add items upto half of the max capacity and verify that dump works correctly.
- verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY / 2);
-
- // Add items upto the max capacity and verify that dump works correctly.
- verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY);
-
- // Add more items than max capacity and verify that dump works correctly.
- verifyProcStateHistoryDump(MAX_PROC_STATE_SEQ_HISTORY + MAX_PROC_STATE_SEQ_HISTORY / 2);
-
- }
-
- private void verifyProcStateHistoryDump(int count) {
- final ProcStateSeqHistory history = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
- final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- final IndentingPrintWriter writer = new IndentingPrintWriter(
- new PrintWriter(outputStream), " ");
-
- if (count == 0) {
- // Verify with no uid info written to history.
- history.dumpUL(writer);
- writer.flush();
- assertEquals("When no uid info is there, dump should contain NONE",
- "NONE", outputStream.toString().trim());
- return;
- }
-
- int uid = 111;
- long procStateSeq = 222;
- // Add count items and verify dump works correctly.
- for (int i = 0; i < count; ++i) {
- uid++;
- procStateSeq++;
- history.addProcStateSeqUL(uid, procStateSeq);
- }
- history.dumpUL(writer);
- writer.flush();
- final String[] uidsDump = outputStream.toString().split(LINE_SEPARATOR);
- // Dump will have at most MAX_PROC_STATE_SEQ_HISTORY items.
- final int expectedCount = (count < MAX_PROC_STATE_SEQ_HISTORY)
- ? count : MAX_PROC_STATE_SEQ_HISTORY;
- assertEquals(expectedCount, uidsDump.length);
- for (int i = 0; i < expectedCount; ++i) {
- assertEquals(ProcStateSeqHistory.getString(uid, procStateSeq), uidsDump[i]);
- uid--;
- procStateSeq--;
- }
- }
-
private void assertCycleDayAsExpected(PersistableBundle config, int carrierCycleDay,
boolean expectValid) {
config.putInt(KEY_MONTHLY_DATA_CYCLE_DAY_INT, carrierCycleDay);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index b4919b6..1cec0d9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -277,4 +277,10 @@
verify(options, times(1)).abort();
}
}
+
+// TODO(b/69270257): Add test to verify task layout is passed additional data such as activity and
+// source.
+// @Test
+// public void testCreateTaskLayout() {
+// }
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 2fffb89..d74d994 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -191,6 +191,7 @@
private String mPackage;
private int mFlags = 0;
private int mTaskId = 0;
+ private int mUserId = 0;
private IVoiceInteractionSession mVoiceSession;
private ActivityStack mStack;
@@ -224,6 +225,11 @@
return this;
}
+ TaskBuilder setUserId(int userId) {
+ mUserId = userId;
+ return this;
+ }
+
TaskBuilder setStack(ActivityStack stack) {
mStack = stack;
return this;
@@ -245,10 +251,12 @@
final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
+ task.userId = mUserId;
mSupervisor.setFocusStackUnchecked("test", mStack);
mStack.addTask(task, true, "creating test task");
task.setStack(mStack);
task.setWindowContainerController(mock(TaskWindowContainerController.class));
+ task.touchActiveTime();
return task;
}
diff --git a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
index 1adfb67..54df744 100644
--- a/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/LockTaskControllerTest.java
@@ -90,6 +90,7 @@
@Mock private LockPatternUtils mLockPatternUtils;
@Mock private LockTaskNotify mLockTaskNotify;
@Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+ @Mock private RecentTasks mRecentTasks;
private LockTaskController mLockTaskController;
private Context mContext;
@@ -110,9 +111,10 @@
Looper.prepare();
}
+ mSupervisor.mRecentTasks = mRecentTasks;
+
mLockTaskController = new LockTaskController(mContext, mSupervisor,
new ImmediatelyExecuteHandler());
-
mLockTaskController.setWindowManager(mWindowManager);
mLockTaskController.mStatusBarService = mStatusBarService;
mLockTaskController.mDevicePolicyManager = mDevicePolicyManager;
@@ -601,6 +603,8 @@
eq(mContext.getPackageName()));
verify(mStatusBarService).disable2(eq(statusBarMask2), any(IBinder.class),
eq(mContext.getPackageName()));
+ // THEN recents should have been notified
+ verify(mRecentTasks).onLockTaskModeStateChanged(anyInt(), eq(TEST_USER_ID));
// THEN the DO/PO should be informed about the operation
verify(mDevicePolicyManager).notifyLockTaskModeChanged(true, TEST_PACKAGE_NAME,
TEST_USER_ID);
diff --git a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
index afece5d..13ca10c 100644
--- a/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
@@ -23,6 +23,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -30,6 +31,8 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static java.lang.Integer.MAX_VALUE;
@@ -42,6 +45,7 @@
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -64,6 +68,7 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
+import java.util.Random;
import java.util.Set;
/**
@@ -78,17 +83,19 @@
private static final int TEST_QUIET_USER_ID = 20;
private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
private static final UserInfo QUIET_USER_INFO = new UserInfo();
- private static final ComponentName MY_COMPONENT = new ComponentName(
- RecentTasksTest.class.getPackage().getName(), RecentTasksTest.class.getName());
private static int LAST_TASK_ID = 1;
+ private static int LAST_STACK_ID = 1;
private static int INVALID_STACK_ID = 999;
private Context mContext = InstrumentationRegistry.getContext();
private ActivityManagerService mService;
+ private ActivityDisplay mDisplay;
+ private ActivityDisplay mOtherDisplay;
private ActivityStack mStack;
+ private ActivityStack mHomeStack;
private TestTaskPersister mTaskPersister;
- private RecentTasks mRecentTasks;
- private RunningTasks mRunningTasks;
+ private TestRecentTasks mRecentTasks;
+ private TestRunningTasks mRunningTasks;
private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
@@ -133,22 +140,25 @@
mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
- mRecentTasks = mService.getRecentTasks();
+ mRecentTasks = (TestRecentTasks) mService.getRecentTasks();
mRecentTasks.loadParametersFromResources(mContext.getResources());
+ mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ ((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack);
mCallbacksRecorder = new CallbacksRecorder();
mRecentTasks.registerCallback(mCallbacksRecorder);
QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
- mTasks.add(createTask(".Task1"));
- mTasks.add(createTask(".Task2"));
- mTasks.add(createTask(".Task3"));
- mTasks.add(createTask(".Task4"));
- mTasks.add(createTask(".Task5"));
+ mTasks.add(createTaskBuilder(".Task1").build());
+ mTasks.add(createTaskBuilder(".Task2").build());
+ mTasks.add(createTaskBuilder(".Task3").build());
+ mTasks.add(createTaskBuilder(".Task4").build());
+ mTasks.add(createTaskBuilder(".Task5").build());
- mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
- mSameDocumentTasks.add(createDocumentTask(".DocumentTask1", null /* affinity */));
+ mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
+ mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
}
@Test
@@ -172,9 +182,9 @@
mCallbacksRecorder.clear();
// Add a task which will trigger the trimming of another
- TaskRecord documentTask1 = createDocumentTask(".DocumentTask1", null /* affinity */);
+ TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
documentTask1.maxRecents = 1;
- TaskRecord documentTask2 = createDocumentTask(".DocumentTask1", null /* affinity */);
+ TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
mRecentTasks.add(documentTask1);
mRecentTasks.add(documentTask2);
assertTrue(mCallbacksRecorder.added.contains(documentTask1));
@@ -194,13 +204,15 @@
@Test
public void testUsersTasks() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
+
// Setup some tasks for the users
mTaskPersister.userTaskIdsOverride = new SparseBooleanArray();
mTaskPersister.userTaskIdsOverride.put(1, true);
mTaskPersister.userTaskIdsOverride.put(2, true);
mTaskPersister.userTasksOverride = new ArrayList<>();
- mTaskPersister.userTasksOverride.add(createTask(".UserTask1"));
- mTaskPersister.userTasksOverride.add(createTask(".UserTask2"));
+ mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build());
+ mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build());
// Assert no user tasks are initially loaded
assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0);
@@ -235,6 +247,20 @@
@Test
public void testOrderedIteration() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
+ TaskRecord task1 = createTaskBuilder(".Task1").build();
+ task1.lastActiveTime = new Random().nextInt();
+ TaskRecord task2 = createTaskBuilder(".Task1").build();
+ task2.lastActiveTime = new Random().nextInt();
+ TaskRecord task3 = createTaskBuilder(".Task1").build();
+ task3.lastActiveTime = new Random().nextInt();
+ TaskRecord task4 = createTaskBuilder(".Task1").build();
+ task4.lastActiveTime = new Random().nextInt();
+ mRecentTasks.add(task1);
+ mRecentTasks.add(task2);
+ mRecentTasks.add(task3);
+ mRecentTasks.add(task4);
+
MutableLong prevLastActiveTime = new MutableLong(0);
final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
for (int i = 0; i < tasks.size(); i++) {
@@ -246,6 +272,8 @@
@Test
public void testTrimToGlobalMaxNumRecents() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
+
// Limit the global maximum number of recent tasks to a fixed size
mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */);
@@ -260,8 +288,9 @@
@Test
public void testTrimQuietProfileTasks() throws Exception {
- TaskRecord qt1 = createTask(".QuietTask1", TEST_QUIET_USER_ID);
- TaskRecord qt2 = createTask(".QuietTask2", TEST_QUIET_USER_ID);
+ mRecentTasks.setOnlyTestVisibleRange();
+ TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build();
+ TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build();
mRecentTasks.add(qt1);
mRecentTasks.add(qt2);
@@ -274,16 +303,17 @@
@Test
public void testSessionDuration() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
- TaskRecord t1 = createTask(".Task1");
+ TaskRecord t1 = createTaskBuilder(".Task1").build();
t1.touchActiveTime();
mRecentTasks.add(t1);
// Force a small sleep just beyond the session duration
SystemClock.sleep(75);
- TaskRecord t2 = createTask(".Task2");
+ TaskRecord t2 = createTaskBuilder(".Task2").build();
t2.touchActiveTime();
mRecentTasks.add(t2);
@@ -293,12 +323,15 @@
@Test
public void testVisibleTasks_excludedFromRecents() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */);
- TaskRecord excludedTask1 = createTask(".ExcludedTask1", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
- TEST_USER_0_ID);
- TaskRecord excludedTask2 = createTask(".ExcludedTask2", FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
- TEST_USER_0_ID);
+ TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1")
+ .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ .build();
+ TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2")
+ .setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
+ .build();
mRecentTasks.add(excludedTask1);
mRecentTasks.add(mTasks.get(0));
@@ -312,6 +345,7 @@
@Test
public void testVisibleTasks_minNum() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */);
for (int i = 0; i < 4; i++) {
@@ -327,12 +361,12 @@
mRecentTasks.add(mTasks.get(4));
// Ensure that there are a minimum number of tasks regardless of session length
- assertTrue(mCallbacksRecorder.trimmed.isEmpty());
- assertTrue(mCallbacksRecorder.removed.isEmpty());
+ assertNoTasksTrimmed();
}
@Test
public void testVisibleTasks_maxNum() throws Exception {
+ mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
for (int i = 0; i < 5; i++) {
@@ -346,16 +380,78 @@
}
@Test
+ public void testBackStackTasks_expectNoTrim() throws Exception {
+ mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+ final MyTestActivityStackSupervisor supervisor =
+ (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+ final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ supervisor.setHomeStack(homeStack);
+
+ // Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
+ // the tasks belong in stacks above the home stack
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build());
+
+ assertNoTasksTrimmed();
+ }
+
+ @Test
+ public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception {
+ mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+ final MyTestActivityStackSupervisor supervisor =
+ (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+ final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
+ supervisor.setHomeStack(homeStack);
+
+ // Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
+ // the home stack is trimmed once a new task is added
+ final TaskRecord behindHomeTask = createTaskBuilder(".Task1")
+ .setStack(behindHomeStack)
+ .build();
+ mRecentTasks.add(behindHomeTask);
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
+
+ assertTrimmed(behindHomeTask);
+ }
+
+ @Test
+ public void testOtherDisplayTasks_expectNoTrim() throws Exception {
+ mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
+
+ final MyTestActivityStackSupervisor supervisor =
+ (MyTestActivityStackSupervisor) mService.mStackSupervisor;
+ final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
+ final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor);
+ supervisor.setHomeStack(homeStack);
+
+ // Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
+ // removed
+ mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build());
+ mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build());
+ mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build());
+
+ assertNoTasksTrimmed();
+ }
+
+ @Test
public void testNotRecentsComponent_denyApiAccess() throws Exception {
doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
anyInt(), anyInt());
// Expect the following methods to fail due to recents component not being set
- ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(
- TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
+ mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
testRecentTasksApis(false /* expectNoSecurityException */);
// Don't throw for the following tests
- ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.DENY);
+ mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY);
testGetTasksApis(false /* expectNoSecurityException */);
}
@@ -365,7 +461,7 @@
anyInt(), anyInt());
// Set the recents component and ensure that the following calls do not fail
- ((TestRecentTasks) mRecentTasks).setIsCallerRecentsOverride(TestRecentTasks.GRANT);
+ mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
testRecentTasksApis(true /* expectNoSecurityException */);
testGetTasksApis(true /* expectNoSecurityException */);
}
@@ -436,38 +532,27 @@
mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
mService.getTasks(MAX_VALUE);
if (expectCallable) {
- assertTrue(((TestRecentTasks) mRecentTasks).mLastAllowed);
- assertTrue(((TestRunningTasks) mRunningTasks).mLastAllowed);
+ assertTrue(mRecentTasks.lastAllowed);
+ assertTrue(mRunningTasks.lastAllowed);
} else {
- assertFalse(((TestRecentTasks) mRecentTasks).mLastAllowed);
- assertFalse(((TestRunningTasks) mRunningTasks).mLastAllowed);
+ assertFalse(mRecentTasks.lastAllowed);
+ assertFalse(mRunningTasks.lastAllowed);
}
}
- private ComponentName createComponent(String className) {
- return new ComponentName(mContext.getPackageName(), className);
+ private TaskBuilder createTaskBuilder(String className) {
+ return new TaskBuilder(mService.mStackSupervisor)
+ .setComponent(new ComponentName(mContext.getPackageName(), className))
+ .setStack(mStack)
+ .setTaskId(LAST_TASK_ID++)
+ .setUserId(TEST_USER_0_ID);
}
- private TaskRecord createTask(String className) {
- return createTask(className, TEST_USER_0_ID);
- }
-
- private TaskRecord createTask(String className, int userId) {
- return createTask(className, 0 /* flags */, userId);
- }
-
- private TaskRecord createTask(String className, int flags, int userId) {
- TaskRecord task = new TaskBuilder(mService.mStackSupervisor)
- .setComponent(createComponent(className))
- .setStack(mStack).setFlags(flags).setTaskId(LAST_TASK_ID++).build();
- task.userId = userId;
- task.touchActiveTime();
- return task;
- }
-
- private TaskRecord createDocumentTask(String className, String affinity) {
- TaskRecord task = createTask(className, FLAG_ACTIVITY_NEW_DOCUMENT, TEST_USER_0_ID);
- task.affinity = affinity;
+ private TaskRecord createDocumentTask(String className) {
+ TaskRecord task = createTaskBuilder(className)
+ .setFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+ .build();
+ task.affinity = null;
return task;
}
@@ -476,6 +561,10 @@
return Arrays.binarySearch(userIds, targetUserId) >= 0;
}
+ private void assertNoTasksTrimmed() {
+ assertTrimmed();
+ }
+
private void assertTrimmed(TaskRecord... tasks) {
final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed;
final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed;
@@ -532,10 +621,41 @@
}
@Override
+ public void initialize() {
+ super.initialize();
+ mDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY);
+ mOtherDisplay = new ActivityDisplay(this, DEFAULT_DISPLAY);
+ attachDisplay(mOtherDisplay);
+ attachDisplay(mDisplay);
+ }
+
+ @Override
RunningTasks createRunningTasks() {
mRunningTasks = new TestRunningTasks();
return mRunningTasks;
}
+
+ void setHomeStack(ActivityStack stack) {
+ mHomeStack = stack;
+ }
+ }
+
+ private class MyTestActivityStack extends TestActivityStack {
+ private ActivityDisplay mDisplay = null;
+
+ MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
+ super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
+ ACTIVITY_TYPE_STANDARD, true);
+ mDisplay = display;
+ }
+
+ @Override
+ ActivityDisplay getDisplay() {
+ if (mDisplay != null) {
+ return mDisplay;
+ }
+ return super.getDisplay();
+ }
}
private static class CallbacksRecorder implements Callbacks {
@@ -564,7 +684,6 @@
}
private static class TestTaskPersister extends TaskPersister {
-
SparseBooleanArray userTaskIdsOverride;
ArrayList<TaskRecord> userTasksOverride;
@@ -595,8 +714,10 @@
static final int DENY_THROW_SECURITY_EXCEPTION = 2;
private boolean mOverrideIsCallerRecents;
+ private boolean mIsTrimmableOverride;
private int mIsCallerRecentsPolicy;
- boolean mLastAllowed;
+
+ boolean lastAllowed;
TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
UserController userController) {
@@ -623,24 +744,39 @@
mIsCallerRecentsPolicy = policy;
}
+ /**
+ * To simplify the setup for some tests, the caller can request that we only rely on the
+ * visible range test to determine what is trimmable. In this case, we don't try to
+ * use the stack order to determine additionally if the task is trimmable when it is not
+ * in the visible range.
+ */
+ void setOnlyTestVisibleRange() {
+ mIsTrimmableOverride = true;
+ }
+
@Override
ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
boolean getTasksAllowed,
boolean getDetailedTasks, int userId, int callingUid) {
- mLastAllowed = getTasksAllowed;
+ lastAllowed = getTasksAllowed;
return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
callingUid);
}
+
+ @Override
+ protected boolean isTrimmable(TaskRecord task) {
+ return mIsTrimmableOverride || super.isTrimmable(task);
+ }
}
private static class TestRunningTasks extends RunningTasks {
- boolean mLastAllowed;
+ boolean lastAllowed;
@Override
void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
int callingUid, boolean allowed) {
- mLastAllowed = allowed;
+ lastAllowed = allowed;
super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
callingUid, allowed);
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index 6938e0f..6a1d268 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -94,12 +94,29 @@
}
@Test
- public void testStartStopTracker() {
+ public void testStartStopTrackerScreenOnOff() {
+ mInjector.mInteractive = false;
startTracker(mTracker);
- assertNotNull(mInjector.mSensorListener);
+ assertNull(mInjector.mSensorListener);
assertNotNull(mInjector.mSettingsObserver);
assertNotNull(mInjector.mBroadcastReceiver);
assertTrue(mInjector.mIdleScheduled);
+ Intent onIntent = new Intent();
+ onIntent.setAction(Intent.ACTION_SCREEN_ON);
+ mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+ onIntent);
+ assertNotNull(mInjector.mSensorListener);
+
+ Intent offIntent = new Intent();
+ offIntent.setAction(Intent.ACTION_SCREEN_OFF);
+ mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+ offIntent);
+ assertNull(mInjector.mSensorListener);
+
+ mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+ onIntent);
+ assertNotNull(mInjector.mSensorListener);
+
mTracker.stop();
assertNull(mInjector.mSensorListener);
assertNull(mInjector.mSettingsObserver);
@@ -532,7 +549,6 @@
mInjector.waitForHandler();
}
-
private static final class Idle implements MessageQueue.IdleHandler {
private boolean mIdle;
@@ -565,6 +581,7 @@
long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
Handler mHandler;
boolean mIdleScheduled;
+ boolean mInteractive = true;
public TestInjector(Handler handler) {
mHandler = handler;
@@ -577,7 +594,7 @@
@Override
public void registerSensorListener(Context context,
- SensorEventListener sensorListener) {
+ SensorEventListener sensorListener, Handler handler) {
mSensorListener = sensorListener;
}
@@ -694,5 +711,9 @@
public void cancelIdleJob(Context context) {
mIdleScheduled = false;
}
+
+ public boolean isInteractive(Context context) {
+ return mInteractive;
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
index 4e23b51..04f5e5e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/ZOrderingTests.java
@@ -42,6 +42,9 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
/**
* Tests for the {@link WindowLayersController} class.
@@ -323,4 +326,21 @@
assertWindowLayerGreaterThan(mTransaction, assistantStackWindow, dockedStackWindow);
assertWindowLayerGreaterThan(mTransaction, pinnedStackWindow, assistantStackWindow);
}
+
+ @Test
+ public void testAssignWindowLayers_ForSysUiPanels() throws Exception {
+ final WindowState navBarPanel =
+ createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel");
+ final WindowState statusBarPanel =
+ createWindow(null, TYPE_STATUS_BAR_PANEL, mDisplayContent, "StatusBarPanel");
+ final WindowState statusBarSubPanel =
+ createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel");
+ mDisplayContent.assignChildLayers(mTransaction);
+
+ // Ime should be above all app windows and below system windows if it is targeting an app
+ // window.
+ assertWindowLayerGreaterThan(mTransaction, navBarPanel, mNavBarWindow);
+ assertWindowLayerGreaterThan(mTransaction, statusBarPanel, mStatusBarWindow);
+ assertWindowLayerGreaterThan(mTransaction, statusBarSubPanel, statusBarPanel);
+ }
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
index a35b463..7763150 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACHeader.java
@@ -32,7 +32,7 @@
// numbers associate with this endpoint
private byte mControls; // Vers 2.0 thing
- public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 2363c4d..75531d1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -32,7 +32,7 @@
private byte mChannelNames; // 10:1 Unused (0x00)
private byte mTerminal; // 11:1 Unused (0x00)
- public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index d348664..c7634ba 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -30,7 +30,7 @@
private byte[] mControls; // bitmasks of which controls are present for each channel
private byte mNameID; // string descriptor ID of mixer name
- public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
index 9f2f09e..468ae57 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
@@ -28,7 +28,7 @@
private byte mSourceID; // 7:1 From Input Terminal. (0x01)
private byte mTerminal; // 8:1 Unused.
- public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
index 1523bb5..1d8498a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatI.java
@@ -33,7 +33,7 @@
// min & max rates otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
index b1e7680..3c45790 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASFormatII.java
@@ -38,7 +38,7 @@
// the min & max rates. otherwise mSamFreqType rates.
// All 3-byte values. All rates in Hz
- public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
index 2d4f604..4fbbb21 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb10ASGeneral.java
@@ -34,7 +34,7 @@
private int mFormatTag; // 5:2 The Audio Data Format that has to be used to communicate
// with this interface.
- public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
index eefae3d..fe1b502 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACHeader.java
@@ -29,7 +29,7 @@
// See audio20.pdf Appendix A.7, “Audio Function Category Codes.”
private byte mControls; // 8:1 See audio20.pdf Table 4-5.
- public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+ public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
super(length, type, subtype, subclass, spec);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index 3e2ac39..ee1b32c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -39,7 +39,7 @@
private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the
// Input Terminal.
- public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index 1b267a6..ab96585 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -33,7 +33,7 @@
private byte mNameID; // 12+p+N:1 Index of a string descriptor, describing the
// Mixer Unit.
- public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 67478aa..20a97af 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -34,7 +34,7 @@
private int mControls; // 9:2 - see Audio20.pdf Table 4-10
private byte mTerminalID; // 11:1 - Index of a string descriptor, describing the
- public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+ public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
index c031996..7310a3e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatI.java
@@ -31,7 +31,7 @@
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subslot.
- public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
index dc44ff0..fe88743 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatII.java
@@ -34,7 +34,7 @@
/**
* TBD
*/
- public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
index b44a216..b0ba02f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASFormatIII.java
@@ -31,7 +31,7 @@
private byte mBitResolution; // 5:1 The number of effectively used bits from
// the available bits in an audio subframe.
- public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+ public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) {
super(length, type, subtype, formatType, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
index 18d48a0..de20738 100644
--- a/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/services/usb/java/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -41,7 +41,7 @@
private byte mChannelNames; // 15:1 Index of a string descriptor, describing the
// name of the first physical channel.
- public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) {
+ public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 6e1ce07..409e605c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,7 +38,7 @@
static final byte ATTRIBSMASK_SYNC = 0x0C;
static final byte ATTRIBMASK_TRANS = 0x03;
- public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index d351902..e63bb74 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,7 +24,7 @@
private static final String TAG = "UsbACAudioStreamEndpoint";
//TODO data fields...
- public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+ public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
index 4a6839d..7ebccf3 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,16 +25,16 @@
abstract class UsbACEndpoint extends UsbDescriptor {
private static final String TAG = "UsbACEndpoint";
- protected final byte mSubclass; // from the mSubclass member of the "enclosing"
- // Interface Descriptor, not the stream.
- protected byte mSubtype; // 2:1 HEADER descriptor subtype
+ protected final int mSubclass; // from the mSubclass member of the "enclosing"
+ // Interface Descriptor, not the stream.
+ protected byte mSubtype; // 2:1 HEADER descriptor subtype
- UsbACEndpoint(int length, byte type, byte subclass) {
+ UsbACEndpoint(int length, byte type, int subclass) {
super(length, type);
mSubclass = subclass;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
@@ -52,7 +52,7 @@
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
int length, byte type) {
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return new UsbACAudioControlEndpoint(length, type, subClass);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
index ab3903b..2c7ef79 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -46,7 +46,7 @@
// logical channel
private byte mUnitName; // ?:1 Index of a string descriptor, describing this Feature Unit.
- public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
index 01a355e..88d026e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACHeaderInterface.java
@@ -31,7 +31,7 @@
// of this descriptor header and all Unit and Terminal descriptors.
public UsbACHeaderInterface(
- int length, byte type, byte subtype, byte subclass, int adcRelease) {
+ int length, byte type, byte subtype, int subclass, int adcRelease) {
super(length, type, subtype, subclass);
mADCRelease = adcRelease;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
index df6c53f..38c12a1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterface.java
@@ -78,10 +78,10 @@
public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
protected final byte mSubtype; // 2:1 HEADER descriptor subtype
- protected final byte mSubclass; // from the mSubclass member of the
+ protected final int mSubclass; // from the mSubclass member of the
// "enclosing" Interface Descriptor
- public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+ public UsbACInterface(int length, byte type, byte subtype, int subclass) {
super(length, type);
mSubtype = subtype;
mSubclass = subclass;
@@ -91,12 +91,12 @@
return mSubtype;
}
- public byte getSubclass() {
+ public int getSubclass() {
return mSubclass;
}
private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
switch (subtype) {
case ACI_HEADER:
{
@@ -157,7 +157,7 @@
}
private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+ ByteStream stream, int length, byte type, byte subtype, int subClass) {
//int spec = parser.getUsbSpec();
int acInterfaceSpec = parser.getACInterfaceSpec();
switch (subtype) {
@@ -182,7 +182,7 @@
}
private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
- byte subtype, byte subClass) {
+ byte subtype, int subClass) {
switch (subtype) {
case MSI_HEADER:
return new UsbMSMidiHeader(length, type, subtype, subClass);
@@ -212,7 +212,7 @@
int length, byte type) {
byte subtype = stream.getByte();
UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
- byte subClass = interfaceDesc.getUsbSubclass();
+ int subClass = interfaceDesc.getUsbSubclass();
switch (subClass) {
case AUDIO_AUDIOCONTROL:
return allocAudioControlDescriptor(
@@ -236,7 +236,7 @@
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte subClass = getSubclass();
+ int subClass = getSubclass();
String subClassName = UsbStrings.getACInterfaceSubclassName(subClass);
byte subtype = getSubtype();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
index 9e00a79..bd027ae 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
@@ -22,7 +22,7 @@
public final class UsbACInterfaceUnparsed extends UsbACInterface {
private static final String TAG = "UsbACInterfaceUnparsed";
- public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) {
+ public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
index 9c31457..42ee889 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -28,7 +28,7 @@
private byte mNumJacks;
private byte[] mJackIds;
- public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+ public UsbACMidiEndpoint(int length, byte type, int subclass) {
super(length, type, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
index 88faed9..606fa2b 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -24,7 +24,7 @@
// are connected.
protected byte mNumOutputs; // The number of output channels
- public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
index b16bc57..4644fe1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -32,7 +32,7 @@
// Input Pin of this Selector Unit is connected.
private byte mNameIndex; // Index of a string descriptor, describing the Selector Unit.
- public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+ public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) {
super(length, type, subtype, subClass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
index 2836508..36139d6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -32,7 +32,7 @@
protected int mTerminalType; // 4:2 USB Streaming. (0x0101)
protected byte mAssocTerminal; // 6:1 Unused (0x00)
- public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+ public UsbACTerminal(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
index 7a92f9e..5e515a1 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbASFormat.java
@@ -40,7 +40,7 @@
public static final byte EXT_FORMAT_TYPE_II = (byte) 0x82;
public static final byte EXT_FORMAT_TYPE_III = (byte) 0x83;
- public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+ public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) {
super(length, type, subtype, mSubclass);
mFormatType = formatType;
}
@@ -66,8 +66,8 @@
* stream.
*/
public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
- ByteStream stream, int length, byte type,
- byte subtype, byte subclass) {
+ ByteStream stream, int length, byte type,
+ byte subtype, int subclass) {
byte formatType = stream.getByte();
int acInterfaceSpec = parser.getACInterfaceSpec();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 511e59a..993778f 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -30,15 +30,15 @@
public final class UsbConfigDescriptor extends UsbDescriptor {
private static final String TAG = "UsbConfigDescriptor";
- private int mTotalLength; // 2:2 Total length in bytes of data returned
+ private int mTotalLength; // 2:2 Total length in bytes of data returned
private byte mNumInterfaces; // 4:1 Number of Interfaces
- private byte mConfigValue; // 5:1 Value to use as an argument to select this configuration
- private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
- private byte mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
- // D6 Self Powered
- // D5 Remote Wakeup
- // D4..0 Reserved, set to 0.
- private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
+ private int mConfigValue; // 5:1 Value to use as an argument to select this configuration
+ private byte mConfigIndex; // 6:1 Index of String Descriptor describing this configuration
+ private int mAttribs; // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+ // D6 Self Powered
+ // D5 Remote Wakeup
+ // D4..0 Reserved, set to 0.
+ private int mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
new ArrayList<UsbInterfaceDescriptor>();
@@ -56,7 +56,7 @@
return mNumInterfaces;
}
- public byte getConfigValue() {
+ public int getConfigValue() {
return mConfigValue;
}
@@ -64,11 +64,11 @@
return mConfigIndex;
}
- public byte getAttribs() {
+ public int getAttribs() {
return mAttribs;
}
- public byte getMaxPower() {
+ public int getMaxPower() {
return mMaxPower;
}
@@ -92,10 +92,10 @@
public int parseRawDescriptors(ByteStream stream) {
mTotalLength = stream.unpackUsbShort();
mNumInterfaces = stream.getByte();
- mConfigValue = stream.getByte();
+ mConfigValue = stream.getUnsignedByte();
mConfigIndex = stream.getByte();
- mAttribs = stream.getByte();
- mMaxPower = stream.getByte();
+ mAttribs = stream.getUnsignedByte();
+ mMaxPower = stream.getUnsignedByte();
return mLength;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
index 8c7565b..3fc5fe3 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -85,36 +85,36 @@
public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
// Class IDs
- public static final byte CLASSID_DEVICE = 0x00;
- public static final byte CLASSID_AUDIO = 0x01;
- public static final byte CLASSID_COM = 0x02;
- public static final byte CLASSID_HID = 0x03;
- // public static final byte CLASSID_??? = 0x04;
- public static final byte CLASSID_PHYSICAL = 0x05;
- public static final byte CLASSID_IMAGE = 0x06;
- public static final byte CLASSID_PRINTER = 0x07;
- public static final byte CLASSID_STORAGE = 0x08;
- public static final byte CLASSID_HUB = 0x09;
- public static final byte CLASSID_CDC_CONTROL = 0x0A;
- public static final byte CLASSID_SMART_CARD = 0x0B;
- //public static final byte CLASSID_??? = 0x0C;
- public static final byte CLASSID_SECURITY = 0x0D;
- public static final byte CLASSID_VIDEO = 0x0E;
- public static final byte CLASSID_HEALTHCARE = 0x0F;
- public static final byte CLASSID_AUDIOVIDEO = 0x10;
- public static final byte CLASSID_BILLBOARD = 0x11;
- public static final byte CLASSID_TYPECBRIDGE = 0x12;
- public static final byte CLASSID_DIAGNOSTIC = (byte) 0xDC;
- public static final byte CLASSID_WIRELESS = (byte) 0xE0;
- public static final byte CLASSID_MISC = (byte) 0xEF;
- public static final byte CLASSID_APPSPECIFIC = (byte) 0xFE;
- public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+ public static final int CLASSID_DEVICE = 0x00;
+ public static final int CLASSID_AUDIO = 0x01;
+ public static final int CLASSID_COM = 0x02;
+ public static final int CLASSID_HID = 0x03;
+ // public static final int CLASSID_??? = 0x04;
+ public static final int CLASSID_PHYSICAL = 0x05;
+ public static final int CLASSID_IMAGE = 0x06;
+ public static final int CLASSID_PRINTER = 0x07;
+ public static final int CLASSID_STORAGE = 0x08;
+ public static final int CLASSID_HUB = 0x09;
+ public static final int CLASSID_CDC_CONTROL = 0x0A;
+ public static final int CLASSID_SMART_CARD = 0x0B;
+ //public static final int CLASSID_??? = 0x0C;
+ public static final int CLASSID_SECURITY = 0x0D;
+ public static final int CLASSID_VIDEO = 0x0E;
+ public static final int CLASSID_HEALTHCARE = 0x0F;
+ public static final int CLASSID_AUDIOVIDEO = 0x10;
+ public static final int CLASSID_BILLBOARD = 0x11;
+ public static final int CLASSID_TYPECBRIDGE = 0x12;
+ public static final int CLASSID_DIAGNOSTIC = 0xDC;
+ public static final int CLASSID_WIRELESS = 0xE0;
+ public static final int CLASSID_MISC = 0xEF;
+ public static final int CLASSID_APPSPECIFIC = 0xFE;
+ public static final int CLASSID_VENDSPECIFIC = 0xFF;
// Audio Subclass codes
- public static final byte AUDIO_SUBCLASS_UNDEFINED = 0x00;
- public static final byte AUDIO_AUDIOCONTROL = 0x01;
- public static final byte AUDIO_AUDIOSTREAMING = 0x02;
- public static final byte AUDIO_MIDISTREAMING = 0x03;
+ public static final int AUDIO_SUBCLASS_UNDEFINED = 0x00;
+ public static final int AUDIO_AUDIOCONTROL = 0x01;
+ public static final int AUDIO_AUDIOSTREAMING = 0x02;
+ public static final int AUDIO_MIDISTREAMING = 0x03;
// Request IDs
public static final int REQUEST_GET_STATUS = 0x00;
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index c5052c8..6c6bd01 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -291,7 +291,7 @@
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+ public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
// ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
@@ -313,7 +313,7 @@
/**
* @hide
*/
- public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+ public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index c89d9b6..8e7f0fd 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -36,9 +36,9 @@
public static final int USBSPEC_2_0 = 0x0200;
private int mSpec; // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
- private byte mDevClass; // 4:1 class code
- private byte mDevSubClass; // 5:1 subclass code
- private byte mProtocol; // 6:1 protocol
+ private int mDevClass; // 4:1 class code
+ private int mDevSubClass; // 5:1 subclass code
+ private int mProtocol; // 6:1 protocol
private byte mPacketSize; // 7:1 Maximum Packet Size for Zero Endpoint.
// Valid Sizes are 8, 16, 32, 64
private int mVendorID; // 8:2 vendor ID
@@ -61,15 +61,15 @@
return mSpec;
}
- public byte getDevClass() {
+ public int getDevClass() {
return mDevClass;
}
- public byte getDevSubClass() {
+ public int getDevSubClass() {
return mDevSubClass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -137,9 +137,9 @@
@Override
public int parseRawDescriptors(ByteStream stream) {
mSpec = stream.unpackUsbShort();
- mDevClass = stream.getByte();
- mDevSubClass = stream.getByte();
- mProtocol = stream.getByte();
+ mDevClass = stream.getUnsignedByte();
+ mDevSubClass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mPacketSize = stream.getByte();
mVendorID = stream.unpackUsbShort();
mProductID = stream.unpackUsbShort();
@@ -161,9 +161,9 @@
int spec = getSpec();
canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));
- byte devClass = getDevClass();
+ int devClass = getDevClass();
String classStr = UsbStrings.getClassName(devClass);
- byte devSubClass = getDevSubClass();
+ int devSubClass = getDevSubClass();
String subClasStr = UsbStrings.getClassName(devSubClass);
canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
+ devSubClass + ": " + subClasStr);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 9cb8f05..1130238 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -27,16 +27,16 @@
public class UsbEndpointDescriptor extends UsbDescriptor {
private static final String TAG = "UsbEndpointDescriptor";
- public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111;
- public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000;
- public static final byte DIRECTION_OUTPUT = 0x00;
- public static final byte DIRECTION_INPUT = (byte) 0x80;
+ public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
+ public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
+ public static final int DIRECTION_OUTPUT = 0x0000;
+ public static final int DIRECTION_INPUT = (byte) 0x0080;
- public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
- public static final byte TRANSTYPE_CONTROL = 0x00;
- public static final byte TRANSTYPE_ISO = 0x01;
- public static final byte TRANSTYPE_BULK = 0x02;
- public static final byte TRANSTYPE_INTERRUPT = 0x03;
+ public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+ public static final int TRANSTYPE_CONTROL = 0x00;
+ public static final int TRANSTYPE_ISO = 0x01;
+ public static final int TRANSTYPE_BULK = 0x02;
+ public static final int TRANSTYPE_INTERRUPT = 0x03;
public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100;
public static final byte SYNCTYPE_NONE = 0b00000000;
@@ -44,18 +44,18 @@
public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000;
public static final byte SYNCTYPE_RESERVED = 0b00001100;
- public static final byte MASK_ATTRIBS_USEAGE = 0b00110000;
- public static final byte USEAGE_DATA = 0b00000000;
- public static final byte USEAGE_FEEDBACK = 0b00010000;
- public static final byte USEAGE_EXPLICIT = 0b00100000;
- public static final byte USEAGE_RESERVED = 0b00110000;
+ public static final int MASK_ATTRIBS_USEAGE = 0b00110000;
+ public static final int USEAGE_DATA = 0b00000000;
+ public static final int USEAGE_FEEDBACK = 0b00010000;
+ public static final int USEAGE_EXPLICIT = 0b00100000;
+ public static final int USEAGE_RESERVED = 0b00110000;
- private byte mEndpointAddress; // 2:1 Endpoint Address
+ private int mEndpointAddress; // 2:1 Endpoint Address
// Bits 0..3b Endpoint Number.
// Bits 4..6b Reserved. Set to Zero
// Bits 7 Direction 0 = Out, 1 = In
// (Ignored for Control Endpoints)
- private byte mAttributes; // 3:1 Various flags
+ private int mAttributes; // 3:1 Various flags
// Bits 0..1 Transfer Type:
// 00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
// Bits 2..7 are reserved. If Isochronous endpoint,
@@ -71,7 +71,7 @@
// 11: Reserved
private int mPacketSize; // 4:2 Maximum Packet Size this endpoint is capable of
// sending or receiving
- private byte mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
+ private int mInterval; // 6:1 Interval for polling endpoint data transfers. Value in
// frame counts.
// Ignored for Bulk & Control Endpoints. Isochronous must equal
// 1 and field may range from 1 to 255 for interrupt endpoints.
@@ -83,11 +83,11 @@
mHierarchyLevel = 4;
}
- public byte getEndpointAddress() {
+ public int getEndpointAddress() {
return mEndpointAddress;
}
- public byte getAttributes() {
+ public int getAttributes() {
return mAttributes;
}
@@ -95,7 +95,7 @@
return mPacketSize;
}
- public byte getInterval() {
+ public int getInterval() {
return mInterval;
}
@@ -113,10 +113,10 @@
@Override
public int parseRawDescriptors(ByteStream stream) {
- mEndpointAddress = stream.getByte();
- mAttributes = stream.getByte();
+ mEndpointAddress = stream.getUnsignedByte();
+ mAttributes = stream.getUnsignedByte();
mPacketSize = stream.unpackUsbShort();
- mInterval = stream.getByte();
+ mInterval = stream.getUnsignedByte();
if (mLength == 9) {
mRefresh = stream.getByte();
mSyncAddress = stream.getByte();
@@ -130,13 +130,13 @@
canvas.openList();
- byte address = getEndpointAddress();
+ int address = getEndpointAddress();
canvas.writeListItem("Address: "
+ ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
+ ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
== UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
- byte attributes = getAttributes();
+ int attributes = getAttributes();
canvas.openListItem();
canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " ");
switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 645807d..d87b1af 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -31,12 +31,12 @@
public class UsbInterfaceDescriptor extends UsbDescriptor {
private static final String TAG = "UsbInterfaceDescriptor";
- protected byte mInterfaceNumber; // 2:1 Number of Interface
+ protected int mInterfaceNumber; // 2:1 Number of Interface
protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
protected byte mNumEndpoints; // 4:1 Number of Endpoints used for this interface
- protected byte mUsbClass; // 5:1 Class Code
- protected byte mUsbSubclass; // 6:1 Subclass Code
- protected byte mProtocol; // 7:1 Protocol Code
+ protected int mUsbClass; // 5:1 Class Code
+ protected int mUsbSubclass; // 6:1 Subclass Code
+ protected int mProtocol; // 7:1 Protocol Code
protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface
private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
@@ -49,18 +49,18 @@
@Override
public int parseRawDescriptors(ByteStream stream) {
- mInterfaceNumber = stream.getByte();
+ mInterfaceNumber = stream.getUnsignedByte();
mAlternateSetting = stream.getByte();
mNumEndpoints = stream.getByte();
- mUsbClass = stream.getByte();
- mUsbSubclass = stream.getByte();
- mProtocol = stream.getByte();
+ mUsbClass = stream.getUnsignedByte();
+ mUsbSubclass = stream.getUnsignedByte();
+ mProtocol = stream.getUnsignedByte();
mDescrIndex = stream.getByte();
return mLength;
}
- public byte getInterfaceNumber() {
+ public int getInterfaceNumber() {
return mInterfaceNumber;
}
@@ -72,15 +72,15 @@
return mNumEndpoints;
}
- public byte getUsbClass() {
+ public int getUsbClass() {
return mUsbClass;
}
- public byte getUsbSubclass() {
+ public int getUsbSubclass() {
return mUsbSubclass;
}
- public byte getProtocol() {
+ public int getProtocol() {
return mProtocol;
}
@@ -108,9 +108,9 @@
public void report(ReportCanvas canvas) {
super.report(canvas);
- byte usbClass = getUsbClass();
- byte usbSubclass = getUsbSubclass();
- byte protocol = getProtocol();
+ int usbClass = getUsbClass();
+ int usbSubclass = getUsbSubclass();
+ int protocol = getProtocol();
String className = UsbStrings.getClassName(usbClass);
String subclassName = "";
if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index 85a3e68..d0ca6db 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiHeader extends UsbACInterface {
private static final String TAG = "UsbMSMidiHeader";
- public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
index 1d5cbf2..7df7cfc 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiInputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiInputJack";
- UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+ UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
index 9f50240a..1879ac0 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -25,7 +25,7 @@
public final class UsbMSMidiOutputJack extends UsbACInterface {
private static final String TAG = "UsbMSMidiOutputJack";
- public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+ public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) {
super(length, type, subtype, subclass);
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
index 64ecebc..fb4576a 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -32,8 +32,8 @@
private static HashMap<Byte, String> sDescriptorNames;
private static HashMap<Byte, String> sACControlInterfaceNames;
private static HashMap<Byte, String> sACStreamingInterfaceNames;
- private static HashMap<Byte, String> sClassNames;
- private static HashMap<Byte, String> sAudioSubclassNames;
+ private static HashMap<Integer, String> sClassNames;
+ private static HashMap<Integer, String> sAudioSubclassNames;
private static HashMap<Integer, String> sAudioEncodingNames;
private static HashMap<Integer, String> sTerminalNames;
private static HashMap<Integer, String> sFormatNames;
@@ -92,7 +92,7 @@
}
private static void initClassNames() {
- sClassNames = new HashMap<Byte, String>();
+ sClassNames = new HashMap<Integer, String>();
sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
@@ -118,7 +118,7 @@
}
private static void initAudioSubclassNames() {
- sAudioSubclassNames = new HashMap<Byte, String>();
+ sAudioSubclassNames = new HashMap<Integer, String>();
sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
@@ -300,7 +300,7 @@
/**
* Retrieves the name for the specified USB class ID.
*/
- public static String getClassName(byte classID) {
+ public static String getClassName(int classID) {
String name = sClassNames.get(classID);
int iClassID = classID & 0xFF;
return name != null
@@ -312,7 +312,7 @@
/**
* Retrieves the name for the specified USB audio subclass ID.
*/
- public static String getAudioSubclassName(byte subClassID) {
+ public static String getAudioSubclassName(int subClassID) {
String name = sAudioSubclassNames.get(subClassID);
int iSubclassID = subClassID & 0xFF;
return name != null
@@ -335,7 +335,7 @@
/**
* Retrieves the name for the specified USB audio interface subclass ID.
*/
- public static String getACInterfaceSubclassName(byte subClassID) {
+ public static String getACInterfaceSubclassName(int subClassID) {
return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming";
}
}
diff --git a/test-base/Android.mk b/test-base/Android.mk
index f73eff7..9fc70f3 100644
--- a/test-base/Android.mk
+++ b/test-base/Android.mk
@@ -16,6 +16,21 @@
LOCAL_PATH:= $(call my-dir)
+# Build the android.test.base library
+# ===================================
+# This contains the junit.framework and android.test classes that were in
+# Android API level 25 excluding those from android.test.runner.
+# Also contains the com.android.internal.util.Predicate[s] classes.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := android.test.base
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+
+include $(BUILD_JAVA_LIBRARY)
+
# Build the legacy-test library
# =============================
# This contains the junit.framework and android.test classes that were in
@@ -45,8 +60,22 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
-# Generate the stub source files for legacy.test.stubs
-# ====================================================
+# Build the repackaged.android.test.base library
+# ==============================================
+# This contains repackaged versions of the classes from legacy-test.
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := repackaged.android.test.base
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Generate the stub source files for android.test.base.stubs
+# ==========================================================
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
@@ -60,81 +89,81 @@
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src
-LEGACY_TEST_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/api.txt
-LEGACY_TEST_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/removed.txt
+ANDROID_TEST_BASE_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/api.txt
+ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/removed.txt
-LEGACY_TEST_API_FILE := $(LOCAL_PATH)/api/android-test-base-current.txt
-LEGACY_TEST_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-base-removed.txt
+ANDROID_TEST_BASE_API_FILE := $(LOCAL_PATH)/api/android-test-base-current.txt
+ANDROID_TEST_BASE_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-base-removed.txt
LOCAL_DROIDDOC_OPTIONS:= \
-stubpackages android.test:android.test.suitebuilder.annotation:com.android.internal.util:junit.framework \
-stubsourceonly \
- -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/legacy.test.stubs_intermediates/src \
+ -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.base.stubs_intermediates/src \
-nodocs \
- -api $(LEGACY_TEST_OUTPUT_API_FILE) \
- -removedApi $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE) \
+ -api $(ANDROID_TEST_BASE_OUTPUT_API_FILE) \
+ -removedApi $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE) \
LOCAL_UNINSTALLABLE_MODULE := true
-LOCAL_MODULE := legacy-test-api-stubs-gen
+LOCAL_MODULE := android-test-base-api-stubs-gen
include $(BUILD_DROIDDOC)
# Remember the target that will trigger the code generation.
-legacy_test_api_gen_stamp := $(full_target)
+android_test_base_gen_stamp := $(full_target)
# Add some additional dependencies
-$(LEGACY_TEST_OUTPUT_API_FILE): $(full_target)
-$(LEGACY_TEST_OUTPUT_REMOVED_API_FILE): $(full_target)
+$(ANDROID_TEST_BASE_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE): $(full_target)
-# Build the legacy.test.stubs library
-# ===================================
+# Build the android.test.base.stubs library
+# =========================================
include $(CLEAR_VARS)
-LOCAL_MODULE := legacy.test.stubs
+LOCAL_MODULE := android.test.base.stubs
LOCAL_SOURCE_FILES_ALL_GENERATED := true
LOCAL_SDK_VERSION := current
-LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
# Make sure to run droiddoc first to generate the stub source files.
-LOCAL_ADDITIONAL_DEPENDENCIES := $(legacy_test_api_gen_stamp)
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_base_gen_stamp)
+android_test_base_gen_stamp :=
include $(BUILD_STATIC_JAVA_LIBRARY)
# Archive a copy of the classes.jar in SDK build.
-$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):legacy.test.stubs.jar)
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.base.stubs.jar)
-# Check that the legacy.test.stubs library has not changed
-# ========================================================
+# Check that the android.test.base.stubs library has not changed
+# ==============================================================
# Check that the API we're building hasn't changed from the not-yet-released
# SDK version.
$(eval $(call check-api, \
- check-legacy-test-api-current, \
- $(LEGACY_TEST_API_FILE), \
- $(LEGACY_TEST_OUTPUT_API_FILE), \
- $(LEGACY_TEST_REMOVED_API_FILE), \
- $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE), \
+ check-android-test-base-api-current, \
+ $(ANDROID_TEST_BASE_API_FILE), \
+ $(ANDROID_TEST_BASE_OUTPUT_API_FILE), \
+ $(ANDROID_TEST_BASE_REMOVED_API_FILE), \
+ $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE), \
-error 2 -error 3 -error 4 -error 5 -error 6 \
-error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
-error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
-error 25 -error 26 -error 27, \
cat $(LOCAL_PATH)/api/apicheck_msg_android_test_base.txt, \
- check-legacy-test-api, \
- $(call doc-timestamp-for,legacy-test-api-stubs-gen) \
+ check-android-test-base-api, \
+ $(call doc-timestamp-for,android-test-base-api-stubs-gen) \
))
-.PHONY: check-legacy-test-api
-checkapi: check-legacy-test-api
+.PHONY: check-android-test-base-api
+checkapi: check-android-test-base-api
-.PHONY: update-legacy-test-api
-update-api: update-legacy-test-api
+.PHONY: update-android-test-base-api
+update-api: update-android-test-base-api
-update-legacy-test-api: $(LEGACY_TEST_OUTPUT_API_FILE) | $(ACP)
+update-android-test-base-api: $(ANDROID_TEST_BASE_OUTPUT_API_FILE) | $(ACP)
@echo Copying current.txt
- $(hide) $(ACP) $(LEGACY_TEST_OUTPUT_API_FILE) $(LEGACY_TEST_API_FILE)
+ $(hide) $(ACP) $(ANDROID_TEST_BASE_OUTPUT_API_FILE) $(ANDROID_TEST_BASE_API_FILE)
@echo Copying removed.txt
- $(hide) $(ACP) $(LEGACY_TEST_OUTPUT_REMOVED_API_FILE) $(LEGACY_TEST_REMOVED_API_FILE)
+ $(hide) $(ACP) $(ANDROID_TEST_BASE_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_BASE_REMOVED_API_FILE)
# Build the legacy-android-test library
# =====================================
@@ -154,6 +183,17 @@
include $(BUILD_STATIC_JAVA_LIBRARY)
+# Build the legacy.test.stubs library
+# ===================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := legacy.test.stubs
+LOCAL_SDK_VERSION := current
+
+LOCAL_STATIC_JAVA_LIBRARIES := android.test.base.stubs
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
ifeq ($(HOST_OS),linux)
# Build the legacy-performance-test-hostdex library
# =================================================
@@ -165,5 +205,3 @@
include $(BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY)
endif # HOST_OS == linux
-
-legacy_test_api_gen_stamp :=
diff --git a/test-base/api/apicheck_msg_android_test_base.txt b/test-base/api/apicheck_msg_android_test_base.txt
index ad5f235..144aecc 100644
--- a/test-base/api/apicheck_msg_android_test_base.txt
+++ b/test-base/api/apicheck_msg_android_test_base.txt
@@ -6,10 +6,10 @@
1) You can add "@hide" javadoc comments to the methods, etc. listed in the
errors above.
- 2) You can update legacy-test-current.txt by executing the following command:
- make update-legacy-test-api
+ 2) You can update android-test-base-current.txt by executing the following command:
+ make update-android-test-base-api
- To submit the revised legacy-test-current.txt to the main Android repository,
+ To submit the revised android-test-base-current.txt to the main Android repository,
you will need approval.
******************************
diff --git a/test-runner/Android.mk b/test-runner/Android.mk
index c0fd7f8..87fe831 100644
--- a/test-runner/Android.mk
+++ b/test-runner/Android.mk
@@ -26,7 +26,7 @@
core-oj \
core-libart \
framework \
- legacy-test \
+ android.test.base \
android.test.mock \
LOCAL_MODULE:= android.test.runner
@@ -43,7 +43,7 @@
core-oj \
core-libart \
framework \
- legacy-test \
+ android.test.base \
android.test.mock \
LOCAL_JARJAR_RULES := $(LOCAL_PATH)/../test-base/jarjar-rules.txt
@@ -65,7 +65,7 @@
core-oj \
core-libart \
framework \
- legacy-test \
+ android.test.base \
android.test.mock \
LOCAL_MODULE_CLASS := JAVA_LIBRARIES
@@ -104,7 +104,7 @@
LOCAL_MODULE := android.test.runner.stubs
LOCAL_JAVA_LIBRARIES := \
- legacy.test.stubs \
+ android.test.base.stubs \
android.test.mock.stubs \
LOCAL_SOURCE_FILES_ALL_GENERATED := true
diff --git a/tests/net/java/com/android/internal/util/RingBufferTest.java b/tests/net/java/com/android/internal/util/RingBufferTest.java
index 7a2344317..90a373a 100644
--- a/tests/net/java/com/android/internal/util/RingBufferTest.java
+++ b/tests/net/java/com/android/internal/util/RingBufferTest.java
@@ -17,6 +17,7 @@
package com.android.internal.util;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.support.test.filters.SmallTest;
@@ -129,6 +130,55 @@
assertArraysEqual(expected2, buffer.toArray());
}
+ @Test
+ public void testGetNextSlot() {
+ int capacity = 100;
+ RingBuffer<DummyClass1> buffer = new RingBuffer<>(DummyClass1.class, capacity);
+
+ final DummyClass1[] actual = new DummyClass1[capacity];
+ final DummyClass1[] expected = new DummyClass1[capacity];
+ for (int i = 0; i < capacity; ++i) {
+ final DummyClass1 obj = buffer.getNextSlot();
+ obj.x = capacity * i;
+ actual[i] = obj;
+ expected[i] = new DummyClass1();
+ expected[i].x = capacity * i;
+ }
+ assertArraysEqual(expected, buffer.toArray());
+
+ for (int i = 0; i < capacity; ++i) {
+ if (actual[i] != buffer.getNextSlot()) {
+ fail("getNextSlot() should re-use objects if available");
+ }
+ }
+
+ RingBuffer<DummyClass2> buffer2 = new RingBuffer<>(DummyClass2.class, capacity);
+ assertNull("getNextSlot() should return null if the object can't be initiated "
+ + "(No nullary constructor)", buffer2.getNextSlot());
+
+ RingBuffer<DummyClass3> buffer3 = new RingBuffer<>(DummyClass3.class, capacity);
+ assertNull("getNextSlot() should return null if the object can't be initiated "
+ + "(Inaccessible class)", buffer3.getNextSlot());
+ }
+
+ public static final class DummyClass1 {
+ int x;
+
+ public boolean equals(Object o) {
+ if (o instanceof DummyClass1) {
+ final DummyClass1 other = (DummyClass1) o;
+ return other.x == this.x;
+ }
+ return false;
+ }
+ }
+
+ public static final class DummyClass2 {
+ public DummyClass2(int x) {}
+ }
+
+ private static final class DummyClass3 {}
+
static <T> void assertArraysEqual(T[] expected, T[] got) {
if (expected.length != got.length) {
fail(Arrays.toString(expected) + " and " + Arrays.toString(got)
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index a9e1e9d..abbb82a 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -21,6 +21,7 @@
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.IProvisioningCallback;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
@@ -184,5 +185,7 @@
void restoreBackupData(in byte[] data);
void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
+
+ void startSubscriptionProvisioning(in OsuProvider provider, in IProvisioningCallback callback);
}
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 183cf0d..558004c 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -33,6 +33,8 @@
import android.net.NetworkRequest;
import android.net.wifi.hotspot2.OsuProvider;
import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.ProvisioningCallback;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -3610,4 +3612,45 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Start subscription provisioning flow
+ * @param provider {@link OsuProvider} to provision with
+ * @param callback {@link ProvisioningCallback} for updates regarding provisioning flow
+ * @hide
+ */
+ public void startSubscriptionProvisioning(OsuProvider provider, ProvisioningCallback callback,
+ @Nullable Handler handler) {
+ Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
+ try {
+ mService.startSubscriptionProvisioning(provider,
+ new ProvisioningCallbackProxy(looper, callback));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private static class ProvisioningCallbackProxy extends IProvisioningCallback.Stub {
+ private final Handler mHandler;
+ private final ProvisioningCallback mCallback;
+
+ ProvisioningCallbackProxy(Looper looper, ProvisioningCallback callback) {
+ mHandler = new Handler(looper);
+ mCallback = callback;
+ }
+
+ @Override
+ public void onProvisioningStatus(int status) {
+ mHandler.post(() -> {
+ mCallback.onProvisioningStatus(status);
+ });
+ }
+
+ @Override
+ public void onProvisioningFailure(int status) {
+ mHandler.post(() -> {
+ mCallback.onProvisioningFailure(status);
+ });
+ }
+ }
}
diff --git a/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl
new file mode 100644
index 0000000..c2cb16a
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/IProvisioningCallback.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2;
+
+/**
+ * Interface for Provisioning callback.
+ *
+ * @hide
+ */
+oneway interface IProvisioningCallback
+{
+ /**
+ * Service to manager callback providing failure notification
+ */
+ void onProvisioningFailure(int status);
+
+ /**
+ * Service to manager callback providing Provisioning status
+ */
+ void onProvisioningStatus(int status);
+}
+
diff --git a/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
new file mode 100644
index 0000000..8b86cdd
--- /dev/null
+++ b/wifi/java/android/net/wifi/hotspot2/ProvisioningCallback.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.hotspot2;
+
+import android.os.Handler;
+
+/**
+ * Base class for provisioning callbacks. Should be extended by applications and set when calling
+ * {@link WifiManager#startSubscriptionProvisiong(OsuProvider, ProvisioningCallback, Handler)}.
+ *
+ * @hide
+ */
+public abstract class ProvisioningCallback {
+
+ /**
+ * The reason code for Provisioning Failure due to connection failure to OSU AP.
+ * @hide
+ */
+ public static final int OSU_FAILURE_AP_CONNECTION = 1;
+
+ /**
+ * The status code for Provisioning flow to indicate connecting to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_AP_CONNECTING = 1;
+
+ /**
+ * The status code for Provisioning flow to indicate connected to OSU AP
+ * @hide
+ */
+ public static final int OSU_STATUS_AP_CONNECTED = 2;
+
+ /**
+ * Provisioning status for OSU failure
+ * @param status indicates error condition
+ */
+ public abstract void onProvisioningFailure(int status);
+
+ /**
+ * Provisioning status when OSU is in progress
+ * @param status indicates status of OSU flow
+ */
+ public abstract void onProvisioningStatus(int status);
+}
+