Merge "AOD padding animation should match slice" into pi-dev
diff --git a/api/current.txt b/api/current.txt
index 8703bf4..52b4741c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -41426,6 +41426,7 @@
method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
method public void cancelMissedCallsNotification();
method public android.content.Intent createManageBlockedNumbersIntent();
+ method public boolean endCall();
method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
method public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
method public java.lang.String getDefaultDialerPackage();
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index e6272ed..50ed18d 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -366,7 +366,7 @@
sp<AlarmMonitor> periodicAlarmMonitor;
sp<StatsLogProcessor> processor = new StatsLogProcessor(
uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec * NS_PER_SEC,
- [](const ConfigKey&){});
+ [](const ConfigKey&){return true;});
processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
return processor;
}
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index ed07acc..4d27948 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -75,7 +75,7 @@
const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& periodicAlarmMonitor,
const int64_t timeBaseNs,
- const std::function<void(const ConfigKey&)>& sendBroadcast)
+ const std::function<bool(const ConfigKey&)>& sendBroadcast)
: mUidMap(uidMap),
mAnomalyAlarmMonitor(anomalyAlarmMonitor),
mPeriodicAlarmMonitor(periodicAlarmMonitor),
@@ -465,12 +465,21 @@
// We suspect that the byteSize() computation is expensive, so we set a rate limit.
size_t totalBytes = metricsManager.byteSize();
mLastByteSizeTimes[key] = timestampNs;
+ bool requestDump = false;
if (totalBytes >
StatsdStats::kMaxMetricsBytesPerConfig) { // Too late. We need to start clearing data.
metricsManager.dropData(timestampNs);
StatsdStats::getInstance().noteDataDropped(key);
VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
- } else if (totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) {
+ } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
+ (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) {
+ // Request to send a broadcast if:
+ // 1. in memory data > threshold OR
+ // 2. config has old data report on disk.
+ requestDump = true;
+ }
+
+ if (requestDump) {
// Send broadcast so that receivers can pull data.
auto lastBroadcastTime = mLastBroadcastTimes.find(key);
if (lastBroadcastTime != mLastBroadcastTimes.end()) {
@@ -479,10 +488,12 @@
return;
}
}
- mLastBroadcastTimes[key] = timestampNs;
- VLOG("StatsD requesting broadcast for %s", key.ToString().c_str());
- mSendBroadcast(key);
- StatsdStats::getInstance().noteBroadcastSent(key);
+ if (mSendBroadcast(key)) {
+ mOnDiskDataConfigs.erase(key);
+ VLOG("StatsD triggered data fetch for %s", key.ToString().c_str());
+ mLastBroadcastTimes[key] = timestampNs;
+ StatsdStats::getInstance().noteBroadcastSent(key);
+ }
}
}
@@ -505,6 +516,8 @@
return;
}
proto.flush(fd.get());
+ // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP.
+ mOnDiskDataConfigs.insert(key);
}
void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason) {
@@ -533,6 +546,11 @@
}
}
+void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ mOnDiskDataConfigs.insert(key);
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 8de0f41..d6fb8de 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -48,7 +48,7 @@
StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
const int64_t timeBaseNs,
- const std::function<void(const ConfigKey&)>& sendBroadcast);
+ const std::function<bool(const ConfigKey&)>& sendBroadcast);
virtual ~StatsLogProcessor();
void OnLogEvent(LogEvent* event, bool reconnectionStarts);
@@ -99,6 +99,9 @@
#endif
}
+ // Add a specific config key to the possible configs to dump ASAP.
+ void noteOnDiskData(const ConfigKey& key);
+
private:
// For testing only.
inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
@@ -118,6 +121,9 @@
// Tracks when we last checked the bytes consumed for each config key.
std::unordered_map<ConfigKey, long> mLastByteSizeTimes;
+ // Tracks which config keys has metric reports on disk
+ std::set<ConfigKey> mOnDiskDataConfigs;
+
sp<UidMap> mUidMap; // Reference to the UidMap to lookup app name and version for each uid.
StatsPullerManager mStatsPullerManager;
@@ -159,7 +165,7 @@
// Function used to send a broadcast so that receiver for the config key can call getData
// to retrieve the stored data.
- std::function<void(const ConfigKey& key)> mSendBroadcast;
+ std::function<bool(const ConfigKey& key)> mSendBroadcast;
const int64_t mTimeBaseNs;
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index e823f68..acf3ad2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -158,11 +158,14 @@
auto receiver = mConfigManager->GetConfigReceiver(key);
if (sc == nullptr) {
VLOG("Could not find StatsCompanionService");
+ return false;
} else if (receiver == nullptr) {
VLOG("Statscompanion could not find a broadcast receiver for %s",
key.ToString().c_str());
+ return false;
} else {
sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+ return true;
}
}
);
@@ -948,6 +951,11 @@
IPCThreadState* ipc = IPCThreadState::self();
ConfigKey configKey(ipc->getCallingUid(), key);
mConfigManager->SetConfigReceiver(configKey, intentSender);
+ if (StorageManager::hasConfigMetricsReport(configKey)) {
+ VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk",
+ configKey.ToString().c_str());
+ mProcessor->noteOnDiskData(configKey);
+ }
return Status::ok();
}
@@ -988,6 +996,15 @@
return Status::ok();
}
+Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) {
+ // Permission check not necessary as it's meant for applications to write to
+ // statsd.
+ android::util::stats_write(util::APP_BREADCRUMB_REPORTED,
+ IPCThreadState::self()->getCallingUid(), label,
+ state);
+ return Status::ok();
+}
+
void StatsService::binderDied(const wp <IBinder>& who) {
ALOGW("statscompanion service died");
StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 67fc770..b3a4776 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -139,6 +139,11 @@
/** Inform statsCompanion that statsd is ready. */
virtual void sayHiToStatsCompanion();
+ /**
+ * Binder call to get AppBreadcrumbReported atom.
+ */
+ virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
+
/** IBinder::DeathRecipient */
virtual void binderDied(const wp<IBinder>& who) override;
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index bf0f720..2d14b05 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -62,7 +62,7 @@
: mConfigKey(key), mUidMap(uidMap),
mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1),
mTtlEndNs(-1),
- mLastReportTimeNs(timeBaseNs),
+ mLastReportTimeNs(currentTimeNs),
mLastReportWallClockNs(getWallClockNs()) {
// Init the ttl end timestamp.
refreshTtl(timeBaseNs);
@@ -247,16 +247,6 @@
return;
}
- // Label is 2nd from last field and must be from [0, 15].
- long appHookLabel = event.GetLong(event.size()-1, &err);
- if (err != NO_ERROR ) {
- VLOG("APP_BREADCRUMB_REPORTED had error when parsing the label field");
- return;
- } else if (appHookLabel < 0 || appHookLabel > 15) {
- VLOG("APP_BREADCRUMB_REPORTED does not have valid label %ld", appHookLabel);
- return;
- }
-
// The state must be from 0,3. This part of code must be manually updated.
long appHookState = event.GetLong(event.size(), &err);
if (err != NO_ERROR ) {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 170d6a7..4f39df9e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -79,7 +79,8 @@
}
};
- // Returns the elapsed realtime when this metric manager last reported metrics.
+ // Returns the elapsed realtime when this metric manager last reported metrics. If this config
+ // has not yet dumped any reports, this is the time the metricsmanager was initialized.
inline int64_t getLastReportTimeNs() const {
return mLastReportTimeNs;
};
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index ea8da14..1f81812 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -160,6 +160,34 @@
}
}
+bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) {
+ unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
+ if (dir == NULL) {
+ VLOG("Path %s does not exist", STATS_DATA_DIR);
+ return false;
+ }
+
+ string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
+
+ dirent* de;
+ while ((de = readdir(dir.get()))) {
+ char* name = de->d_name;
+ if (name[0] == '.') continue;
+
+ size_t nameLen = strlen(name);
+ size_t suffixLen = suffix.length();
+ if (suffixLen <= nameLen &&
+ strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
+ // Check again that the file name is parseable.
+ int64_t result[3];
+ parseFileName(name, result);
+ if (result[0] == -1) continue;
+ return true;
+ }
+ }
+ return false;
+}
+
void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto) {
unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
if (dir == NULL) {
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index 8953be9..4840f3c 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -63,6 +63,11 @@
const std::function<void(const ConfigKey&)>& sendBroadcast);
/**
+ * Returns true if there's at least one report on disk.
+ */
+ static bool hasConfigMetricsReport(const ConfigKey& key);
+
+ /**
* Appends ConfigMetricsReport found on disk to the specific proto and
* delete it.
*/
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 9fdf7a3..de6e6e5 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -62,7 +62,7 @@
sp<AlarmMonitor> periodicAlarmMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
StatsLogProcessor p(m, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
- [](const ConfigKey& key) {});
+ [](const ConfigKey& key) {return true;});
MockMetricsManager mockMetricsManager;
@@ -81,7 +81,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
MockMetricsManager mockMetricsManager;
@@ -107,7 +107,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
MockMetricsManager mockMetricsManager;
@@ -131,7 +131,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
ConfigKey key(3, 4);
StatsdConfig config;
config.add_allowed_log_source("AID_ROOT");
@@ -156,7 +156,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
ConfigKey key(3, 4);
StatsdConfig config;
auto annotation = config.add_annotation();
@@ -185,7 +185,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
int broadcastCount = 0;
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+ [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
LogEvent event1(0, 1 /*logd timestamp*/, 1001 /*elapsedRealtime*/);
event1.init();
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index dde50c2..e23131d 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -44,7 +44,7 @@
sp<AlarmMonitor> subscriberAlarmMonitor;
// Construct the processor with a dummy sendBroadcast function that does nothing.
StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
- [](const ConfigKey& key) {});
+ [](const ConfigKey& key) {return true;});
LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
addEvent.write(100); // parent UID
addEvent.write(101); // isolated UID
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 5903993..e0c98cb 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -459,7 +459,7 @@
new AlarmMonitor(1, [](const sp<IStatsCompanionService>&, int64_t){},
[](const sp<IStatsCompanionService>&){});
sp<StatsLogProcessor> processor = new StatsLogProcessor(
- uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){});
+ uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){return true;});
processor->OnConfigUpdated(currentTimeNs, key, config);
return processor;
}
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 29d7a21..1418012 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -571,8 +571,8 @@
Landroid/bluetooth/BluetoothHeadset;->disconnectAudio()Z
Landroid/bluetooth/BluetoothHeadset;->getActiveDevice()Landroid/bluetooth/BluetoothDevice;
Landroid/bluetooth/BluetoothHeadset;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z
-Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
-Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall()Z
+Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall()Z
Landroid/bluetooth/BluetoothHearingAid;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String;
Landroid/bluetooth/BluetoothHearingAid;->getActiveDevices()Ljava/util/List;
Landroid/bluetooth/BluetoothHearingAid;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index a68f485..0c91a20 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,7 @@
package android.bluetooth;
+import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
@@ -633,8 +634,9 @@
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param device Bluetooth headset
- * @return false if there is no headset connected of if the connected headset doesn't support
- * voice recognition or on error, true otherwise
+ * @return false if there is no headset connected, or the connected headset doesn't support
+ * voice recognition, or voice recognition is already started, or audio channel is occupied,
+ * or on error, true otherwise
*/
public boolean startVoiceRecognition(BluetoothDevice device) {
if (DBG) log("startVoiceRecognition()");
@@ -654,10 +656,15 @@
* Stop Bluetooth Voice Recognition mode, and shut down the
* Bluetooth audio path.
*
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * If this function returns true, this intent will be broadcasted with
+ * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+ *
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
* @param device Bluetooth headset
- * @return false if there is no headset connected or on error, true otherwise
+ * @return false if there is no headset connected, or voice recognition has not started,
+ * or voice recognition has ended on this headset, or on error, true otherwise
*/
public boolean stopVoiceRecognition(BluetoothDevice device) {
if (DBG) log("stopVoiceRecognition()");
@@ -798,11 +805,12 @@
}
/**
- * Check if Bluetooth SCO audio is connected.
+ * Check if at least one headset's SCO audio is connected or connecting
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
*
- * @return true if SCO is connected, false otherwise or on error
+ * @return true if at least one device's SCO audio is connected or connecting, false otherwise
+ * or on error
* @hide
*/
public boolean isAudioOn() {
@@ -821,11 +829,21 @@
}
/**
- * Initiates a connection of headset audio.
- * It setup SCO channel with remote connected headset device.
+ * Initiates a connection of headset audio to the current active device
*
- * @return true if successful false if there was some error such as there is no connected
- * headset
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * If this function returns true, this intent will be broadcasted with
+ * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+ *
+ * <p> {@link #EXTRA_STATE} will transition from
+ * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+ * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+ * in case of failure to establish the audio connection.
+ *
+ * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
+ * before calling this method
+ *
+ * @return false if there was some error such as there is no active headset
* @hide
*/
public boolean connectAudio() {
@@ -844,11 +862,14 @@
}
/**
- * Initiates a disconnection of headset audio.
- * It tears down the SCO channel from remote headset device.
+ * Initiates a disconnection of HFP SCO audio.
+ * Tear down voice recognition or virtual voice call if any.
*
- * @return true if successful false if there was some error such as there is no connected SCO
- * channel
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * If this function returns true, this intent will be broadcasted with
+ * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+ *
+ * @return false if audio is not connected, or on error, true otherwise
* @hide
*/
public boolean disconnectAudio() {
@@ -867,22 +888,33 @@
}
/**
- * Initiates a SCO channel connection with the headset (if connected).
- * Also initiates a virtual voice call for Handsfree devices as many devices
- * do not accept SCO audio without a call.
- * This API allows the handsfree device to be used for routing non-cellular
- * call audio.
+ * Initiates a SCO channel connection as a virtual voice call to the current active device
+ * Active handsfree device will be notified of incoming call and connected call.
*
- * @param device Remote Bluetooth Device
- * @return true if successful, false if there was some error.
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * If this function returns true, this intent will be broadcasted with
+ * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+ *
+ * <p> {@link #EXTRA_STATE} will transition from
+ * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+ * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+ * in case of failure to establish the audio connection.
+ *
+ * @return true if successful, false if one of the following case applies
+ * - SCO audio is not idle (connecting or connected)
+ * - virtual call has already started
+ * - there is no active device
+ * - a Telecom managed call is going on
+ * - binder is dead or Bluetooth is disabled or other error
* @hide
*/
- public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean startScoUsingVirtualVoiceCall() {
if (DBG) log("startScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ if (service != null && isEnabled()) {
try {
- return service.startScoUsingVirtualVoiceCall(device);
+ return service.startScoUsingVirtualVoiceCall();
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
@@ -894,19 +926,24 @@
}
/**
- * Terminates an ongoing SCO connection and the associated virtual
- * call.
+ * Terminates an ongoing SCO connection and the associated virtual call.
*
- * @param device Remote Bluetooth Device
- * @return true if successful, false if there was some error.
+ * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+ * If this function returns true, this intent will be broadcasted with
+ * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+ *
+ * @return true if successful, false if one of the following case applies
+ * - virtual voice call is not started or has ended
+ * - binder is dead or Bluetooth is disabled or other error
* @hide
*/
- public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean stopScoUsingVirtualVoiceCall() {
if (DBG) log("stopScoUsingVirtualVoiceCall()");
final IBluetoothHeadset service = mService;
- if (service != null && isEnabled() && isValidDevice(device)) {
+ if (service != null && isEnabled()) {
try {
- return service.stopScoUsingVirtualVoiceCall(device);
+ return service.stopScoUsingVirtualVoiceCall();
} catch (RemoteException e) {
Log.e(TAG, e.toString());
}
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 8c256be..124f207 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -152,4 +152,10 @@
* Requires Manifest.permission.DUMP.
*/
void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
+
+ /**
+ * Apps can send an atom via this application breadcrumb with the specified label and state for
+ * this label. This allows building custom metrics and predicates.
+ */
+ void sendAppBreadcrumbAtom(int label, int state);
}
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index e8b4197..e3de307 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -16,59 +16,101 @@
package android.util;
-import android.os.Process;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
/**
* StatsLog provides an API for developers to send events to statsd. The events can be used to
* define custom metrics inside statsd.
*/
public final class StatsLog extends StatsLogInternal {
- private static final String TAG = "StatsManager";
+ private static final String TAG = "StatsLog";
+ private static final boolean DEBUG = false;
+
+ private static IStatsManager sService;
private StatsLog() {}
/**
* Logs a start event.
*
- * @param label developer-chosen label that is from [0, 16).
+ * @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logStart(int label) {
- if (label >= 0 && label < 16) {
- StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(),
- label, APP_BREADCRUMB_REPORTED__STATE__START);
- return true;
+ synchronized (StatsLog.class) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging start");
+ return false;
+ }
+ service.sendAppBreadcrumbAtom(label,
+ StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+ return true;
+ } catch (RemoteException e) {
+ sService = null;
+ if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging start");
+ return false;
+ }
}
- return false;
}
/**
* Logs a stop event.
*
- * @param label developer-chosen label that is from [0, 16).
+ * @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logStop(int label) {
- if (label >= 0 && label < 16) {
- StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(),
- label, APP_BREADCRUMB_REPORTED__STATE__STOP);
- return true;
+ synchronized (StatsLog.class) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging stop");
+ return false;
+ }
+ service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+ return true;
+ } catch (RemoteException e) {
+ sService = null;
+ if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging stop");
+ return false;
+ }
}
- return false;
}
/**
* Logs an event that does not represent a start or stop boundary.
*
- * @param label developer-chosen label that is from [0, 16).
+ * @param label developer-chosen label.
* @return True if the log request was sent to statsd.
*/
public static boolean logEvent(int label) {
- if (label >= 0 && label < 16) {
- StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(), label,
- APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
- return true;
+ synchronized (StatsLog.class) {
+ try {
+ IStatsManager service = getIStatsManagerLocked();
+ if (service == null) {
+ if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging event");
+ return false;
+ }
+ service.sendAppBreadcrumbAtom(
+ label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+ return true;
+ } catch (RemoteException e) {
+ sService = null;
+ if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging event");
+ return false;
+ }
}
- return false;
+ }
+
+ private static IStatsManager getIStatsManagerLocked() throws RemoteException {
+ if (sService != null) {
+ return sService;
+ }
+ sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+ return sService;
}
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 88300db..4c7dc11 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1132,8 +1132,7 @@
if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
commitLocked();
- mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
- mContext.getPackageName());
+ mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED));
}
}
}
@@ -1893,14 +1892,19 @@
}
}
- final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
- .setPackageName(mContext.getPackageName())
+ mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
- mMetricsLogger.write(log);
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied));
}
}
+ private LogMaker newLog(int category) {
+ return new LogMaker(category)
+ .setPackageName(mContext.getPackageName())
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE,
+ isCompatibilityModeEnabledLocked() ? 1 : 0);
+ }
+
/**
* Set the tracked views.
*
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 15b2718..b9a8864 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -147,7 +147,7 @@
setAvatar(sender.getIcon());
}
mAvatarView.setVisibility(VISIBLE);
- mSenderName.setVisibility(VISIBLE);
+ mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE);
mTextColor = getNormalTextColor();
mSendingTextColor = calculateSendingTextColor();
}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index af9aae3..538ea11 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -436,10 +436,29 @@
}
private void updateHistoricMessageVisibility() {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
+ int numHistoric = mHistoricMessages.size();
+ for (int i = 0; i < numHistoric; i++) {
MessagingMessage existing = mHistoricMessages.get(i);
existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
}
+ int numGroups = mGroups.size();
+ for (int i = 0; i < numGroups; i++) {
+ MessagingGroup group = mGroups.get(i);
+ int visibleChildren = 0;
+ List<MessagingMessage> messages = group.getMessages();
+ int numGroupMessages = messages.size();
+ for (int j = 0; j < numGroupMessages; j++) {
+ MessagingMessage message = messages.get(j);
+ if (message.getVisibility() != GONE) {
+ visibleChildren++;
+ }
+ }
+ if (visibleChildren > 0 && group.getVisibility() == GONE) {
+ group.setVisibility(VISIBLE);
+ } else if (visibleChildren == 0 && group.getVisibility() != GONE) {
+ group.setVisibility(GONE);
+ }
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index d2b670f..ffcb503 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -145,4 +145,6 @@
MessagingMessageState getState();
void setVisibility(int visibility);
+
+ int getVisibility();
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 79bb534..270527d5 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -297,6 +297,26 @@
GLuint mTexture = 0;
};
+static bool isFP16Supported(const sk_sp<GrContext>& grContext) {
+ static std::once_flag sOnceFlag;
+ static bool supported = false;
+
+ std::call_once(sOnceFlag, [](const sk_sp<GrContext>& grContext) {
+ if (!grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
+ supported = false;
+ return;
+ }
+
+ sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16,
+ GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
+ GraphicBuffer::USAGE_SW_READ_NEVER, "tempFp16Buffer");
+ status_t error = buffer->initCheck();
+ supported = !error;
+ }, grContext);
+
+ return supported;
+}
+
sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
SkBitmap& skBitmap) {
renderThread.eglManager().initialize();
@@ -318,7 +338,7 @@
type = GL_UNSIGNED_BYTE;
break;
case kRGBA_F16_SkColorType:
- isSupported = grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig);
+ isSupported = isFP16Supported(grContext);
if (isSupported) {
type = GL_HALF_FLOAT;
pixelFormat = PIXEL_FORMAT_RGBA_FP16;
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index fe89b89..b9731d1 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -414,7 +414,9 @@
* with {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}.
* Note that only focus changes (gains and losses) affecting the focus owner are reported,
* not gains and losses of other focus requesters in the system.<br>
- * Notifications are delivered on the main {@link Looper}.
+ * Notifications are delivered on the {@link Looper} associated with the one of
+ * the creation of the {@link AudioManager} used to request focus
+ * (see {@link AudioManager#requestAudioFocus(AudioFocusRequest)}).
* @param listener the listener receiving the focus change notifications.
* @return this {@code Builder} instance.
* @throws NullPointerException thrown when a null focus listener is used.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 68463e1..566db94 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4558,8 +4558,7 @@
/**
* The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
*/
- private ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>
- mDeviceCallbacks =
+ private final ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> mDeviceCallbacks =
new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>();
/**
@@ -4859,22 +4858,21 @@
calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
AudioDeviceInfo[] removed_devices =
calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
-
if (added_devices.length != 0 || removed_devices.length != 0) {
synchronized (mDeviceCallbacks) {
for (int i = 0; i < mDeviceCallbacks.size(); i++) {
handler = mDeviceCallbacks.valueAt(i).getHandler();
if (handler != null) {
- if (added_devices.length != 0) {
- handler.sendMessage(Message.obtain(handler,
- MSG_DEVICES_DEVICES_ADDED,
- added_devices));
- }
if (removed_devices.length != 0) {
handler.sendMessage(Message.obtain(handler,
MSG_DEVICES_DEVICES_REMOVED,
removed_devices));
}
+ if (added_devices.length != 0) {
+ handler.sendMessage(Message.obtain(handler,
+ MSG_DEVICES_DEVICES_ADDED,
+ added_devices));
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index b98f27e..f14def1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -185,6 +185,7 @@
@VisibleForTesting
protected void bind(final Condition condition, final View row, final int rowId) {
if (condition == null) throw new IllegalArgumentException("condition must not be null");
+
final boolean enabled = condition.state == Condition.STATE_TRUE;
final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
new ConditionTag();
@@ -207,7 +208,6 @@
MetricsLogger.action(mContext,
MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
updateAlarmWarningText(tag.condition);
- announceConditionSelection(tag);
}
}
});
@@ -328,6 +328,7 @@
boolean enabled, int rowId, Uri conditionId) {
if (tag.lines == null) {
tag.lines = row.findViewById(android.R.id.content);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
}
if (tag.line1 == null) {
tag.line1 = (TextView) row.findViewById(android.R.id.text1);
@@ -450,16 +451,6 @@
bind(newCondition, row, rowId);
updateAlarmWarningText(tag.condition);
tag.rb.setChecked(true);
- announceConditionSelection(tag);
- }
-
- private void announceConditionSelection(ConditionTag tag) {
- // condition will always be priority-only
- String modeText = mContext.getString(R.string.zen_interruption_level_priority);
- if (tag.line1 != null) {
- mZenRadioGroupContent.announceForAccessibility(mContext.getString(
- R.string.zen_mode_and_condition, modeText, tag.line1.getText()));
- }
}
private void updateAlarmWarningText(Condition condition) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
index 7369ba8..adf49aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
@@ -203,8 +203,14 @@
private void setupUi(ConditionTag tag, View row) {
- tag.lines = row.findViewById(android.R.id.content);
- tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+ if (tag.lines == null) {
+ tag.lines = row.findViewById(android.R.id.content);
+ tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+ }
+
+ if (tag.line1 == null) {
+ tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+ }
// text2 is not used in zen duration dialog
row.findViewById(android.R.id.text2).setVisibility(View.GONE);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d53f1a0..88edd12 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -208,6 +208,9 @@
<!-- to read and change hvac values in a car -->
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
+ <!-- Permission necessary to change car audio volume through CarAudioManager -->
+ <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+
<application
android:name=".SystemUIApplication"
android:persistent="true"
@@ -419,7 +422,8 @@
android:theme="@style/Theme.AlertDialogHost"
android:finishOnCloseSystemDialogs="true"
android:launchMode="singleTop"
- android:excludeFromRecents="true" />
+ android:excludeFromRecents="true"
+ android:visibleToInstantApps="true"/>
<!-- started from PipUI -->
<activity
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 9fdb00e..11bd98f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -49,6 +49,7 @@
<EditText android:id="@+id/passwordEntry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:contentDescription="@string/keyguard_accessibility_password"
android:gravity="center_horizontal"
android:singleLine="true"
android:textStyle="normal"
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index ed63089..513d848 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -120,6 +120,9 @@
<!-- Accessibility description of the PIN password view. [CHAR_LIMIT=none] -->
<string name="keyguard_accessibility_pin_area">PIN area</string>
+ <!-- Accessibility description of the normal password view. [CHAR_LIMIT=none] -->
+ <string name="keyguard_accessibility_password">Device password</string>
+
<!-- Accessibility description of the SIM PIN password view. [CHAR_LIMIT=none] -->
<string name="keyguard_accessibility_sim_pin_area">SIM PIN area</string>
<!-- Accessibility description of the SIM PUK password view. [CHAR_LIMIT=none] -->
diff --git a/packages/SystemUI/res/drawable/car_ic_notification_2.xml b/packages/SystemUI/res/drawable/car_ic_notification_2.xml
deleted file mode 100644
index c74ae15..0000000
--- a/packages/SystemUI/res/drawable/car_ic_notification_2.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="32dp"
- android:height="38dp"
- android:viewportWidth="32"
- android:viewportHeight="38" >
- <group
- android:translateX="-6"
- android:translateY="-3">
- <path
- android:pathData="M26.6195649,6.98115478 C31.5083629,8.85235985 34.9817444,13.6069337 34.9817444,19.1767606 L34.9817444,27.9542254 L38,27.9542254 L38,34.2161972 L6,34.2161972 L6,27.9542254 L9.01825558,27.9542254 L9.01825558,19.1767606 C9.01825558,13.6069337 12.4916371,8.85235985 17.3804351,6.98115478 C17.723241,4.726863 19.6609451,3 22,3 C24.3390549,3 26.276759,4.726863 26.6195649,6.98115478 Z M17.326572,36.3035211 L26.673428,36.3035211 C26.673428,38.8973148 24.581063,41 22,41 C19.418937,41 17.326572,38.8973148 17.326572,36.3035211 Z"
- android:strokeColor="#00000000"
- android:fillType="evenOdd"
- android:fillColor="@color/car_grey_50" />
- </group>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml
index 99d2425..41e0786 100644
--- a/packages/SystemUI/res/values/attrs_car.xml
+++ b/packages/SystemUI/res/values/attrs_car.xml
@@ -63,4 +63,32 @@
<attr name="hvacPropertyId" format="integer"/>
<attr name="hvacTempFormat" format="string"/>
</declare-styleable>
+
+ <declare-styleable name="carVolumeItems"/>
+ <declare-styleable name="carVolumeItems_item">
+ <!-- Align with AudioAttributes.USAGE_* -->
+ <attr name="usage">
+ <enum name="unknown" value="0"/>
+ <enum name="media" value="1"/>
+ <enum name="voice_communication" value="2"/>
+ <enum name="voice_communication_signalling" value="3"/>
+ <enum name="alarm" value="4"/>
+ <enum name="notification" value="5"/>
+ <enum name="notification_ringtone" value="6"/>
+ <enum name="notification_communication_request" value="7"/>
+ <enum name="notification_communication_instant" value="8"/>
+ <enum name="notification_communication_delayed" value="9"/>
+ <enum name="notification_event" value="10"/>
+ <enum name="assistance_accessibility" value="11"/>
+ <enum name="assistance_navigation_guidance" value="12"/>
+ <enum name="assistance_sonification" value="13"/>
+ <enum name="game" value="14"/>
+ <!-- hidden, do not use -->
+ <!-- enum name="virtual_source" value="15"/ -->
+ <enum name="assistant" value="16"/>
+ </attr>
+
+ <!-- Icon resource ids to render on UI -->
+ <attr name="icon" format="reference"/>
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3a9b934..95ccc3c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1363,6 +1363,13 @@
<string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
<string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
+ <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
+ <string name="volume_ringer_hint_mute">mute</string>
+ <!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] -->
+ <string name="volume_ringer_hint_unmute">unmute</string>
+ <!-- Hint for accessibility. For example: double tap to vibrate [CHAR_LIMIT=NONE] -->
+ <string name="volume_ringer_hint_vibrate">vibrate</string>
+
<string name="volume_dialog_title">%s volume controls</string>
<string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
diff --git a/packages/SystemUI/res/xml/car_volume_items.xml b/packages/SystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 0000000..742dfdd
--- /dev/null
+++ b/packages/SystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+
+<!--
+ Defines all possible items on car volume settings UI, keyed by usage.
+
+ This enables the CarSettings UI to associate VolumeGroups surfaced by
+ CarAudioManager.getVolumeGroupCount with renderable assets (ie: title, icon)
+ for presentation.
+
+ Order matters in this configuration. If one volume group contains multiple
+ audio usages, the first one appears in this file would be picked to be
+ presented on UI.
+
+ When overriding this configuration, please consult also the
+ car_volume_groups.xml, which is read by car audio service.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+ <item car:usage="unknown"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="media"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="voice_communication"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="voice_communication_signalling"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="alarm"
+ car:icon="@*android:drawable/ic_audio_alarm"/>
+ <item car:usage="notification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_ringtone"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="notification_communication_request"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_instant"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_delayed"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_event"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_accessibility"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_navigation_guidance"
+ car:icon="@drawable/car_ic_navigation"/>
+ <item car:usage="assistance_sonification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="game"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="assistant"
+ car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 30a17a1..f066e34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -57,6 +57,7 @@
import androidx.slice.Slice;
import androidx.slice.SliceItem;
+import androidx.slice.SliceManager;
import androidx.slice.core.SliceQuery;
import androidx.slice.widget.ListContent;
import androidx.slice.widget.RowContent;
@@ -390,6 +391,11 @@
}
}
+ public void refresh() {
+ Slice slice = SliceManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
+ onChanged(slice);
+ }
+
public static class Row extends LinearLayout {
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 454528e..f6b5d69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -287,7 +287,12 @@
}
}
- public void refreshTime() {
+ public void dozeTimeTick() {
+ refreshTime();
+ mKeyguardSlice.refresh();
+ }
+
+ private void refreshTime() {
mClockView.refresh();
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index f867b34..c5e66f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -79,7 +79,6 @@
private DateFormat mDateFormat;
private String mLastText;
private boolean mRegistered;
- private boolean mRegisteredEveryMinute;
private String mNextAlarm;
private NextAlarmController mNextAlarmController;
protected AlarmManager mAlarmManager;
@@ -175,7 +174,7 @@
mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
mZenModeController.addCallback(this);
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
- registerClockUpdate(false /* everyMinute */);
+ registerClockUpdate();
updateClock();
return true;
}
@@ -214,22 +213,13 @@
/**
* Registers a broadcast receiver for clock updates, include date, time zone and manually
* changing the date/time via the settings app.
- *
- * @param everyMinute {@code true} if you also want updates every minute.
*/
- protected void registerClockUpdate(boolean everyMinute) {
+ private void registerClockUpdate() {
if (mRegistered) {
- if (mRegisteredEveryMinute == everyMinute) {
- return;
- } else {
- unregisterClockUpdate();
- }
+ return;
}
IntentFilter filter = new IntentFilter();
- if (everyMinute) {
- filter.addAction(Intent.ACTION_TIME_TICK);
- }
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
@@ -237,15 +227,6 @@
getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
null /* scheduler */);
mRegistered = true;
- mRegisteredEveryMinute = everyMinute;
- }
-
- protected void unregisterClockUpdate() {
- if (!mRegistered) {
- return;
- }
- getContext().unregisterReceiver(mIntentReceiver);
- mRegistered = false;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 5748ec9b..19980a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -211,6 +211,11 @@
//TODO: May not be needed. Mobile is always expected to be visible (not a dot)
}
+ @Override
+ public int getVisibleState() {
+ return 0;
+ }
+
@VisibleForTesting
public MobileIconState getState() {
return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index c3b4fdd..ca00a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -146,6 +146,11 @@
}
}
+ @Override
+ public int getVisibleState() {
+ return mVisibleState;
+ }
+
private void init() {
int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 6383816..b831b86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -23,6 +23,7 @@
void setStaticDrawableColor(int color);
void setDecorColor(int color);
void setVisibleState(int state);
+ int getVisibleState();
boolean isIconVisible();
default boolean isIconBlocked() {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
index 3ed8cce..3975ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
@@ -37,10 +37,10 @@
private final NotificationDozeHelper mDozer;
private final ViewGroup mParent;
- private final float mOverflowNumberSizeDark;
- private final int mOverflowNumberPaddingDark;
- private final float mOverflowNumberSize;
- private final int mOverflowNumberPadding;
+ private float mOverflowNumberSizeDark;
+ private int mOverflowNumberPaddingDark;
+ private float mOverflowNumberSize;
+ private int mOverflowNumberPadding;
private int mOverflowNumberColor;
private int mOverflowNumberColorDark;
@@ -50,7 +50,10 @@
mContext = ctx;
mParent = parent;
mDozer = new NotificationDozeHelper();
+ initDimens();
+ }
+ public void initDimens() {
Resources res = mContext.getResources();
mOverflowNumberSize = res.getDimensionPixelSize(
R.dimen.group_overflow_number_size);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 9fcb090..ee83250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -212,7 +212,7 @@
}
public void hideClock(boolean animate) {
- animateHide(mClockView, animate);
+ animateHiddenState(mClockView, View.GONE, animate);
}
public void showClock(boolean animate) {
@@ -240,21 +240,29 @@
}
/**
- * Hides a view.
+ * Animate a view to INVISIBLE or GONE
*/
- private void animateHide(final View v, boolean animate) {
+ private void animateHiddenState(final View v, int state, boolean animate) {
v.animate().cancel();
if (!animate) {
v.setAlpha(0f);
- v.setVisibility(View.INVISIBLE);
+ v.setVisibility(state);
return;
}
+
v.animate()
.alpha(0f)
.setDuration(160)
.setStartDelay(0)
.setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(() -> v.setVisibility(View.INVISIBLE));
+ .withEndAction(() -> v.setVisibility(state));
+ }
+
+ /**
+ * Hides a view.
+ */
+ private void animateHide(final View v, boolean animate) {
+ animateHiddenState(v, View.INVISIBLE, animate);
}
/**
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 b650944..b475b64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2267,7 +2267,7 @@
}
public void onScreenTurningOn() {
- mKeyguardStatusView.refreshTime();
+ mKeyguardStatusView.dozeTimeTick();
}
@Override
@@ -2690,7 +2690,7 @@
}
public void dozeTimeTick() {
- mKeyguardStatusView.refreshTime();
+ mKeyguardStatusView.dozeTimeTick();
mKeyguardBottomArea.dozeTimeTick();
if (mDarkAmount > 0) {
positionClockAndNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 8398879..4538977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -30,12 +30,12 @@
import android.util.Log;
import android.view.View;
-import android.view.ViewGroup;
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.stack.AnimationFilter;
+import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ViewState;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
/**
@@ -50,8 +50,8 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_OVERFLOW = false;
// Max 5 status icons including battery
- private static final int MAX_ICONS = 4;
- private static final int MAX_DOTS = 3;
+ private static final int MAX_ICONS = 7;
+ private static final int MAX_DOTS = 1;
private int mDotPadding;
private int mStaticDotDiameter;
@@ -97,7 +97,7 @@
mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
mStaticDotDiameter = 2 * radius;
- mUnderflowWidth = mIconDotFrameWidth + 2 * (mStaticDotDiameter + mDotPadding);
+ mUnderflowWidth = mIconDotFrameWidth + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
}
@Override
@@ -193,6 +193,7 @@
public void onViewAdded(View child) {
super.onViewAdded(child);
StatusIconState vs = new StatusIconState();
+ vs.justAdded = true;
child.setTag(R.id.status_bar_view_state_tag, vs);
}
@@ -212,7 +213,6 @@
float contentStart = getPaddingStart();
int childCount = getChildCount();
// Underflow === don't show content until that index
- int firstUnderflowIndex = -1;
if (DEBUG) android.util.Log.d(TAG, "calculateIconTranslations: start=" + translationX
+ " width=" + width + " underflow=" + mNeedsUnderflow);
@@ -235,13 +235,13 @@
translationX -= getViewTotalWidth(child);
}
- // Show either 1-4 dots, or 3 dots + overflow
+ // Show either 1-MAX_ICONS icons, or (MAX_ICONS - 1) icons + overflow
int totalVisible = mLayoutStates.size();
int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
mUnderflowStart = 0;
int visible = 0;
- firstUnderflowIndex = -1;
+ int firstUnderflowIndex = -1;
for (int i = totalVisible - 1; i >= 0; i--) {
StatusIconState state = mLayoutStates.get(i);
// Allow room for underflow if we found we need it in onMeasure
@@ -324,14 +324,52 @@
public static class StatusIconState extends ViewState {
/// StatusBarIconView.STATE_*
public int visibleState = STATE_ICON;
+ public boolean justAdded = true;
@Override
public void applyToView(View view) {
- if (view instanceof StatusIconDisplayable) {
- StatusIconDisplayable icon = (StatusIconDisplayable) view;
- icon.setVisibleState(visibleState);
+ if (!(view instanceof StatusIconDisplayable)) {
+ return;
}
- super.applyToView(view);
+ StatusIconDisplayable icon = (StatusIconDisplayable) view;
+ AnimationProperties animationProperties = null;
+ boolean animate = false;
+
+ if (justAdded) {
+ super.applyToView(view);
+ animationProperties = ADD_ICON_PROPERTIES;
+ animate = true;
+ } else if (icon.getVisibleState() != visibleState) {
+ animationProperties = DOT_ANIMATION_PROPERTIES;
+ animate = true;
+ }
+
+ icon.setVisibleState(visibleState);
+ if (animate) {
+ animateTo(view, animationProperties);
+ } else {
+ super.applyToView(view);
+ }
+
+ justAdded = false;
}
}
+
+ private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(200).setDelay(50);
+
+ private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(200);
}
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 e5ab712..8115297 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -121,8 +121,8 @@
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- initDimens();
mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ initDimens();
setClipChildren(false);
}
@@ -148,6 +148,7 @@
mTranslationForHeader = res.getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin)
- mNotificationHeaderMargin;
+ mHybridGroupManager.initDimens();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index f1a7183..8034345 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -19,23 +19,34 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorSet;
+import android.annotation.DrawableRes;
import android.annotation.Nullable;
import android.app.Dialog;
import android.app.KeyguardManager;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.car.media.ICarVolumeCallback;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.ServiceConnection;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.AudioSystem;
+import android.media.AudioAttributes;
import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.util.AttributeSet;
import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.util.SparseArray;
+import android.util.Xml;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -53,626 +64,533 @@
import androidx.car.widget.PagedListView;
import androidx.car.widget.SeekbarListItem;
+import java.util.Iterator;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.VolumeDialog;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.plugins.VolumeDialogController.State;
-import com.android.systemui.plugins.VolumeDialogController.StreamState;
/**
* Car version of the volume dialog.
*
- * A client of VolumeDialogControllerImpl and its state model.
- *
* Methods ending in "H" must be called on the (ui) handler.
*/
public class CarVolumeDialogImpl implements VolumeDialog {
- private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
+ private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
- private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+ private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
+ private static final String XML_TAG_VOLUME_ITEM = "item";
+ private static final int HOVERING_TIMEOUT = 16000;
+ private static final int NORMAL_TIMEOUT = 3000;
+ private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
+ private static final int DISMISS_DELAY_IN_MILLIS = 50;
+ private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
- private final Context mContext;
- private final H mHandler = new H();
- private final VolumeDialogController mController;
- private final AudioManager mAudioManager;
+ private final Context mContext;
+ private final H mHandler = new H();
- private Window mWindow;
- private CustomDialog mDialog;
- private PagedListView mListView;
- private ListItemAdapter mPagedListAdapter;
- private final List<ListItem> mVolumeLineItems = new ArrayList<>();
- private final List<VolumeRow> mRows = new ArrayList<>();
- private ConfigurableTexts mConfigurableTexts;
- private final SparseBooleanArray mDynamic = new SparseBooleanArray();
- private final KeyguardManager mKeyguard;
- private final Object mSafetyWarningLock = new Object();
+ private Window mWindow;
+ private CustomDialog mDialog;
+ private PagedListView mListView;
+ private ListItemAdapter mPagedListAdapter;
+ // All the volume items.
+ private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
+ // Available volume items in car audio manager.
+ private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>();
+ // Volume items in the PagedListView.
+ private final List<ListItem> mVolumeLineItems = new ArrayList<>();
+ private final KeyguardManager mKeyguard;
- private boolean mShowing;
+ private Car mCar;
+ private CarAudioManager mCarAudioManager;
- private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
- private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
- private State mState;
- private SafetyWarningDialog mSafetyWarning;
- private boolean mHovering = false;
- private boolean mExpanded;
+ private boolean mHovering;
+ private boolean mShowing;
+ private boolean mExpanded;
- public CarVolumeDialogImpl(Context context) {
- mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
- mController = Dependency.get(VolumeDialogController.class);
- mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ public CarVolumeDialogImpl(Context context) {
+ mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
+ mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ mCar = Car.createCar(mContext, mServiceConnection);
+ }
+
+ public void init(int windowType, Callback callback) {
+ initDialog();
+
+ mCar.connect();
+ }
+
+ @Override
+ public void destroy() {
+ mHandler.removeCallbacksAndMessages(null);
+
+ cleanupAudioManager();
+ // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
+ // audio manager beforehand.
+ mCar.disconnect();
+ }
+
+ private void initDialog() {
+ loadAudioUsageItems();
+ mVolumeLineItems.clear();
+ mDialog = new CustomDialog(mContext);
+
+ mHovering = false;
+ mShowing = false;
+ mExpanded = false;
+ mWindow = mDialog.getWindow();
+ mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+ mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
+ final WindowManager.LayoutParams lp = mWindow.getAttributes();
+ lp.format = PixelFormat.TRANSLUCENT;
+ lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+ lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+ lp.windowAnimations = -1;
+ mWindow.setAttributes(lp);
+ mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ mDialog.setCanceledOnTouchOutside(true);
+ mDialog.setContentView(R.layout.car_volume_dialog);
+ mDialog.setOnShowListener(dialog -> {
+ mListView.setTranslationY(-mListView.getHeight());
+ mListView.setAlpha(0);
+ mListView.animate()
+ .alpha(1)
+ .translationY(0)
+ .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+ .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+ .start();
+ });
+ mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
+ mListView.setOnHoverListener((v, event) -> {
+ int action = event.getActionMasked();
+ mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+ || (action == MotionEvent.ACTION_HOVER_MOVE);
+ rescheduleTimeoutH();
+ return true;
+ });
+
+ mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
+ BackgroundStyle.PANEL);
+ mListView.setAdapter(mPagedListAdapter);
+ mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+ }
+
+ public void show(int reason) {
+ mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+ }
+
+ public void dismiss(int reason) {
+ mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+ }
+
+ private void showH(int reason) {
+ if (D.BUG) {
+ Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
}
- public void init(int windowType, Callback callback) {
- initDialog();
+ mHandler.removeMessages(H.SHOW);
+ mHandler.removeMessages(H.DISMISS);
+ rescheduleTimeoutH();
+ // Refresh the data set before showing.
+ mPagedListAdapter.notifyDataSetChanged();
+ if (mShowing) {
+ return;
+ }
+ mShowing = true;
- mController.addCallback(mControllerCallbackH, mHandler);
- mController.getState();
+ mDialog.show();
+ Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+ }
+
+ protected void rescheduleTimeoutH() {
+ mHandler.removeMessages(H.DISMISS);
+ final int timeout = computeTimeoutH();
+ mHandler.sendMessageDelayed(mHandler
+ .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+
+ if (D.BUG) {
+ Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+ }
+ }
+
+ private int computeTimeoutH() {
+ return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
+ }
+
+ protected void dismissH(int reason) {
+ if (D.BUG) {
+ Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]);
+ }
+
+ mHandler.removeMessages(H.DISMISS);
+ mHandler.removeMessages(H.SHOW);
+ if (!mShowing) {
+ return;
+ }
+
+ mListView.animate().cancel();
+ mShowing = false;
+
+ mListView.setTranslationY(0);
+ mListView.setAlpha(1);
+ mListView.animate()
+ .alpha(0)
+ .translationY(-mListView.getHeight())
+ .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .withEndAction(() -> mHandler.postDelayed(() -> {
+ if (D.BUG) {
+ Log.d(TAG, "mDialog.dismiss()");
+ }
+ mDialog.dismiss();
+ }, DISMISS_DELAY_IN_MILLIS))
+ .start();
+
+ Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
+ }
+
+ public void dump(PrintWriter writer) {
+ writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
+ writer.print(" mShowing: "); writer.println(mShowing);
+ }
+
+ private void loadAudioUsageItems() {
+ try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) {
+ AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ // Traverse to the first start tag
+ while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+ && type != XmlResourceParser.START_TAG) {
+ }
+
+ if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
+ throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
+ }
+ int outerDepth = parser.getDepth();
+ int rank = 0;
+ while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+ && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlResourceParser.END_TAG) {
+ continue;
+ }
+ if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
+ TypedArray item = mContext.getResources().obtainAttributes(
+ attrs, R.styleable.carVolumeItems_item);
+ int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
+ if (usage >= 0) {
+ VolumeItem volumeItem = new VolumeItem();
+ volumeItem.usage = usage;
+ volumeItem.rank = rank;
+ volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon, 0);
+ mVolumeItems.put(usage, volumeItem);
+ rank++;
+ }
+ item.recycle();
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Error parsing volume groups configuration", e);
+ }
+ }
+
+ private VolumeItem getVolumeItemForUsages(int[] usages) {
+ int rank = Integer.MAX_VALUE;
+ VolumeItem result = null;
+ for (int usage : usages) {
+ VolumeItem volumeItem = mVolumeItems.get(usage);
+ if (volumeItem.rank < rank) {
+ rank = volumeItem.rank;
+ result = volumeItem;
+ }
+ }
+ return result;
+ }
+
+ private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+ try {
+ return carAudioManager.getGroupVolume(volumeGroupId);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
+ return 0;
+ }
+
+ private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+ try {
+ return carAudioManager.getGroupMaxVolume(volumeGroupId);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
+ return 0;
+ }
+
+ private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem, int volumeGroupId,
+ int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener) {
+ SeekbarListItem listItem = new SeekbarListItem(mContext);
+ listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
+ int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
+ listItem.setProgress(progress);
+ listItem.setOnSeekBarChangeListener(
+ new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
+ listItem.setPrimaryActionIcon(mContext.getResources().getDrawable(volumeItem.icon));
+ if (supplementalIconId != 0) {
+ Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
+ listItem.setSupplementalIcon(supplementalIcon, true,
+ supplementalIconOnClickListener);
+ } else {
+ listItem.setSupplementalEmptyIcon(true);
+ }
+
+ mVolumeLineItems.add(listItem);
+ volumeItem.listItem = listItem;
+ volumeItem.progress = progress;
+ return listItem;
+ }
+
+ private VolumeItem findVolumeItem(SeekbarListItem targetItem) {
+ for (int i = 0; i < mVolumeItems.size(); ++i) {
+ VolumeItem volumeItem = mVolumeItems.valueAt(i);
+ if (volumeItem.listItem == targetItem) {
+ return volumeItem;
+ }
+ }
+ return null;
+ }
+
+ private void cleanupAudioManager() {
+ try {
+ mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
+ mVolumeLineItems.clear();
+ mCarAudioManager = null;
+ }
+
+ private final class H extends Handler {
+ private static final int SHOW = 1;
+ private static final int DISMISS = 2;
+
+ public H() {
+ super(Looper.getMainLooper());
}
@Override
- public void destroy() {
- mController.removeCallback(mControllerCallbackH);
- mHandler.removeCallbacksAndMessages(null);
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SHOW:
+ showH(msg.arg1);
+ break;
+ case DISMISS:
+ dismissH(msg.arg1);
+ break;
+ default:
+ }
+ }
+ }
+
+ private final class CustomDialog extends Dialog implements DialogInterface {
+ public CustomDialog(Context context) {
+ super(context, com.android.systemui.R.style.qs_theme);
}
- private void initDialog() {
- mRows.clear();
- mVolumeLineItems.clear();
- mDialog = new CustomDialog(mContext);
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ rescheduleTimeoutH();
+ return super.dispatchTouchEvent(ev);
+ }
- mConfigurableTexts = new ConfigurableTexts(mContext);
- mHovering = false;
- mShowing = false;
+ @Override
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (isShowing()) {
+ if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+ dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private final class ExpandIconListener implements View.OnClickListener {
+ @Override
+ public void onClick(final View v) {
+ mExpanded = !mExpanded;
+ Animator inAnimator;
+ if (mExpanded) {
+ for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
+ // Adding the items which are not coming from the default item.
+ VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+ if (volumeItem.defaultItem) {
+ // Set progress here due to the progress of seekbar may not be updated.
+ volumeItem.listItem.setProgress(volumeItem.progress);
+ } else {
+ addSeekbarListItem(volumeItem, groupId, 0, null);
+ }
+ }
+ inAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_in_rotate_up);
+ } else {
+ // Only keeping the default stream if it is not expended.
+ Iterator itr = mVolumeLineItems.iterator();
+ while (itr.hasNext()) {
+ SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
+ VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+ if (!volumeItem.defaultItem) {
+ itr.remove();
+ } else {
+ // Set progress here due to the progress of seekbar may not be updated.
+ seekbarListItem.setProgress(volumeItem.progress);
+ }
+ }
+ inAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_in_rotate_down);
+ }
+
+ Animator outAnimator = AnimatorInflater.loadAnimator(
+ mContext, R.anim.car_arrow_fade_out);
+ inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
+ AnimatorSet animators = new AnimatorSet();
+ animators.playTogether(outAnimator, inAnimator);
+ animators.setTarget(v);
+ animators.start();
+ mPagedListAdapter.notifyDataSetChanged();
+ }
+ }
+
+ private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+ private final int mVolumeGroupId;
+ private final CarAudioManager mCarAudioManager;
+
+ private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) {
+ mVolumeGroupId = volumeGroupId;
+ mCarAudioManager = carAudioManager;
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ // For instance, if this event is originated from AudioService,
+ // we can ignore it as it has already been handled and doesn't need to be
+ // sent back down again.
+ return;
+ }
+ try {
+ if (mCarAudioManager == null) {
+ Log.w(TAG, "Ignoring volume change event because the car isn't connected");
+ return;
+ }
+ mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
+ mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {}
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {}
+ }
+
+ private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
+ @Override
+ public void onGroupVolumeChanged(int groupId) {
+ VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+ int value = getSeekbarValue(mCarAudioManager, groupId);
+ // Do not update the progress if it is the same as before. When car audio manager sets its
+ // group volume caused by the seekbar progress changed, it also triggers this callback.
+ // Updating the seekbar at the same time could block the continuous seeking.
+ if (value != volumeItem.progress) {
+ volumeItem.listItem.setProgress(value);
+ volumeItem.progress = value;
+ show(Events.SHOW_REASON_VOLUME_CHANGED);
+ }
+ }
+
+ @Override
+ public void onMasterMuteChanged() {
+ // ignored
+ }
+ };
+
+ private final ServiceConnection mServiceConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ try {
mExpanded = false;
- mWindow = mDialog.getWindow();
- mWindow.requestFeature(Window.FEATURE_NO_TITLE);
- mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
- | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
- mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
- final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.windowAnimations = -1;
- mWindow.setAttributes(lp);
- mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+ int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+ // Populates volume slider items from volume groups to UI.
+ for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+ VolumeItem volumeItem = getVolumeItemForUsages(
+ mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+ mAvailableVolumeItems.add(volumeItem);
+ // The first one is the default item.
+ if (groupId == 0) {
+ volumeItem.defaultItem = true;
+ addSeekbarListItem(volumeItem, groupId, R.drawable.car_ic_keyboard_arrow_down,
+ new ExpandIconListener());
+ }
+ }
- mDialog.setCanceledOnTouchOutside(true);
- mDialog.setContentView(R.layout.car_volume_dialog);
- mDialog.setOnShowListener(dialog -> {
- mListView.setTranslationY(-mListView.getHeight());
- mListView.setAlpha(0);
- mListView.animate()
- .alpha(1)
- .translationY(0)
- .setDuration(300)
- .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
- .start();
- });
- mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
- mListView.setOnHoverListener((v, event) -> {
- int action = event.getActionMasked();
- mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
- || (action == MotionEvent.ACTION_HOVER_MOVE);
- rescheduleTimeoutH();
- return true;
- });
-
- addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music,
- R.drawable.car_ic_keyboard_arrow_down, true, true),
- new ExpandIconListener());
- // We map AudioManager.STREAM_RING to a navigation icon for demo.
- addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0,
- true, false);
- addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0,
- true, false);
-
- mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
- BackgroundStyle.PANEL);
- mListView.setAdapter(mPagedListAdapter);
- mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+ // If list is already initiated, update its content.
+ if (mPagedListAdapter != null) {
+ mPagedListAdapter.notifyDataSetChanged();
+ }
+ mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Car is not connected!", e);
+ }
}
- public void setStreamImportant(int stream, boolean important) {
- mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+ /**
+ * This does not get called when service is properly disconnected.
+ * So we need to also handle cleanups in destroy().
+ */
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ cleanupAudioManager();
}
+ };
- public void setAutomute(boolean automute) {
- if (mAutomute == automute) {
- return;
- }
- mAutomute = automute;
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- }
-
- public void setSilentMode(boolean silentMode) {
- if (mSilentMode == silentMode) {
- return;
- }
- mSilentMode = silentMode;
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- }
-
- private VolumeRow addVolumeRow(int stream, int primaryActionIcon, int supplementalIcon,
- boolean important, boolean defaultStream) {
- VolumeRow volumeRow = new VolumeRow();
- volumeRow.stream = stream;
- volumeRow.primaryActionIcon = primaryActionIcon;
- volumeRow.supplementalIcon = supplementalIcon;
- volumeRow.important = important;
- volumeRow.defaultStream = defaultStream;
- volumeRow.listItem = null;
- mRows.add(volumeRow);
- return volumeRow;
- }
-
- private SeekbarListItem addSeekbarListItem(
- VolumeRow volumeRow, @Nullable View.OnClickListener supplementalIconOnClickListener) {
- int volumeMax = mAudioManager.getStreamMaxVolume(volumeRow.stream);
- int currentVolume = mAudioManager.getStreamVolume(volumeRow.stream);
- SeekbarListItem listItem =
- new SeekbarListItem(mContext, volumeMax, currentVolume,
- new VolumeSeekBarChangeListener(volumeRow), null);
- Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon);
- listItem.setPrimaryActionIcon(primaryIcon);
- if (volumeRow.supplementalIcon != 0) {
- Drawable supplementalIcon = mContext.getResources()
- .getDrawable(volumeRow.supplementalIcon);
- listItem.setSupplementalIcon(supplementalIcon, true,
- supplementalIconOnClickListener);
- } else {
- listItem.setSupplementalEmptyIcon(true);
- }
-
- mVolumeLineItems.add(listItem);
- volumeRow.listItem = listItem;
-
- return listItem;
- }
-
- private static int getImpliedLevel(SeekBar seekBar, int progress) {
- final int m = seekBar.getMax();
- final int n = m / 100 - 1;
- final int level = progress == 0 ? 0
- : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
- return level;
- }
-
- public void show(int reason) {
- mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
- }
-
- public void dismiss(int reason) {
- mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
- }
-
- private void showH(int reason) {
- if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
- mHandler.removeMessages(H.SHOW);
- mHandler.removeMessages(H.DISMISS);
- rescheduleTimeoutH();
- if (mShowing) return;
- mShowing = true;
-
- mDialog.show();
- Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
- mController.notifyVisible(true);
- }
-
- protected void rescheduleTimeoutH() {
- mHandler.removeMessages(H.DISMISS);
- final int timeout = computeTimeoutH();
- mHandler.sendMessageDelayed(mHandler
- .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
- if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
- mController.userActivity();
- }
-
- private int computeTimeoutH() {
- if (mHovering) return 16000;
- if (mSafetyWarning != null) return 5000;
- return 3000;
- }
-
- protected void dismissH(int reason) {
- mHandler.removeMessages(H.DISMISS);
- mHandler.removeMessages(H.SHOW);
- if (!mShowing) return;
- mListView.animate().cancel();
- mShowing = false;
-
- mListView.setTranslationY(0);
- mListView.setAlpha(1);
- mListView.animate()
- .alpha(0)
- .translationY(-mListView.getHeight())
- .setDuration(250)
- .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
- .withEndAction(() -> mHandler.postDelayed(() -> {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- }, 50))
- .start();
-
- Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
- mController.notifyVisible(false);
- synchronized (mSafetyWarningLock) {
- if (mSafetyWarning != null) {
- if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
- mSafetyWarning.dismiss();
- }
- }
- }
-
- private void trimObsoleteH() {
- int initialVolumeItemSize = mVolumeLineItems.size();
- for (int i = mRows.size() - 1; i >= 0; i--) {
- final VolumeRow row = mRows.get(i);
- if (row.ss == null || !row.ss.dynamic) continue;
- if (!mDynamic.get(row.stream)) {
- mRows.remove(i);
- mVolumeLineItems.remove(row.listItem);
- }
- }
-
- if (mVolumeLineItems.size() != initialVolumeItemSize) {
- mPagedListAdapter.notifyDataSetChanged();
- }
- }
-
- private void onStateChangedH(State state) {
- mState = state;
- mDynamic.clear();
- // add any new dynamic rows
- for (int i = 0; i < state.states.size(); i++) {
- final int stream = state.states.keyAt(i);
- final StreamState ss = state.states.valueAt(i);
- if (!ss.dynamic) {
- continue;
- }
- mDynamic.put(stream, true);
- if (findRow(stream) == null) {
- VolumeRow row = addVolumeRow(stream, R.drawable.ic_volume_remote,
- 0, true,false);
- if (mExpanded) {
- addSeekbarListItem(row, null);
- }
- }
- }
-
- for (VolumeRow row : mRows) {
- updateVolumeRowH(row);
- }
- }
-
- private void updateVolumeRowH(VolumeRow row) {
- if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
- if (mState == null) {
- return;
- }
- final StreamState ss = mState.states.get(row.stream);
- if (ss == null) {
- return;
- }
- row.ss = ss;
- if (ss.level == row.requestedLevel) {
- row.requestedLevel = -1;
- }
- // TODO: update Seekbar progress and change the mute icon if necessary.
- }
-
- private VolumeRow findRow(int stream) {
- for (VolumeRow row : mRows) {
- if (row.stream == stream) {
- return row;
- }
- }
- return null;
- }
-
- private VolumeRow findRow(SeekbarListItem targetItem) {
- for (VolumeRow row : mRows) {
- if (row.listItem == targetItem) {
- return row;
- }
- }
- return null;
- }
-
- public void dump(PrintWriter writer) {
- writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
- writer.print(" mShowing: "); writer.println(mShowing);
- writer.print(" mDynamic: "); writer.println(mDynamic);
- writer.print(" mAutomute: "); writer.println(mAutomute);
- writer.print(" mSilentMode: "); writer.println(mSilentMode);
- }
-
- private void recheckH(VolumeRow row) {
- if (row == null) {
- if (D.BUG) Log.d(TAG, "recheckH ALL");
- trimObsoleteH();
- for (VolumeRow r : mRows) {
- updateVolumeRowH(r);
- }
- } else {
- if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
- updateVolumeRowH(row);
- }
- }
-
- private void setStreamImportantH(int stream, boolean important) {
- for (VolumeRow row : mRows) {
- if (row.stream == stream) {
- row.important = important;
- return;
- }
- }
- }
-
- private void showSafetyWarningH(int flags) {
- if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
- || mShowing) {
- synchronized (mSafetyWarningLock) {
- if (mSafetyWarning != null) {
- return;
- }
- mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
- @Override
- protected void cleanUp() {
- synchronized (mSafetyWarningLock) {
- mSafetyWarning = null;
- }
- recheckH(null);
- }
- };
- mSafetyWarning.show();
- }
- recheckH(null);
- }
- rescheduleTimeoutH();
- }
-
- private final VolumeDialogController.Callbacks mControllerCallbackH
- = new VolumeDialogController.Callbacks() {
- @Override
- public void onShowRequested(int reason) {
- showH(reason);
- }
-
- @Override
- public void onDismissRequested(int reason) {
- dismissH(reason);
- }
-
- @Override
- public void onScreenOff() {
- dismissH(Events.DISMISS_REASON_SCREEN_OFF);
- }
-
- @Override
- public void onStateChanged(State state) {
- onStateChangedH(state);
- }
-
- @Override
- public void onLayoutDirectionChanged(int layoutDirection) {
- mListView.setLayoutDirection(layoutDirection);
- }
-
- @Override
- public void onConfigurationChanged() {
- mDialog.dismiss();
- initDialog();
- mConfigurableTexts.update();
- }
-
- @Override
- public void onShowVibrateHint() {
- if (mSilentMode) {
- mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
- }
- }
-
- @Override
- public void onShowSilentHint() {
- if (mSilentMode) {
- mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
- }
- }
-
- @Override
- public void onShowSafetyWarning(int flags) {
- showSafetyWarningH(flags);
- }
-
- @Override
- public void onAccessibilityModeChanged(Boolean showA11yStream) {
- }
- };
-
- private final class H extends Handler {
- private static final int SHOW = 1;
- private static final int DISMISS = 2;
- private static final int RECHECK = 3;
- private static final int RECHECK_ALL = 4;
- private static final int SET_STREAM_IMPORTANT = 5;
- private static final int RESCHEDULE_TIMEOUT = 6;
- private static final int STATE_CHANGED = 7;
-
- public H() {
- super(Looper.getMainLooper());
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case SHOW: showH(msg.arg1); break;
- case DISMISS: dismissH(msg.arg1); break;
- case RECHECK: recheckH((VolumeRow) msg.obj); break;
- case RECHECK_ALL: recheckH(null); break;
- case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
- case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
- case STATE_CHANGED: onStateChangedH(mState); break;
- }
- }
- }
-
- private final class CustomDialog extends Dialog implements DialogInterface {
- public CustomDialog(Context context) {
- super(context, com.android.systemui.R.style.qs_theme);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- rescheduleTimeoutH();
- return super.dispatchTouchEvent(ev);
- }
-
- @Override
- protected void onStart() {
- super.setCanceledOnTouchOutside(true);
- super.onStart();
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isShowing()) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
- return true;
- }
- }
- return false;
- }
- }
-
- private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
- private final VolumeRow mRow;
-
- private VolumeSeekBarChangeListener(VolumeRow volumeRow) {
- mRow = volumeRow;
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- if (mRow.ss == null) {
- return;
- }
- if (D.BUG) {
- Log.d(TAG, AudioSystem.streamToString(mRow.stream)
- + " onProgressChanged " + progress + " fromUser=" + fromUser);
- }
- if (!fromUser) {
- return;
- }
- if (mRow.ss.levelMin > 0) {
- final int minProgress = mRow.ss.levelMin;
- if (progress < minProgress) {
- seekBar.setProgress(minProgress);
- progress = minProgress;
- }
- }
- final int userLevel = getImpliedLevel(seekBar, progress);
- if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
- if (mRow.requestedLevel != userLevel) {
- mController.setStreamVolume(mRow.stream, userLevel);
- mRow.requestedLevel = userLevel;
- Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
- userLevel);
- }
- }
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- if (D.BUG) {
- Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
- }
- mController.setActiveStream(mRow.stream);
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- if (D.BUG) {
- Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
- }
- final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
- Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
- if (mRow.ss.level != userLevel) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
- USER_ATTEMPT_GRACE_PERIOD);
- }
- }
- }
-
- private final class ExpandIconListener implements View.OnClickListener {
- @Override
- public void onClick(final View v) {
- mExpanded = !mExpanded;
- Animator inAnimator;
- if (mExpanded) {
- for (VolumeRow row : mRows) {
- // Adding the items which are not coming from default stream.
- if (!row.defaultStream) {
- addSeekbarListItem(row, null);
- }
- }
- inAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_in_rotate_up);
- } else {
- // Only keeping the default stream if it is not expended.
- Iterator itr = mVolumeLineItems.iterator();
- while (itr.hasNext()) {
- SeekbarListItem item = (SeekbarListItem) itr.next();
- VolumeRow row = findRow(item);
- if (!row.defaultStream) {
- itr.remove();
- }
- }
- inAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_in_rotate_down);
- }
-
- Animator outAnimator = AnimatorInflater.loadAnimator(
- mContext, R.anim.car_arrow_fade_out);
- inAnimator.setStartDelay(100);
- AnimatorSet animators = new AnimatorSet();
- animators.playTogether(outAnimator, inAnimator);
- animators.setTarget(v);
- animators.start();
- mPagedListAdapter.notifyDataSetChanged();
- }
- }
-
- private static class VolumeRow {
- private int stream;
- private StreamState ss;
- private boolean important;
- private boolean defaultStream;
- private int primaryActionIcon;
- private int supplementalIcon;
- private SeekbarListItem listItem;
- private int requestedLevel = -1; // pending user-requested level via progress changed
- }
-}
\ No newline at end of file
+ /**
+ * Wrapper class which contains information of each volume group.
+ */
+ private static class VolumeItem {
+ private @AudioAttributes.AttributeUsage int usage;
+ private int rank;
+ private boolean defaultItem = false;
+ private @DrawableRes int icon;
+ private SeekbarListItem listItem;
+ private int progress;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 6e5b548..dd55264 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -103,11 +103,7 @@
}
private VolumeDialog createCarDefault() {
- CarVolumeDialogImpl impl = new CarVolumeDialogImpl(mContext);
- impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
- impl.setAutomute(true);
- impl.setSilentMode(false);
- return impl;
+ return new CarVolumeDialogImpl(mContext);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b5071de..b0e40fc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -71,6 +71,7 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -627,35 +628,32 @@
switch (mState.ringerModeInternal) {
case AudioManager.RINGER_MODE_VIBRATE:
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+ addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
+ mContext.getString(R.string.volume_ringer_hint_mute));
mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
break;
case AudioManager.RINGER_MODE_SILENT:
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mRingerIcon.setContentDescription(mContext.getString(
- R.string.volume_stream_content_description_unmute,
- getStreamLabelH(ss)));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+ addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
+ mContext.getString(R.string.volume_ringer_hint_unmute));
break;
case AudioManager.RINGER_MODE_NORMAL:
default:
boolean muted = (mAutomute && ss.level == 0) || ss.muted;
if (!isZenMuted && muted) {
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
- mRingerIcon.setContentDescription(mContext.getString(
- R.string.volume_stream_content_description_unmute,
- getStreamLabelH(ss)));
+ addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+ mContext.getString(R.string.volume_ringer_hint_unmute));
mRingerIcon.setTag(Events.ICON_STATE_MUTE);
} else {
mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
if (mController.hasVibrator()) {
- mRingerIcon.setContentDescription(mContext.getString(
- mShowA11yStream
- ? R.string.volume_stream_content_description_vibrate_a11y
- : R.string.volume_stream_content_description_vibrate,
- getStreamLabelH(ss)));
-
+ addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+ mContext.getString(R.string.volume_ringer_hint_vibrate));
} else {
- mRingerIcon.setContentDescription(getStreamLabelH(ss));
+ addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+ mContext.getString(R.string.volume_ringer_hint_mute));
}
mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
}
@@ -664,6 +662,31 @@
}
}
+ private void addAccessibilityDescription(View view, int currState, String hintLabel) {
+ int currStateResId;
+ switch (currState) {
+ case RINGER_MODE_SILENT:
+ currStateResId = R.string.volume_ringer_status_silent;
+ break;
+ case RINGER_MODE_VIBRATE:
+ currStateResId = R.string.volume_ringer_status_vibrate;
+ break;
+ case RINGER_MODE_NORMAL:
+ default:
+ currStateResId = R.string.volume_ringer_status_normal;
+ }
+
+ view.setContentDescription(mContext.getString(currStateResId));
+
+ view.setAccessibilityDelegate(new AccessibilityDelegate() {
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK, hintLabel));
+ }
+ });
+ }
+
/**
* Toggles enable state of views in a VolumeRow (not including seekbar or icon)
* Hides/shows zen icon
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 210764a..17a4fbc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -90,6 +90,17 @@
}
@Test
+ public void refresh_replacesSliceContentAndNotifiesListener() {
+ AtomicBoolean notified = new AtomicBoolean();
+ mKeyguardSliceView.setContentChangeListener((hasHeader)-> {
+ notified.set(true);
+ });
+ mKeyguardSliceView.refresh();
+ Assert.assertTrue("Listener should be notified about slice changes.",
+ notified.get());
+ }
+
+ @Test
public void getTextColor_whiteTextWhenAOD() {
// Set text color to red since the default is white and test would always pass
mKeyguardSliceView.setTextColor(Color.RED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
new file mode 100644
index 0000000..1d8de2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard;
+
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.widget.TextClock;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardStatusViewTest extends SysuiTestCase {
+
+ @Mock
+ KeyguardSliceView mKeyguardSlice;
+ @Mock
+ TextClock mClockView;
+ @InjectMocks
+ KeyguardStatusView mKeyguardStatusView;
+
+ @Before
+ public void setUp() {
+ LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ mKeyguardStatusView =
+ (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null);
+ org.mockito.MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void dozeTimeTick_updatesSlice() {
+ mKeyguardStatusView.dozeTimeTick();
+ verify(mKeyguardSlice).refresh();
+ }
+
+ @Test
+ public void dozeTimeTick_updatesClock() {
+ mKeyguardStatusView.dozeTimeTick();
+ verify(mClockView).refresh();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index a45e690..46e2bfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -85,13 +85,6 @@
}
@Test
- public void unregisterClockUpdate() {
- mProvider.unregisterClockUpdate();
- Assert.assertFalse("Clock updates should have been unregistered.",
- mProvider.isRegistered());
- }
-
- @Test
public void returnsValidSlice() {
Slice slice = mProvider.onBindSlice(mProvider.getUri());
SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 9e8fa22..231cdf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -121,7 +121,7 @@
fragment.initNotificationIconArea(mMockNotificiationAreaController);
fragment.disable(StatusBarManager.DISABLE_CLOCK, 0, false);
- assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+ assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility());
fragment.disable(0, 0, false);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d61f228..1541231 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3967,6 +3967,7 @@
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// NOTE: starting on OS P, it also added the following field:
// Tag FIELD_FLAGS - Flags used to start the session
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SESSION_STARTED = 906;
// An autofill request was processed by a service
@@ -3980,6 +3981,7 @@
// Type TYPE_CLOSE: Service returned a null response.
// Tag FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS: if service requested field classification,
// number of entries field ids in the request.
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_REQUEST = 907;
// Tag of a field for a package of an autofill service
@@ -3998,6 +4000,7 @@
// Tag FIELD_AUTOFILL_NUM_DATASETS: The number of datasets shown
// NOTE: starting on OS P, it also added the following field:
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_FILL_UI = 910;
// Tag of a field for the length of the filter text
@@ -4005,12 +4008,17 @@
// An autofill authentication succeeded
// Package: Package of app that was autofilled
+ // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_AUTHENTICATED = 912;
// An activity was autofilled and all values could be applied
// Package: Package of app that is autofilled
// Tag FIELD_AUTOFILL_NUM_VALUES: Number of values that were suggested to be autofilled
// Tag FIELD_AUTOFILL_NUM_VIEWS_FILLED: Number of views that could be filled
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_DATASET_APPLIED = 913;
// Tag of a field for the number values to be filled in
@@ -4027,6 +4035,7 @@
// Tag FIELD_AUTOFILL_NUM_IDS: The number of ids that are saved
// NOTE: starting on OS P, it also added the following field:
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_UI = 916;
// Tag of a field for the number of saveable ids
@@ -4038,10 +4047,14 @@
// Type TYPE_FAILURE: The request failed
// Package: Package of app that was autofilled
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_DATA_SAVE_REQUEST = 918;
// An auto-fill session was finished
// Package: Package of app that was autofilled
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SESSION_FINISHED = 919;
// meta-event: a reader has checkpointed the log here.
@@ -4167,6 +4180,8 @@
// Package: Real package of the app being autofilled
// Tag FIELD_AUTOFILL_SERVICE: Package of the autofill service that processed the request
// TAG FIELD_AUTOFILL_FORGED_COMPONENT_NAME: Component name being forged
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_FORGED_COMPONENT_ATTEMPT = 948;
// FIELD - The component that an app tried tro forged.
@@ -4624,6 +4639,8 @@
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_PREVIOUS_LENGTH: the previous length of the value
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_VALUE_RESET = 1124;
// Tag of AUTOFILL_VALUE_RESET
@@ -4634,18 +4651,24 @@
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_DATASET_AUTHENTICATED = 1126;
// An autofill service provided an invalid dataset authentication
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
// An autofill service provided an invalid authentication extra
// Package: Package of app that was autofilled
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_INVALID_AUTHENTICATION = 1128;
// An autofill service used a custom description (using RemoteViews) in the autofill save UI
@@ -4653,6 +4676,8 @@
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_CUSTOM_DESCRIPTION = 1129;
// FIELD - Type of save object passed by the service when the Save UI is shown
@@ -4664,6 +4689,8 @@
// OS: O MR
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131;
// User tapped a link in the custom description of the autofill save UI provided by an autofill service
@@ -4674,6 +4701,8 @@
// Type TYPE_FAILURE: The link could not launc an activity
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_LINK_TAPPED = 1132;
// Result of the validation on save when an autofill service provided a validator
@@ -4684,6 +4713,8 @@
// Type TYPE_DISMISS: The validation failed
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_VALIDATION = 1133;
// Result of an operation in the autofill save UI after the user tapped a link in the custom description
@@ -4693,6 +4724,8 @@
// Type TYPE_OPEN: The autofill save UI was restored
// Type TYPE_DISMISS: The autofill save UI was destroyed
// Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134;
// Autofill service called API that disables itself
@@ -4705,6 +4738,8 @@
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Package: Package of the autofill service
// OS: O MR
+ // NOTE: starting on OS P, it also added the following field:
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_UI_LATENCY = 1136;
// Action: the snooze leave-behind was shown after the user clicked the snooze icon
@@ -4872,12 +4907,14 @@
// Package: Package of app that is autofilled
// OS: P
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION = 1228;
// The autofill context was commited when the user clicked a view explicitly marked by the
// service as committing it
// Package: Package of app that is autofilled
// OS: P
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229;
// OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
@@ -4890,6 +4927,7 @@
// OS: P
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SERVICE_DISABLED_APP = 1231;
// An autofill service asked to disable autofill for a given activity.
@@ -4898,6 +4936,7 @@
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
// Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
// ACTION: Stop an app and turn on background check
@@ -5109,6 +5148,7 @@
// OS: P
// Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
// Tag FIELD_AUTOFILL_MATCH_SCORE: Average score of the matches, in the range of 0 to 100
+ // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
AUTOFILL_FIELD_CLASSIFICATION_MATCHES = 1273;
// Tag used to report autofill field classification scores
@@ -5775,6 +5815,9 @@
// OS: P
ACTION_STORAGE_MIGRATE_LATER = 1413;
+ // Tag used to report whether an activity is being autofilled on compatibility mode.
+ FIELD_AUTOFILL_COMPAT_MODE = 1414;
+
// ---- End P Constants, all P constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index e582daa..6ff9539 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -504,7 +504,7 @@
sessionId = sRandom.nextInt();
} while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
- assertCallerLocked(componentName);
+ assertCallerLocked(componentName, compatMode);
final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock,
sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory,
@@ -518,7 +518,7 @@
/**
* Asserts the component is owned by the caller.
*/
- private void assertCallerLocked(@NonNull ComponentName componentName) {
+ private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) {
final String packageName = componentName.getPackageName();
final PackageManager pm = mContext.getPackageManager();
final int callingUid = Binder.getCallingUid();
@@ -536,7 +536,7 @@
+ ") passed component (" + componentName + ") owned by UID " + packageUid);
mMetricsLogger.write(
Helper.newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT,
- callingPackage, getServicePackageName())
+ callingPackage, getServicePackageName(), compatMode)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
componentName == null ? "null" : componentName.flattenToShortString()));
@@ -774,10 +774,10 @@
@Nullable ArrayList<String> changedDatasetIds,
@Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
- @NonNull String appPackageName) {
+ @NonNull String appPackageName, boolean compatMode) {
logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
- manuallyFilledDatasetIds, null, null, appPackageName);
+ manuallyFilledDatasetIds, null, null, appPackageName, compatMode);
}
@GuardedBy("mLock")
@@ -790,7 +790,7 @@
@Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
@Nullable ArrayList<AutofillId> detectedFieldIdsList,
@Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
- @NonNull String appPackageName) {
+ @NonNull String appPackageName, boolean compatMode) {
if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
if (sVerbose) {
Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
@@ -800,7 +800,8 @@
+ ", changedDatasetIds=" + changedDatasetIds
+ ", manuallyFilledFieldIds=" + manuallyFilledFieldIds
+ ", detectedFieldIds=" + detectedFieldIdsList
- + ", detectedFieldClassifications=" + detectedFieldClassificationsList);
+ + ", detectedFieldClassifications=" + detectedFieldClassificationsList
+ + ", compatMode=" + compatMode);
}
AutofillId[] detectedFieldsIds = null;
FieldClassification[] detectedFieldClassifications = null;
@@ -827,7 +828,7 @@
final int averageScore = (int) ((totalScore * 100) / totalSize);
mMetricsLogger.write(Helper
.newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
- appPackageName, getServicePackageName())
+ appPackageName, getServicePackageName(), compatMode)
.setCounterValue(numberFields)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
averageScore));
@@ -1122,7 +1123,7 @@
/**
* Called by {@link Session} when service asked to disable autofill for an app.
*/
- void disableAutofillForApp(@NonNull String packageName, long duration) {
+ void disableAutofillForApp(@NonNull String packageName, long duration, boolean compatMode) {
synchronized (mLock) {
if (mDisabledApps == null) {
mDisabledApps = new ArrayMap<>(1);
@@ -1135,7 +1136,7 @@
mDisabledApps.put(packageName, expiration);
int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
- packageName, getServicePackageName())
+ packageName, getServicePackageName(), compatMode)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
}
}
@@ -1143,7 +1144,8 @@
/**
* Called by {@link Session} when service asked to disable autofill an app.
*/
- void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) {
+ void disableAutofillForActivity(@NonNull ComponentName componentName, long duration,
+ boolean compatMode) {
synchronized (mLock) {
if (mDisabledActivities == null) {
mDisabledActivities = new ArrayMap<>(1);
@@ -1160,7 +1162,8 @@
mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY)
.setComponentName(componentName)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0));
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 78526f5..5372d0c 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -21,7 +21,6 @@
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.metrics.LogMaker;
-import android.os.Bundle;
import android.service.autofill.Dataset;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -35,10 +34,7 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.LinkedList;
-import java.util.Objects;
-import java.util.Set;
public final class Helper {
@@ -119,6 +115,13 @@
return log;
}
+ @NonNull
+ public static LogMaker newLogMaker(int category, String packageName,
+ String servicePackageName, boolean compatMode) {
+ return newLogMaker(category, packageName, servicePackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0);
+ }
+
public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable CharSequence text) {
if (text == null) {
pw.println("null");
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c055060..88a679b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -644,9 +644,10 @@
Slog.d(TAG, message.toString());
}
if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
- mService.disableAutofillForActivity(mComponentName, disableDuration);
+ mService.disableAutofillForActivity(mComponentName, disableDuration, mCompatMode);
} else {
- mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration);
+ mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
+ mCompatMode);
}
sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
}
@@ -1251,7 +1252,7 @@
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
ignoredDatasets, changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
- mComponentName.getPackageName());
+ mComponentName.getPackageName(), mCompatMode);
}
}
@@ -1306,7 +1307,7 @@
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
ignoredDatasets, changedFieldIds, changedDatasetIds,
manuallyFilledFieldIds, manuallyFilledDatasetIds,
- mComponentName.getPackageName());
+ mComponentName.getPackageName(), mCompatMode);
return;
}
final Scores scores = result.getParcelable(EXTRA_SCORES);
@@ -1371,7 +1372,7 @@
mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
- mComponentName.getPackageName());
+ mComponentName.getPackageName(), mCompatMode);
});
fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
@@ -1602,7 +1603,7 @@
getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
mService.getServicePackageName(), saveInfo, this,
mComponentName.getPackageName(), this,
- mPendingSaveUi);
+ mPendingSaveUi, mCompatMode);
if (client != null) {
try {
client.setSaveUiState(id, true);
@@ -2080,7 +2081,7 @@
getUiForShowing().showFillUi(filledId, response, filterText,
mService.getServicePackageName(), mComponentName.getPackageName(),
- mService.getServiceLabel(), mService.getServiceIcon(), this);
+ mService.getServiceLabel(), mService.getServiceIcon(), this, mCompatMode);
synchronized (mLock) {
if (mUiShownTime == 0) {
@@ -2717,7 +2718,8 @@
}
private LogMaker newLogMaker(int category, String servicePackageName) {
- return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName);
+ return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName,
+ mCompatMode);
}
private void writeLog(int category) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index ee18dc2..811d87be 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -170,13 +170,14 @@
public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
@Nullable String filterText, @Nullable String servicePackageName,
@NonNull String packageName, @NonNull CharSequence serviceLabel,
- @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback) {
+ @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, boolean compatMode) {
if (sDebug) {
final int size = filterText == null ? 0 : filterText.length();
Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
}
- final LogMaker log =
- Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+ final LogMaker log = Helper
+ .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName,
+ compatMode)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
filterText == null ? 0 : filterText.length())
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
@@ -262,14 +263,16 @@
public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
@Nullable String servicePackageName, @NonNull SaveInfo info,
@NonNull ValueFinder valueFinder, @NonNull String packageName,
- @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
+ @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi,
+ boolean compatMode) {
if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
int numIds = 0;
numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
- final LogMaker log =
- Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+ final LogMaker log = Helper
+ .newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName,
+ compatMode)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
mHandler.post(() -> {
@@ -319,7 +322,7 @@
}
mMetricsLogger.write(log);
}
- });
+ }, compatMode);
});
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 80903c1..fa2a0d9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -60,6 +60,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -134,6 +135,7 @@
private final PendingUi mPendingUi;
private final String mServicePackageName;
private final String mPackageName;
+ private final boolean mCompatMode;
private boolean mDestroyed;
@@ -141,12 +143,14 @@
@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
@Nullable String servicePackageName, @NonNull String packageName,
@NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
- @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
+ @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener,
+ boolean compatMode) {
mPendingUi= pendingUi;
mListener = new OneTimeListener(listener);
mOverlayControl = overlayControl;
mServicePackageName = servicePackageName;
mPackageName = packageName;
+ mCompatMode = compatMode;
context = new ContextThemeWrapper(context, THEME_ID);
final LayoutInflater inflater = LayoutInflater.from(context);
@@ -409,14 +413,12 @@
}
private LogMaker newLogMaker(int category, int saveType) {
- return newLogMaker(category)
+ return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode)
.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
}
private LogMaker newLogMaker(int category) {
- return new LogMaker(category)
- .setPackageName(mPackageName)
- .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+ return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode);
}
private void writeLog(int category, int saveType) {
@@ -505,6 +507,7 @@
pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
+ pw.print(prefix); pw.print("compat mode: "); pw.println(mCompatMode);
final View view = mDialog.getWindow().getDecorView();
final int[] loc = view.getLocationOnScreen();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2cf1b7..36e5340 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -525,6 +525,8 @@
private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
// SCO audio deactivation request waiting for headset service to connect
private static final int SCO_STATE_DEACTIVATE_REQ = 5;
+ // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+ private static final int SCO_STATE_DEACTIVATING = 6;
// SCO audio state is active due to an action in BT handsfree (either voice recognition or
// in call audio)
@@ -2705,9 +2707,13 @@
}
public void binderDied() {
+ int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) {
Log.w(TAG, "setMode() client died");
+ if (!mSetModeDeathHandlers.isEmpty()) {
+ oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+ }
int index = mSetModeDeathHandlers.indexOf(this);
if (index < 0) {
Log.w(TAG, "unregistered setMode() client died");
@@ -2716,8 +2722,8 @@
}
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
- // SCO connections not started by the application changing the mode
- if (newModeOwnerPid != 0) {
+ // SCO connections not started by the application changing the mode when pid changes
+ if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
final long ident = Binder.clearCallingIdentity();
disconnectBluetoothSco(newModeOwnerPid);
Binder.restoreCallingIdentity(ident);
@@ -2761,17 +2767,21 @@
return;
}
+ int oldModeOwnerPid = 0;
int newModeOwnerPid = 0;
synchronized(mSetModeDeathHandlers) {
+ if (!mSetModeDeathHandlers.isEmpty()) {
+ oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+ }
if (mode == AudioSystem.MODE_CURRENT) {
mode = mMode;
}
newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
}
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
- // SCO connections not started by the application changing the mode
- if (newModeOwnerPid != 0) {
- disconnectBluetoothSco(newModeOwnerPid);
+ // SCO connections not started by the application changing the mode when pid changes
+ if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+ disconnectBluetoothSco(newModeOwnerPid);
}
}
@@ -3187,28 +3197,17 @@
}
public void setBluetoothScoOnInt(boolean on, String eventSource) {
- if (DEBUG_DEVICES) {
- Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
- }
+ Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
if (on) {
// do not accept SCO ON if SCO audio is not connected
synchronized (mScoClients) {
- if (mBluetoothHeadset != null) {
- if (mBluetoothHeadsetDevice == null) {
- BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
- if (activeDevice != null) {
- // setBtScoActiveDevice() might trigger resetBluetoothSco() which
- // will call setBluetoothScoOnInt(false, "resetBluetoothSco")
- setBtScoActiveDevice(activeDevice);
- }
- }
- if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
- Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
- + mBluetoothHeadsetDevice + " is not in audio connected mode");
- return;
- }
+ if ((mBluetoothHeadset != null)
+ && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+ mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+ Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+ + mBluetoothHeadsetDevice + " is not in audio connected mode");
+ return;
}
}
mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3388,9 +3387,8 @@
public int totalCount() {
synchronized(mScoClients) {
int count = 0;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- count += mScoClients.get(i).getCount();
+ for (ScoClient mScoClient : mScoClients) {
+ count += mScoClient.getCount();
}
return count;
}
@@ -3398,128 +3396,161 @@
private void requestScoState(int state, int scoAudioMode) {
checkScoAudioState();
- if (totalCount() == 0) {
- if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
- // Make sure that the state transitions to CONNECTING even if we cannot initiate
- // the connection.
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
- // Accept SCO audio activation only in NORMAL audio mode or if the mode is
- // currently controlled by the same client process.
- synchronized(mSetModeDeathHandlers) {
- if ((mSetModeDeathHandlers.isEmpty() ||
- mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
- (mScoAudioState == SCO_STATE_INACTIVE ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
- if (mScoAudioState == SCO_STATE_INACTIVE) {
- mScoAudioMode = scoAudioMode;
- if (scoAudioMode == SCO_MODE_UNDEFINED) {
- if (mBluetoothHeadsetDevice != null) {
- mScoAudioMode = new Integer(Settings.Global.getInt(
- mContentResolver,
- "bluetooth_sco_channel_"+
- mBluetoothHeadsetDevice.getAddress(),
- SCO_MODE_VIRTUAL_CALL));
- if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
- mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
- }
- } else {
- mScoAudioMode = SCO_MODE_RAW;
- }
- }
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status = false;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.connectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.startVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
-
- if (status) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- } else {
- broadcastScoConnectionState(
- AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
- } else if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_ACTIVATE_REQ;
- }
- } else {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
- }
- } else {
- broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
+ int clientCount = totalCount();
+ if (clientCount != 0) {
+ Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+ + ", clientCount=" + clientCount);
+ return;
+ }
+ if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+ // Make sure that the state transitions to CONNECTING even if we cannot initiate
+ // the connection.
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+ // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+ // currently controlled by the same client process.
+ synchronized(mSetModeDeathHandlers) {
+ int modeOwnerPid = mSetModeDeathHandlers.isEmpty()
+ ? 0 : mSetModeDeathHandlers.get(0).getPid();
+ if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+ Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+ + modeOwnerPid + " != creatorPid " + mCreatorPid);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ return;
}
- } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
- (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
- mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
- if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
- boolean status = false;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.disconnectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
+ switch (mScoAudioState) {
+ case SCO_STATE_INACTIVE:
+ mScoAudioMode = scoAudioMode;
+ if (scoAudioMode == SCO_MODE_UNDEFINED) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ if (mBluetoothHeadsetDevice != null) {
+ mScoAudioMode = Settings.Global.getInt(mContentResolver,
+ "bluetooth_sco_channel_"
+ + mBluetoothHeadsetDevice.getAddress(),
+ SCO_MODE_VIRTUAL_CALL);
+ if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+ mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+ }
+ }
}
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " connection, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ }
+ if (mBluetoothHeadsetDevice == null) {
+ Log.w(TAG, "requestScoState: no active device while connecting,"
+ + " mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ } else {
+ Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+ + " failed, mScoAudioMode=" + mScoAudioMode);
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_DEACTIVATING:
+ mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to connect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
- if (!status) {
+ }
+ }
+ } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ switch (mScoAudioState) {
+ case SCO_STATE_ACTIVE_INTERNAL:
+ if (mBluetoothHeadset == null) {
+ if (getBluetoothHeadset()) {
+ mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ } else {
+ Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+ + " disconnection, mScoAudioMode=" + mScoAudioMode);
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(
AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
- } else if (getBluetoothHeadset()) {
- mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+ break;
}
- } else {
+ if (mBluetoothHeadsetDevice == null) {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
+ }
+ if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ } else {
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(
+ AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ break;
+ case SCO_STATE_ACTIVATE_REQ:
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
- }
+ break;
+ default:
+ Log.w(TAG, "requestScoState: failed to disconnect in state "
+ + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ break;
}
}
}
}
private void checkScoAudioState() {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
- mScoAudioState == SCO_STATE_INACTIVE &&
- mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
- != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ synchronized (mScoClients) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
+ mScoAudioState == SCO_STATE_INACTIVE &&
+ mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+ != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
}
}
+
private ScoClient getScoClient(IBinder cb, boolean create) {
synchronized(mScoClients) {
- ScoClient client = null;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- client = mScoClients.get(i);
- if (client.getBinder() == cb)
- return client;
+ for (ScoClient existingClient : mScoClients) {
+ if (existingClient.getBinder() == cb) {
+ return existingClient;
+ }
}
if (create) {
- client = new ScoClient(cb);
- mScoClients.add(client);
+ ScoClient newClient = new ScoClient(cb);
+ mScoClients.add(newClient);
+ return newClient;
}
- return client;
+ return null;
}
}
public void clearAllScoClients(int exceptPid, boolean stopSco) {
synchronized(mScoClients) {
ScoClient savedClient = null;
- int size = mScoClients.size();
- for (int i = 0; i < size; i++) {
- ScoClient cl = mScoClients.get(i);
+ for (ScoClient cl : mScoClients) {
if (cl.getPid() != exceptPid) {
cl.clearCount(stopSco);
} else {
@@ -3556,10 +3587,17 @@
mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
if (mBluetoothHeadsetDevice != null) {
if (mBluetoothHeadset != null) {
- if (!mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice)) {
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, 0);
+ boolean status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_RAW)
+ || disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL)
+ || disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_VR);
+ if (status) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ } else {
+ clearAllScoClients(exceptPid, false);
+ mScoAudioState = SCO_STATE_INACTIVE;
}
} else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
getBluetoothHeadset()) {
@@ -3572,6 +3610,34 @@
}
}
+ private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.disconnectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.stopVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
+ private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+ BluetoothDevice device, int scoAudioMode) {
+ switch (scoAudioMode) {
+ case SCO_MODE_RAW:
+ return bluetoothHeadset.connectAudio();
+ case SCO_MODE_VIRTUAL_CALL:
+ return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+ case SCO_MODE_VR:
+ return bluetoothHeadset.startVoiceRecognition(device);
+ default:
+ return false;
+ }
+ }
+
private void resetBluetoothSco() {
synchronized(mScoClients) {
clearAllScoClients(0, false);
@@ -3629,11 +3695,9 @@
return result;
}
- void setBtScoActiveDevice(BluetoothDevice btDevice) {
- if (DEBUG_DEVICES) {
- Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
- }
+ private void setBtScoActiveDevice(BluetoothDevice btDevice) {
synchronized (mScoClients) {
+ Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
if (!Objects.equals(btDevice, previousActiveDevice)) {
if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
@@ -3713,37 +3777,36 @@
boolean status = false;
if (mBluetoothHeadsetDevice != null) {
switch (mScoAudioState) {
- case SCO_STATE_ACTIVATE_REQ:
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.connectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.startVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
- break;
- case SCO_STATE_DEACTIVATE_REQ:
- if (mScoAudioMode == SCO_MODE_RAW) {
- status = mBluetoothHeadset.disconnectAudio();
- } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
- status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
- mBluetoothHeadsetDevice);
- } else if (mScoAudioMode == SCO_MODE_VR) {
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
- }
- break;
- case SCO_STATE_DEACTIVATE_EXT_REQ:
- status = mBluetoothHeadset.stopVoiceRecognition(
- mBluetoothHeadsetDevice);
+ case SCO_STATE_ACTIVATE_REQ:
+ status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_REQ:
+ status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode);
+ if (status) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ }
+ break;
+ case SCO_STATE_DEACTIVATE_EXT_REQ:
+ status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_RAW) ||
+ disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) ||
+ disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, SCO_MODE_VR);
+ if (status) {
+ mScoAudioState = SCO_STATE_DEACTIVATING;
+ }
+ break;
}
}
if (!status) {
- sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
- SENDMSG_REPLACE, 0, 0, null, 0);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
}
}
@@ -6313,33 +6376,47 @@
if (!mScoClients.isEmpty() &&
(mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
- mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+ mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+ mScoAudioState == SCO_STATE_DEACTIVATING)) {
broadcast = true;
}
switch (btState) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
- mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- mScoAudioState = SCO_STATE_INACTIVE;
- clearAllScoClients(0, false);
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
- mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
- mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- default:
- // do not broadcast CONNECTING or invalid state
- broadcast = false;
- break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ setBluetoothScoOn(true);
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ setBluetoothScoOn(false);
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ // startBluetoothSco called after stopBluetoothSco
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+ && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ broadcast = false;
+ break;
+ }
+ }
+ // Tear down SCO if disconnected from external
+ clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+ mScoAudioState = SCO_STATE_INACTIVE;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+ mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ default:
+ // do not broadcast CONNECTING or invalid state
+ broadcast = false;
+ break;
}
}
if (broadcast) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index af5521d..8bf0945 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -88,11 +88,11 @@
import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
import static android.system.OsConstants.O_CREAT;
import static android.system.OsConstants.O_RDWR;
+
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-import static com.android.internal.util.ArrayUtils.appendElement;
import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -275,7 +275,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
-import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.content.NativeLibraryHelper;
import com.android.internal.content.PackageHelper;
import com.android.internal.logging.MetricsLogger;
@@ -411,7 +410,7 @@
static final boolean DEBUG_DOMAIN_VERIFICATION = false;
private static final boolean DEBUG_BACKUP = false;
public static final boolean DEBUG_INSTALL = false;
- public static final boolean DEBUG_REMOVE = true;
+ public static final boolean DEBUG_REMOVE = false;
private static final boolean DEBUG_BROADCASTS = false;
private static final boolean DEBUG_SHOW_INFO = false;
private static final boolean DEBUG_PACKAGE_INFO = false;
@@ -8482,6 +8481,29 @@
}
/**
+ * Clear the package profile if this was an upgrade and the package
+ * version was updated.
+ */
+ private void maybeClearProfilesForUpgradesLI(
+ @Nullable PackageSetting originalPkgSetting,
+ @NonNull PackageParser.Package currentPkg) {
+ if (originalPkgSetting == null || !isUpgrade()) {
+ return;
+ }
+ if (originalPkgSetting.versionCode == currentPkg.mVersionCode) {
+ return;
+ }
+
+ clearAppProfilesLIF(currentPkg, UserHandle.USER_ALL);
+ if (DEBUG_INSTALL) {
+ Slog.d(TAG, originalPkgSetting.name
+ + " clear profile due to version change "
+ + originalPkgSetting.versionCode + " != "
+ + currentPkg.mVersionCode);
+ }
+ }
+
+ /**
* Traces a package scan.
* @see #scanPackageLI(File, int, int, long, UserHandle)
*/
@@ -8763,6 +8785,13 @@
(forceCollect && canSkipFullPackageVerification(pkg));
collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
+ // Reset profile if the application version is changed
+ maybeClearProfilesForUpgradesLI(pkgSetting, pkg);
+
+ /*
+ * A new system app appeared, but we already had a non-system one of the
+ * same name installed earlier.
+ */
boolean shouldHideSystemApp = false;
// A new application appeared on /system, but, we already have a copy of
// the application installed on /data.
@@ -10186,10 +10215,14 @@
// if this is is a sharedUser, check to see if the new package is signed by a newer
// signing certificate than the existing one, and if so, copy over the new details
- if (signatureCheckPs.sharedUser != null
- && pkg.mSigningDetails.hasAncestor(
+ if (signatureCheckPs.sharedUser != null) {
+ if (pkg.mSigningDetails.hasAncestor(
signatureCheckPs.sharedUser.signatures.mSigningDetails)) {
- signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+ signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+ }
+ if (signatureCheckPs.sharedUser.signaturesChanged == null) {
+ signatureCheckPs.sharedUser.signaturesChanged = Boolean.FALSE;
+ }
}
} catch (PackageManagerException e) {
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
@@ -10198,10 +10231,24 @@
// The signature has changed, but this package is in the system
// image... let's recover!
pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
+
// If the system app is part of a shared user we allow that shared user to change
- // signatures as well in part as part of an OTA.
+ // signatures as well as part of an OTA. We still need to verify that the signatures
+ // are consistent within the shared user for a given boot, so only allow updating
+ // the signatures on the first package scanned for the shared user (i.e. if the
+ // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
if (signatureCheckPs.sharedUser != null) {
+ if (signatureCheckPs.sharedUser.signaturesChanged != null &&
+ compareSignatures(
+ signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures,
+ pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
+ throw new PackageManagerException(
+ INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+ "Signature mismatch for shared user: " + pkgSetting.sharedUser);
+ }
+
signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+ signatureCheckPs.sharedUser.signaturesChanged = Boolean.TRUE;
}
// File a report about this.
String msg = "System package " + pkg.packageName
@@ -14031,13 +14078,10 @@
+ Manifest.permission.MANAGE_USERS);
}
final int callingUid = Binder.getCallingUid();
- mPermissionManager.enforceCrossUserPermission(callingUid, userId,
- true /* requireFullPermission */, true /* checkShell */,
- "setPackagesSuspended for user " + userId);
- if (callingUid != Process.ROOT_UID &&
- !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) {
- throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not"
- + " belong to calling app id " + UserHandle.getAppId(callingUid));
+ if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+ && getPackageUid(callingPackage, 0, userId) != callingUid) {
+ throw new SecurityException("Calling package " + callingPackage + " in user "
+ + userId + " does not belong to calling uid " + callingUid);
}
if (!PLATFORM_PACKAGE_NAME.equals(callingPackage)
&& mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId) != null) {
@@ -14164,9 +14208,19 @@
}
}
- void onSuspendingPackageRemoved(String packageName, int removedForUser) {
- final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
- : new int[] {removedForUser};
+ /**
+ * Immediately unsuspends any packages suspended by the given package. To be called
+ * when such a package's data is cleared or it is removed from the device.
+ *
+ * <p><b>Should not be used on a frequent code path</b> as it flushes state to disk
+ * synchronously
+ *
+ * @param packageName The package holding {@link Manifest.permission#SUSPEND_APPS} permission
+ * @param affectedUser The user for which the changes are taking place.
+ */
+ void unsuspendForSuspendingPackage(String packageName, int affectedUser) {
+ final int[] userIds = (affectedUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
+ : new int[] {affectedUser};
for (int userId : userIds) {
List<String> affectedPackages = new ArrayList<>();
synchronized (mPackages) {
@@ -14183,6 +14237,8 @@
new String[affectedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
sendPackagesSuspendedForUser(packageArray, userId, false, null);
+ // Write package restrictions immediately to avoid an inconsistent state.
+ mSettings.writePackageRestrictionsLPr(userId);
}
}
}
@@ -18758,7 +18814,7 @@
final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
if (ps.getPermissionsState().hasPermission(Manifest.permission.SUSPEND_APPS, userId)) {
- onSuspendingPackageRemoved(packageName, userId);
+ unsuspendForSuspendingPackage(packageName, userId);
}
@@ -18899,10 +18955,10 @@
true /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
- null, /*suspendingPackage*/
- null, /*dialogMessage*/
- null, /*suspendedAppExtras*/
- null, /*suspendedLauncherExtras*/
+ null /*suspendingPackage*/,
+ null /*dialogMessage*/,
+ null /*suspendedAppExtras*/,
+ null /*suspendedLauncherExtras*/,
false /*instantApp*/,
false /*virtualPreload*/,
null /*lastDisableAppCaller*/,
@@ -19089,6 +19145,10 @@
if (dsm != null) {
dsm.checkMemory();
}
+ if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
+ == PERMISSION_GRANTED) {
+ unsuspendForSuspendingPackage(packageName, userId);
+ }
}
} else {
succeeded = false;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f8bf9c4..5c6338d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -734,10 +734,10 @@
true /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
- null, /*suspendingPackage*/
- null, /*dialogMessage*/
- null, /*suspendedAppExtras*/
- null, /*suspendedLauncherExtras*/
+ null /*suspendingPackage*/,
+ null /*dialogMessage*/,
+ null /*suspendedAppExtras*/,
+ null /*suspendedLauncherExtras*/,
instantApp,
virtualPreload,
null /*lastDisableAppCaller*/,
@@ -1634,10 +1634,10 @@
false /*notLaunched*/,
false /*hidden*/,
false /*suspended*/,
- null, /*suspendingPackage*/
- null, /*dialogMessage*/
- null, /*suspendedAppExtras*/
- null, /*suspendedLauncherExtras*/
+ null /*suspendingPackage*/,
+ null /*dialogMessage*/,
+ null /*suspendedAppExtras*/,
+ null /*suspendedLauncherExtras*/,
false /*instantApp*/,
false /*virtualPreload*/,
null /*lastDisableAppCaller*/,
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index b6b94f5..ca08415 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -46,6 +46,7 @@
final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();
final PackageSignatures signatures = new PackageSignatures();
+ Boolean signaturesChanged;
SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
super(_pkgFlags, _pkgPrivateFlags);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f659225..d3526f7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1953,7 +1953,7 @@
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
if (userId == UserHandle.getUserId(callingUid)) return;
- if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
if (requireFullPermission) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0ccbb25..a7c203d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1303,8 +1303,10 @@
synchronized (mLock) {
if (mCurrentUserId == userId) {
if (mWaitingForUnlock) {
- // If we're switching users, now is when we transition the wallpaper
- switchUser(userId, null);
+ // the desired wallpaper is not direct-boot aware, load it now
+ final WallpaperData systemWallpaper =
+ getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+ switchWallpaper(systemWallpaper, null);
}
// Make sure that the SELinux labeling of all the relevant files is correct.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9621edd..c0dc750 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1510,6 +1510,10 @@
return (stack != null && stack.isVisible()) ? stack : null;
}
+ boolean hasSplitScreenPrimaryStack() {
+ return getSplitScreenPrimaryStack() != null;
+ }
+
/**
* Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
* not visible.
@@ -1613,6 +1617,18 @@
mService.mWindowsChanged = true;
}
+ /**
+ * In split-screen mode we process the IME containers above the docked divider
+ * rather than directly above their target.
+ */
+ private boolean skipTraverseChild(WindowContainer child) {
+ if (child == mImeWindowsContainers && mService.mInputMethodTarget != null
+ && !hasSplitScreenPrimaryStack()) {
+ return true;
+ }
+ return false;
+ }
+
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
// Special handling so we can process IME windows with #forAllImeWindows above their IME
@@ -1620,11 +1636,10 @@
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayChildWindowContainer child = mChildren.get(i);
- if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
- // In this case the Ime windows will be processed above their target so we skip
- // here.
+ if (skipTraverseChild(child)) {
continue;
}
+
if (child.forAllWindows(callback, traverseTopToBottom)) {
return true;
}
@@ -1633,11 +1648,10 @@
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final DisplayChildWindowContainer child = mChildren.get(i);
- if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
- // In this case the Ime windows will be processed above their target so we skip
- // here.
+ if (skipTraverseChild(child)) {
continue;
}
+
if (child.forAllWindows(callback, traverseTopToBottom)) {
return true;
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index c8baced..39a3629 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -408,7 +408,7 @@
}
void positionDockedStackedDivider(Rect frame) {
- TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
+ TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
if (stack == null) {
// Unfortunately we might end up with still having a divider, even though the underlying
// stack was already removed. This is because we are on AM thread and the removal of the
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 85e4ac7..553b4fe 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -79,7 +79,7 @@
public @interface ReorderMode {}
private final WindowManagerService mService;
- private final IRecentsAnimationRunner mRunner;
+ private IRecentsAnimationRunner mRunner;
private final RecentsAnimationCallbacks mCallbacks;
private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
private final int mDisplayId;
@@ -426,7 +426,10 @@
removeAnimation(taskAdapter);
}
+ // Clear references to the runner
unlinkToDeathOfRunner();
+ mRunner = null;
+
// Clear associated input consumers
mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0de3c92..c710c97 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3997,32 +3997,33 @@
return false;
}
+ private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
+ boolean traverseTopToBottom) {
+ // If this window is the current IME target, so we need to process the IME windows
+ // directly above it. The exception is if we are in split screen
+ // in which case we process the IME at the DisplayContent level to
+ // ensure it is above the docked divider.
+ if (isInputMethodTarget() && !inSplitScreenWindowingMode()) {
+ if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
boolean traverseTopToBottom) {
if (traverseTopToBottom) {
- if (isInputMethodTarget()) {
- // This window is the current IME target, so we need to process the IME windows
- // directly above it.
- if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
- if (callback.apply(this)) {
+ if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
+ || callback.apply(this)) {
return true;
}
} else {
- if (callback.apply(this)) {
+ if (callback.apply(this)
+ || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
return true;
}
- if (isInputMethodTarget()) {
- // This window is the current IME target, so we need to process the IME windows
- // directly above it.
- if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
- return true;
- }
- }
}
-
return false;
}
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 288f350..b47dbfa 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -38,6 +38,7 @@
#include <pthread.h>
#include <string.h>
#include <cinttypes>
+#include <iomanip>
static jobject mCallbacksObj = NULL;
@@ -1692,25 +1693,25 @@
<< " satellites:: " << std::endl;
}
- internalState << "constellation: 1=GPS, 2=SBAS, 3=GLO, 4=QZSS, 5=BDS, 6=GAL; "
- << "ephemerisType: 0=Eph, 1=Alm, 2=?; "
- << "ephemerisSource: 0=Demod, 1=Supl, 2=Server, 3=?; "
- << "ephemerisHealth: 0=Good, 1=Bad, 2=?" << std::endl;
+ internalState << "constell: 1=GPS, 2=SBAS, 3=GLO, 4=QZSS, 5=BDS, 6=GAL; "
+ << "ephType: 0=Eph, 1=Alm, 2=Unk; "
+ << "ephSource: 0=Demod, 1=Supl, 2=Server, 3=Unk; "
+ << "ephHealth: 0=Good, 1=Bad, 2=Unk" << std::endl;
for (size_t i = 0; i < data.satelliteDataArray.size(); i++) {
- internalState << "svid: " << data.satelliteDataArray[i].svid
- << ", constellation: "
+ internalState << "constell: "
<< static_cast<uint32_t>(data.satelliteDataArray[i].constellation)
- << ", ephemerisType: "
- << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisType)
- << ", ephemerisSource: "
- << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisSource)
- << ", ephemerisHealth: "
- << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisHealth)
- << ", serverPredictionIsAvailable: "
+ << ", svid: " << std::setw(3) << data.satelliteDataArray[i].svid
+ << ", serverPredAvail: "
<< data.satelliteDataArray[i].serverPredictionIsAvailable
- << ", serverPredictionAgeSeconds: "
+ << ", serverPredAgeSec: " << std::setw(7)
<< data.satelliteDataArray[i].serverPredictionAgeSeconds
- << ", ephemerisAgeSeconds: "
+ << ", ephType: "
+ << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisType)
+ << ", ephSource: "
+ << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisSource)
+ << ", ephHealth: "
+ << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisHealth)
+ << ", ephAgeSec: " << std::setw(7)
<< data.satelliteDataArray[i].ephemerisAgeSeconds << std::endl;
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
new file mode 100644
index 0000000..e076399
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.eq;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link WindowContainer#forAllWindows} and various implementations.
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowContainerTraversalTests extends WindowTestsBase {
+
+ @Test
+ public void testDockedDividerPosition() throws Exception {
+ final WindowState splitScreenWindow = createWindowOnStack(null,
+ WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+ mDisplayContent, "splitScreenWindow");
+ final WindowState splitScreenSecondaryWindow = createWindowOnStack(null,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+ TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
+
+ sWm.mInputMethodTarget = splitScreenWindow;
+
+ Consumer<WindowState> c = mock(Consumer.class);
+ mDisplayContent.forAllWindows(c, false);
+
+ verify(c).accept(eq(mDockedDividerWindow));
+ verify(c).accept(eq(mImeWindow));
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index cd524a5..13e3069 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -46,6 +46,10 @@
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
@@ -654,8 +658,45 @@
}
}
- private interface Operation {
- void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
+ /**
+ * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
+ *
+ * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
+ */
+ private static class Operation {
+ private interface ExecuteOp {
+ void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
+ }
+
+ private final @Nullable Runnable mSetupOp;
+ private final @NonNull ExecuteOp mExecuteOp;
+ private final @Nullable Runnable mDropOp;
+
+ private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
+ @Nullable Runnable cancelOp) {
+ mSetupOp = setupOp;
+ mExecuteOp = executeOp;
+ mDropOp = cancelOp;
+ }
+
+ private void setup() {
+ if (mSetupOp != null) {
+ mSetupOp.run();
+ }
+ }
+
+ void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
+ setup();
+ mExecuteOp.run(opId, service);
+ }
+
+ void drop() {
+ setup();
+
+ if (mDropOp != null) {
+ mDropOp.run();
+ }
+ }
}
/**
@@ -902,6 +943,10 @@
private void runOrAddOperation(Operation op) {
synchronized (mRemoteServiceLock) {
if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
+ Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
+ + "destruction");
+
+ op.drop();
return;
}
@@ -922,9 +967,15 @@
int opsAdded = mNumOps.getOpsAdded();
if (mNumOps.getOpsAdded() >= opsAllowed) {
- if (DEBUG || opsAllowed + 10 > opsAdded) {
- Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
- + "run in last 24 hours");
+ try {
+ if (DEBUG || opsAllowed + 10 > opsAdded) {
+ Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
+ + "were run in last 24 hours");
+ }
+
+ op.drop();
+ } catch (Exception e) {
+ Slog.e(TAG, mPuuid + ": Could not drop operation", e);
}
} else {
mNumOps.addOp(currentTime);
@@ -972,34 +1023,87 @@
+ ")");
}
+ /**
+ * Create an AudioRecord enough for starting and releasing the data buffered for the event.
+ *
+ * @param event The event that was received
+ * @return The initialized AudioRecord
+ */
+ private @NonNull AudioRecord createAudioRecordForEvent(
+ @NonNull SoundTrigger.GenericRecognitionEvent event) {
+ AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+ attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
+ AudioAttributes attributes = attributesBuilder.build();
+
+ // Use same AudioFormat processing as in RecognitionEvent.fromParcel
+ AudioFormat originalFormat = event.getCaptureFormat();
+ AudioFormat captureFormat = (new AudioFormat.Builder())
+ .setChannelMask(originalFormat.getChannelMask())
+ .setEncoding(originalFormat.getEncoding())
+ .setSampleRate(originalFormat.getSampleRate())
+ .build();
+
+ int bufferSize = AudioRecord.getMinBufferSize(
+ captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
+ ? AudioFormat.SAMPLE_RATE_HZ_MAX
+ : captureFormat.getSampleRate(),
+ captureFormat.getChannelCount() == 2
+ ? AudioFormat.CHANNEL_IN_STEREO
+ : AudioFormat.CHANNEL_IN_MONO,
+ captureFormat.getEncoding());
+
+ return new AudioRecord(attributes, captureFormat, bufferSize,
+ event.getCaptureSession());
+ }
+
@Override
public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
- runOrAddOperation((opId, service) -> {
- if (!mRecognitionConfig.allowMultipleTriggers) {
- synchronized (mCallbacksLock) {
- mCallbacks.remove(mPuuid.getUuid());
- }
- mDestroyOnceRunningOpsDone = true;
- }
+ runOrAddOperation(new Operation(
+ // always execute:
+ () -> {
+ if (!mRecognitionConfig.allowMultipleTriggers) {
+ // Unregister this remoteService once op is done
+ synchronized (mCallbacksLock) {
+ mCallbacks.remove(mPuuid.getUuid());
+ }
+ mDestroyOnceRunningOpsDone = true;
+ }
+ },
+ // execute if not throttled:
+ (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
+ // execute if throttled:
+ () -> {
+ if (event.isCaptureAvailable()) {
+ AudioRecord capturedData = createAudioRecordForEvent(event);
- service.onGenericRecognitionEvent(mPuuid, opId, event);
- });
+ // Currently we need to start and release the audio record to reset
+ // the DSP even if we don't want to process the event
+ capturedData.startRecording();
+ capturedData.release();
+ }
+ }));
}
@Override
public void onError(int status) {
if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
- runOrAddOperation((opId, service) -> {
- synchronized (mCallbacksLock) {
- mCallbacks.remove(mPuuid.getUuid());
- }
- mDestroyOnceRunningOpsDone = true;
-
- service.onError(mPuuid, opId, status);
- });
+ runOrAddOperation(
+ new Operation(
+ // always execute:
+ () -> {
+ // Unregister this remoteService once op is done
+ synchronized (mCallbacksLock) {
+ mCallbacks.remove(mPuuid.getUuid());
+ }
+ mDestroyOnceRunningOpsDone = true;
+ },
+ // execute if not throttled:
+ (opId, service) -> service.onError(mPuuid, opId, status),
+ // nothing to do if throttled
+ null));
}
@Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 72c67d3..29898ff 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1299,13 +1299,18 @@
}
/**
- * Ends an ongoing call.
- * TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this
- * method (clockwork & gearhead).
- * @hide
+ * Ends the foreground call on the device.
+ * <p>
+ * If there is a ringing call, calling this method rejects the ringing call. Otherwise the
+ * foreground call is ended.
+ * <p>
+ * Requires permission {@link android.Manifest.permission#ANSWER_PHONE_CALLS}.
+ *
+ * @return {@code true} if there is a call which will be rejected or terminated, {@code false}
+ * otherwise.
*/
+ @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS)
@SystemApi
- @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public boolean endCall() {
try {
if (isServiceConnected()) {