summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmds/incidentd/Android.bp14
-rw-r--r--cmds/incidentd/src/Privacy.cpp6
-rw-r--r--cmds/incidentd/src/Privacy.h3
-rw-r--r--cmds/incidentd/src/PrivacyFilter.cpp86
-rw-r--r--cmds/incidentd/src/PrivacyFilter.h8
-rw-r--r--cmds/incidentd/src/Reporter.cpp3
-rw-r--r--cmds/incidentd/src/cipher/IncidentKeyStore.cpp87
-rw-r--r--cmds/incidentd/src/cipher/IncidentKeyStore.h53
-rw-r--r--cmds/incidentd/src/cipher/ProtoEncryption.cpp139
-rw-r--r--cmds/incidentd/src/cipher/ProtoEncryption.h80
-rw-r--r--cmds/incidentd/src/cipher/cipher_blocks.proto25
-rw-r--r--cmds/incidentd/tests/IncidentKeyStore_test.cpp66
-rw-r--r--cmds/incidentd/tests/ProtoEncryption_test.cpp85
-rw-r--r--config/hiddenapi-greylist-max-p.txt2
-rw-r--r--core/java/android/hardware/display/ColorDisplayManager.java14
-rw-r--r--core/java/android/net/ConnectivityManager.java10
-rw-r--r--core/java/android/net/IpSecManager.java6
-rw-r--r--core/java/android/net/IpSecTransform.java3
-rw-r--r--core/java/android/os/GraphicsEnvironment.java42
-rw-r--r--core/java/android/provider/Settings.java54
-rw-r--r--core/java/android/view/autofill/AutofillManager.java46
-rw-r--r--core/java/android/webkit/WebViewLibraryLoader.java6
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java12
-rw-r--r--core/proto/android/app/settings_enums.proto5
-rw-r--r--core/res/res/layout-car/car_preference.xml12
-rw-r--r--core/res/res/layout-car/car_preference_category.xml14
-rw-r--r--core/res/res/values/dimens_car.xml4
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java5
-rw-r--r--libs/protoutil/src/ProtoFileReader.cpp2
-rw-r--r--location/java/com/android/internal/location/GpsNetInitiatedHandler.java14
-rw-r--r--media/java/android/media/AudioRecord.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java6
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java3
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java33
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java319
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java947
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java29
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java21
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java34
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java4
-rw-r--r--services/core/java/com/android/server/IpSecService.java20
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java8
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java408
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java80
-rw-r--r--services/core/java/com/android/server/display/color/DisplayTransformManager.java9
-rw-r--r--services/core/java/com/android/server/location/GnssLocationProvider.java2
-rw-r--r--services/core/java/com/android/server/location/GnssVisibilityControl.java65
-rw-r--r--services/core/java/com/android/server/pm/ApexManager.java164
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java4
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--telephony/java/android/telephony/PhoneStateListener.java251
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl25
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java3
-rw-r--r--tests/net/java/com/android/server/IpSecServiceParameterizedTest.java27
65 files changed, 1904 insertions, 1685 deletions
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index 534a38f14136..25e0328b4f38 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -60,12 +60,6 @@ cc_binary {
"libservices",
"libutils",
"libprotobuf-cpp-lite",
- "libcrypto",
- "libkeystore_aidl",
- "libkeystore_binder",
- "libkeystore_parcelables",
- "android.hardware.keymaster@4.0",
- "libkeymaster4support",
],
static_libs: [
@@ -119,8 +113,6 @@ cc_test {
"src/incidentd_util.cpp",
"src/proto_util.cpp",
"src/report_directory.cpp",
- "src/cipher/IncidentKeyStore.cpp",
- "src/cipher/ProtoEncryption.cpp",
"src/**/*.proto",
],
@@ -142,12 +134,6 @@ cc_test {
"libprotoutil",
"libservices",
"libutils",
- "libcrypto",
- "libkeystore_aidl",
- "libkeystore_binder",
- "libkeystore_parcelables",
- "android.hardware.keymaster@4.0",
- "libkeymaster4support",
],
target: {
diff --git a/cmds/incidentd/src/Privacy.cpp b/cmds/incidentd/src/Privacy.cpp
index 0cc358fd2746..0a187e166135 100644
--- a/cmds/incidentd/src/Privacy.cpp
+++ b/cmds/incidentd/src/Privacy.cpp
@@ -28,8 +28,6 @@ namespace incidentd {
using namespace android::os;
using std::strstream;
-static const bool kEncryptionEnabled = false;
-
uint64_t encode_field_id(const Privacy* p) { return (uint64_t)p->type << 32 | p->field_id; }
string Privacy::toString() const {
@@ -52,10 +50,6 @@ const Privacy* lookup(const Privacy* p, uint32_t fieldId) {
return NULL;
}
-bool sectionEncryption(int section_id) {
- return kEncryptionEnabled ? (section_id == 3025) /*restricted image section*/ : false;
-}
-
static bool isAllowed(const uint8_t policy, const uint8_t check) {
switch (check) {
case PRIVACY_POLICY_LOCAL:
diff --git a/cmds/incidentd/src/Privacy.h b/cmds/incidentd/src/Privacy.h
index 9cde748bc44c..763edb03e485 100644
--- a/cmds/incidentd/src/Privacy.h
+++ b/cmds/incidentd/src/Privacy.h
@@ -90,9 +90,6 @@ private:
uint8_t mPolicy;
};
-// TODO: Add privacy flag in incident.proto and auto generate it inside Privacy.
-bool sectionEncryption(int section_id);
-
/**
* If a privacy policy is other than the defined values, update it to a real one.
*/
diff --git a/cmds/incidentd/src/PrivacyFilter.cpp b/cmds/incidentd/src/PrivacyFilter.cpp
index ca6fb3708ef1..d00ecdde5c63 100644
--- a/cmds/incidentd/src/PrivacyFilter.cpp
+++ b/cmds/incidentd/src/PrivacyFilter.cpp
@@ -16,20 +16,19 @@
#define DEBUG false
#include "Log.h"
+#include "incidentd_util.h"
#include "PrivacyFilter.h"
+#include "proto_util.h"
#include "incidentd_util.h"
#include "proto_util.h"
#include "Section.h"
#include <android-base/file.h>
-#include <android/util/ProtoFileReader.h>
#include <android/util/protobuf.h>
+#include <android/util/ProtoFileReader.h>
#include <log/log.h>
-#include "cipher/IncidentKeyStore.h"
-#include "cipher/ProtoEncryption.h"
-
namespace android {
namespace os {
namespace incidentd {
@@ -146,8 +145,6 @@ public:
*/
status_t writeData(int fd);
- sp<ProtoReader> getData() { return mData; }
-
private:
/**
* The global set of field --> required privacy level mapping.
@@ -259,47 +256,8 @@ void PrivacyFilter::addFd(const sp<FilterFd>& output) {
mOutputs.push_back(output);
}
-static void write_section_to_file(int sectionId, FieldStripper& fieldStripper, sp<FilterFd> output,
- bool encryptIfNeeded) {
- status_t err;
-
- if (sectionEncryption(sectionId) && encryptIfNeeded) {
- ProtoEncryptor encryptor(fieldStripper.getData());
- size_t encryptedSize = encryptor.encrypt();
-
- if (encryptedSize <= 0) {
- output->onWriteError(BAD_VALUE);
- return;
- }
- err = write_section_header(output->getFd(), sectionId, encryptedSize);
- VLOG("Encrypted: write section header size %lu", (unsigned long)encryptedSize);
-
- encryptor.flush(output->getFd());
-
- if (err != NO_ERROR) {
- output->onWriteError(err);
- return;
- }
- } else {
- err = write_section_header(output->getFd(), sectionId, fieldStripper.dataSize());
- VLOG("No encryption: write section header size %lu",
- (unsigned long)fieldStripper.dataSize());
-
- if (err != NO_ERROR) {
- output->onWriteError(err);
- return;
- }
-
- err = fieldStripper.writeData(output->getFd());
- if (err != NO_ERROR) {
- output->onWriteError(err);
- return;
- }
- }
-}
-
-status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize,
- bool encryptIfNeeded) {
+status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel,
+ size_t* maxSize) {
status_t err;
if (maxSize != NULL) {
@@ -309,9 +267,9 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, s
// Order the writes by privacy filter, with increasing levels of filtration,k
// so we can do the filter once, and then write many times.
sort(mOutputs.begin(), mOutputs.end(),
- [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
- return a->getPrivacyPolicy() < b->getPrivacyPolicy();
- });
+ [](const sp<FilterFd>& a, const sp<FilterFd>& b) -> bool {
+ return a->getPrivacyPolicy() < b->getPrivacyPolicy();
+ });
uint8_t privacyPolicy = PRIVACY_POLICY_LOCAL; // a.k.a. no filtering
FieldStripper fieldStripper(mRestrictions, buffer.data()->read(), bufferLevel);
@@ -330,7 +288,17 @@ status_t PrivacyFilter::writeData(const FdBuffer& buffer, uint8_t bufferLevel, s
// Write the resultant buffer to the fd, along with the header.
ssize_t dataSize = fieldStripper.dataSize();
if (dataSize > 0) {
- write_section_to_file(mSectionId, fieldStripper, output, encryptIfNeeded);
+ err = write_section_header(output->getFd(), mSectionId, dataSize);
+ if (err != NO_ERROR) {
+ output->onWriteError(err);
+ continue;
+ }
+
+ err = fieldStripper.writeData(output->getFd());
+ if (err != NO_ERROR) {
+ output->onWriteError(err);
+ continue;
+ }
}
if (maxSize != NULL) {
@@ -382,18 +350,8 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
// Read this section from the reader into an FdBuffer
size_t sectionSize = reader->readRawVarint();
-
FdBuffer sectionData;
-
- // Write data to FdBuffer, if the section was encrypted, decrypt first.
- if (sectionEncryption(fieldId)) {
- VLOG("sectionSize %lu", (unsigned long)sectionSize);
- ProtoDecryptor decryptor(reader, sectionSize);
- err = decryptor.decryptAndFlush(&sectionData);
- } else {
- err = sectionData.write(reader, sectionSize);
- }
-
+ err = sectionData.write(reader, sectionSize);
if (err != NO_ERROR) {
ALOGW("filter_and_write_report FdBuffer.write failed (this shouldn't happen): %s",
strerror(-err));
@@ -401,8 +359,7 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
}
// Do the filter and write.
- err = filter.writeData(sectionData, bufferLevel, nullptr,
- false /* do not encrypt again*/);
+ err = filter.writeData(sectionData, bufferLevel, nullptr);
if (err != NO_ERROR) {
ALOGW("filter_and_write_report filter.writeData had an error: %s", strerror(-err));
return err;
@@ -411,7 +368,6 @@ status_t filter_and_write_report(int to, int from, uint8_t bufferLevel,
// We don't need this field. Incident does not have any direct children
// other than sections. So just skip them.
write_field_or_skip(NULL, reader, fieldTag, true);
- VLOG("Skip this.... section %d", fieldId);
}
}
diff --git a/cmds/incidentd/src/PrivacyFilter.h b/cmds/incidentd/src/PrivacyFilter.h
index d426db966a9a..76b28498a0ac 100644
--- a/cmds/incidentd/src/PrivacyFilter.h
+++ b/cmds/incidentd/src/PrivacyFilter.h
@@ -82,14 +82,8 @@ public:
* was written (i.e. after filtering).
*
* The buffer is assumed to have already been filtered to bufferLevel.
- *
- * This function can be called when persisting data to disk or when sending
- * data to client. In the former case, we need to encrypt the data when that
- * section requires encryption. In the latter case, we shouldn't send the
- * unencrypted data to client.
*/
- status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize,
- bool encryptIfNeeded);
+ status_t writeData(const FdBuffer& buffer, uint8_t bufferLevel, size_t* maxSize);
private:
int mSectionId;
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index dc4065bce39b..02b6bbe6c9b1 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -465,8 +465,7 @@ status_t ReportWriter::writeSection(const FdBuffer& buffer) {
}
});
- return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize,
- true /*encrypt if needed*/);
+ return filter.writeData(buffer, PRIVACY_POLICY_LOCAL, &mMaxSectionDataFilteredSize);
}
diff --git a/cmds/incidentd/src/cipher/IncidentKeyStore.cpp b/cmds/incidentd/src/cipher/IncidentKeyStore.cpp
deleted file mode 100644
index ae0a92094d0b..000000000000
--- a/cmds/incidentd/src/cipher/IncidentKeyStore.cpp
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Log.h"
-
-#include "IncidentKeyStore.h"
-
-#include <sys/stat.h>
-
-static constexpr size_t AES_KEY_BYTES = 32;
-static constexpr size_t GCM_MAC_BYTES = 16;
-constexpr char kKeyname[] = "IncidentKey";
-
-namespace android {
-namespace os {
-namespace incidentd {
-
-using namespace keystore;
-using std::string;
-
-IncidentKeyStore& IncidentKeyStore::getInstance() {
- static IncidentKeyStore sInstance(new keystore::KeystoreClientImpl);
- return sInstance;
-}
-
-bool IncidentKeyStore::encrypt(const string& data, int32_t flags, string* output) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (data.empty()) {
- ALOGW("IncidentKeyStore: Encrypt empty data?!");
- return false;
- }
- if (!mClient->doesKeyExist(kKeyname)) {
- auto gen_result = generateKeyLocked(kKeyname, 0);
- if (!gen_result.isOk()) {
- ALOGE("IncidentKeyStore: Key generate failed.");
- return false;
- }
- }
- if (!mClient->encryptWithAuthentication(kKeyname, data, flags, output)) {
- ALOGE("IncidentKeyStore: Encryption failed.");
- return false;
- }
- return true;
-}
-
-bool IncidentKeyStore::decrypt(const std::string& input, string* output) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (input.empty()) {
- ALOGE("IncidentKeyStore: Decrypt empty input?");
- return false;
- }
- if (!mClient->decryptWithAuthentication(kKeyname, input, output)) {
- ALOGE("IncidentKeyStore: Decryption failed.");
- return false;
- }
- return true;
-}
-
-KeyStoreNativeReturnCode IncidentKeyStore::generateKeyLocked(const std::string& name,
- int32_t flags) {
- auto paramBuilder = AuthorizationSetBuilder()
- .AesEncryptionKey(AES_KEY_BYTES * 8)
- .GcmModeMinMacLen(GCM_MAC_BYTES * 8)
- .Authorization(TAG_NO_AUTH_REQUIRED);
-
- AuthorizationSet hardware_enforced_characteristics;
- AuthorizationSet software_enforced_characteristics;
- return mClient->generateKey(name, paramBuilder, flags, &hardware_enforced_characteristics,
- &software_enforced_characteristics);
-}
-
-} // namespace incidentd
-} // namespace os
-} // namespace android
diff --git a/cmds/incidentd/src/cipher/IncidentKeyStore.h b/cmds/incidentd/src/cipher/IncidentKeyStore.h
deleted file mode 100644
index 27611cd7faad..000000000000
--- a/cmds/incidentd/src/cipher/IncidentKeyStore.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <keystore/keystore_client_impl.h>
-
-namespace android {
-namespace os {
-namespace incidentd {
-
-class IncidentKeyStore {
-public:
- static IncidentKeyStore& getInstance();
-
- IncidentKeyStore(keystore::KeystoreClient* client) : mClient(client) {}
-
- /**
- * Encrypt the plainText and output the encrypted message.
- *
- * Returns true on success and false otherwise.
- * If the key has not been created yet, it will generate the key in KeyMaster.
- */
- bool encrypt(const std::string& plainText, int32_t flags, std::string* output);
-
- /**
- * Decrypt and output the decrypted message.
- *
- * Returns true on success and false otherwise.
- */
- bool decrypt(const std::string& encryptedData, std::string* output);
-
-private:
- std::unique_ptr<keystore::KeystoreClient> mClient;
- std::mutex mMutex;
- keystore::KeyStoreNativeReturnCode generateKeyLocked(const std::string& name, int32_t flags);
-};
-
-} // namespace incidentd
-} // namespace os
-} // namespace android
diff --git a/cmds/incidentd/src/cipher/ProtoEncryption.cpp b/cmds/incidentd/src/cipher/ProtoEncryption.cpp
deleted file mode 100644
index 493796d2af4d..000000000000
--- a/cmds/incidentd/src/cipher/ProtoEncryption.cpp
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#define DEBUG true // STOPSHIP if true
-#include "Log.h"
-
-#include "ProtoEncryption.h"
-
-#include <android/util/protobuf.h>
-
-#include "IncidentKeyStore.h"
-
-namespace android {
-namespace os {
-namespace incidentd {
-
-using android::util::FIELD_COUNT_REPEATED;
-using android::util::FIELD_TYPE_STRING;
-using android::util::ProtoOutputStream;
-using android::util::ProtoReader;
-using std::string;
-
-static const int FIELD_ID_BLOCK = 1;
-
-size_t ProtoEncryptor::encrypt() {
- string block;
- int i = 0;
- // Read at most sBlockSize at a time and encrypt.
- while (mReader->readBuffer() != NULL) {
- size_t readBytes =
- mReader->currentToRead() > sBlockSize ? sBlockSize : mReader->currentToRead();
- block.resize(readBytes);
- std::memcpy(block.data(), mReader->readBuffer(), readBytes);
-
- string encrypted;
- if (IncidentKeyStore::getInstance().encrypt(block, 0, &encrypted)) {
- mOutputStream.write(FIELD_TYPE_STRING | FIELD_ID_BLOCK | FIELD_COUNT_REPEATED,
- encrypted);
- VLOG("Block %d Encryption: original %lld now %lld", i++, (long long)readBytes,
- (long long)encrypted.length());
- mReader->move(readBytes);
- } else {
- return 0;
- }
- }
- return mOutputStream.size();
-}
-
-status_t ProtoEncryptor::flush(int fd) {
- if (!mOutputStream.flush(fd)) {
- return BAD_VALUE;
- }
- return NO_ERROR;
-}
-
-status_t ProtoDecryptor::readOneBlock(string* output) {
- if (!mReader->hasNext()) {
- return NO_ERROR;
- }
- uint64_t fieldTag = mReader->readRawVarint();
- uint32_t fieldId = read_field_id(fieldTag);
- uint8_t wireType = read_wire_type(fieldTag);
- if (wireType == WIRE_TYPE_LENGTH_DELIMITED) {
- // Read this section from the reader into an FdBuffer
- size_t sectionSize = mReader->readRawVarint();
- output->resize(sectionSize);
- size_t pos = 0;
- while (pos < sectionSize && mReader->readBuffer() != NULL) {
- size_t toRead = (sectionSize - pos) > mReader->currentToRead()
- ? mReader->currentToRead()
- : (sectionSize - pos);
- std::memcpy(&((output->data())[pos]), mReader->readBuffer(), toRead);
- pos += toRead;
- mReader->move(toRead);
- }
- if (pos != sectionSize) {
- return BAD_VALUE;
- ALOGE("Failed to read one block");
- }
- } else {
- return BAD_VALUE;
- }
- return NO_ERROR;
-}
-
-status_t ProtoDecryptor::decryptAndFlush(FdBuffer* out) {
- size_t mStartBytes = mReader->bytesRead();
- size_t bytesRead = 0;
- int i = 0;
- status_t err = NO_ERROR;
- // Let's read until we read mTotalSize. If any error occurs before that, make sure to move the
- // read pointer so the caller can continue to read the following sections.
- while (bytesRead < mTotalSize) {
- string block;
- err = readOneBlock(&block);
- bytesRead = mReader->bytesRead() - mStartBytes;
-
- if (err != NO_ERROR) {
- break;
- }
-
- if (block.length() == 0) {
- VLOG("Done reading all blocks");
- break;
- }
-
- string decryptedBlock;
- if ((IncidentKeyStore::getInstance()).decrypt(block, &decryptedBlock)) {
- VLOG("Block %d Original Size %lu Decrypted size %lu", i++,
- (unsigned long)block.length(), (unsigned long)decryptedBlock.length());
- out->write(reinterpret_cast<uint8_t*>(decryptedBlock.data()), decryptedBlock.length());
- } else {
- err = BAD_VALUE;
- break;
- }
- }
-
- if (bytesRead < mTotalSize) {
- mReader->move(mTotalSize - bytesRead);
- }
- return err;
-}
-
-} // namespace incidentd
-} // namespace os
-} // namespace android
diff --git a/cmds/incidentd/src/cipher/ProtoEncryption.h b/cmds/incidentd/src/cipher/ProtoEncryption.h
deleted file mode 100644
index 5b72ca88ec64..000000000000
--- a/cmds/incidentd/src/cipher/ProtoEncryption.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <android/util/ProtoOutputStream.h>
-#include <android/util/ProtoReader.h>
-#include <frameworks/base/cmds/incidentd/src/cipher/cipher_blocks.pb.h>
-
-#include "FdBuffer.h"
-
-namespace android {
-namespace os {
-namespace incidentd {
-
-// PlainText IncidentReport format
-// [section1_header(id, size, type)][section1_data] ...
-
-// Let's say section1 needs encryption
-// After encryption, it becomes
-// [section1_header(id, encrypted_size, type)][[cipher_block][cipher_block][cipher_block]..]
-
-// When clients read the report, it's decrypted, and written in its original format
-
-/**
- * Takes a ProtoReader, encrypts its whole content -- which is one section, and flush to
- * a file descriptor.
- * The underlying encryption is done using Keystore binder APIs. We encrypt the data
- * in blocks, and write to the file in android.os.incidentd.CipherBlocks format.
- */
-class ProtoEncryptor {
-public:
- ProtoEncryptor(const sp<android::util::ProtoReader>& reader) : mReader(reader){};
-
- // Encrypt the data from ProtoReader, and store in CipherBlocks format.
- // return the size of CipherBlocks.
- size_t encrypt();
-
- status_t flush(int fd);
-
-private:
- static const size_t sBlockSize = 8 * 1024;
- const sp<android::util::ProtoReader> mReader;
- android::util::ProtoOutputStream mOutputStream;
-};
-
-// Read data from ProtoReader, which is in CipherBlocks proto format. Parse and decrypt
-// block by block.
-class ProtoDecryptor {
-public:
- ProtoDecryptor(const sp<android::util::ProtoReader>& reader, size_t size)
- : mReader(reader), mTotalSize(size){};
- status_t decryptAndFlush(FdBuffer* out);
-
-private:
- const sp<android::util::ProtoReader> mReader;
-
- // Total size in bytes we should read from ProtoReader.
- const size_t mTotalSize;
-
- // Read one cipher block from ProtoReader, instead of reading the whole content
- // and parse to CipherBlocks which could be huge.
- status_t readOneBlock(std::string* output);
-};
-
-} // namespace incidentd
-} // namespace os
-} // namespace android
diff --git a/cmds/incidentd/src/cipher/cipher_blocks.proto b/cmds/incidentd/src/cipher/cipher_blocks.proto
deleted file mode 100644
index 5c7ed242c7a5..000000000000
--- a/cmds/incidentd/src/cipher/cipher_blocks.proto
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-syntax = "proto2";
-
-package android.os.incidentd;
-
-// This proto is never instantiated anywhere. It only exists to keep a record of the format of the
-// encrypted data on disk.
-message CipherBlocks {
- repeated string blocks = 1;
-}
diff --git a/cmds/incidentd/tests/IncidentKeyStore_test.cpp b/cmds/incidentd/tests/IncidentKeyStore_test.cpp
deleted file mode 100644
index 2250fda90e01..000000000000
--- a/cmds/incidentd/tests/IncidentKeyStore_test.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "cipher/IncidentKeyStore.h"
-
-#include <binder/ProcessState.h>
-#include <gtest/gtest.h>
-
-#include <fstream>
-
-using namespace android::os::incidentd;
-
-class IncidentKeyStoreTest : public ::testing::Test {
-protected:
- std::unique_ptr<IncidentKeyStore> incidentKeyStore;
- void SetUp() override {
- android::ProcessState::self()->startThreadPool();
- incidentKeyStore = std::make_unique<IncidentKeyStore>(
- static_cast<keystore::KeystoreClient*>(new keystore::KeystoreClientImpl));
- };
- void TearDown() override { incidentKeyStore = nullptr; };
-};
-
-TEST_F(IncidentKeyStoreTest, test_encrypt_decrypt) {
- std::string plaintext;
- plaintext.resize(4 * 1024, 'a');
-
- std::string encrypted;
- EXPECT_TRUE(incidentKeyStore->encrypt(plaintext, 0, &encrypted));
- std::string decrypted;
- EXPECT_TRUE(incidentKeyStore->decrypt(encrypted, &decrypted));
-
- EXPECT_FALSE(encrypted.empty());
- EXPECT_EQ(plaintext, decrypted);
-}
-
-TEST_F(IncidentKeyStoreTest, test_encrypt_empty_hash) {
- std::string hash = "";
-
- std::string encrypted;
- EXPECT_FALSE(incidentKeyStore->encrypt(hash, 0, &encrypted));
-
- EXPECT_TRUE(encrypted.empty());
-}
-
-TEST_F(IncidentKeyStoreTest, test_decrypt_empty_hash) {
- std::string hash = "";
-
- std::string decrypted;
- EXPECT_FALSE(incidentKeyStore->decrypt(hash, &decrypted));
-
- EXPECT_TRUE(decrypted.empty());
-} \ No newline at end of file
diff --git a/cmds/incidentd/tests/ProtoEncryption_test.cpp b/cmds/incidentd/tests/ProtoEncryption_test.cpp
deleted file mode 100644
index 6742e034d70d..000000000000
--- a/cmds/incidentd/tests/ProtoEncryption_test.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Log.h"
-
-#include "cipher/ProtoEncryption.h"
-
-#include <android-base/file.h>
-#include <gtest/gtest.h>
-
-#include "FdBuffer.h"
-#include "android/util/ProtoFileReader.h"
-
-using namespace android::os::incidentd;
-using android::sp;
-using std::string;
-using ::testing::Test;
-
-const std::string kTestPath = GetExecutableDirectory();
-const std::string kTestDataPath = kTestPath + "/testdata/";
-
-TEST(ProtoEncryptionTest, test_encrypt_decrypt) {
- const std::string plaintextFile = kTestDataPath + "plaintext.txt";
- const std::string encryptedFile = kTestDataPath + "encrypted.txt";
- size_t msg1Size = 20 * 1024;
-
- // Create a file with plain text.
- {
- unique_fd fd(
- open(plaintextFile.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR));
- ASSERT_NE(fd.get(), -1);
- string content;
- content.resize(msg1Size, 'a');
- WriteFully(fd, content.data(), msg1Size);
- }
-
- // Read the plain text and encrypted
- {
- unique_fd readFd(open(plaintextFile.c_str(), O_RDONLY | O_CLOEXEC));
- unique_fd encryptedFd(
- open(encryptedFile.c_str(), O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR));
-
- ASSERT_NE(readFd.get(), -1);
- ASSERT_NE(encryptedFd.get(), -1);
-
- sp<ProtoFileReader> reader = new ProtoFileReader(readFd.get());
- ProtoEncryptor encryptor(reader);
- EXPECT_TRUE(encryptor.encrypt() > msg1Size);
-
- encryptor.flush(encryptedFd.get());
- }
-
- // Read the encrypted file, and decrypt
- unique_fd encryptedFd(open(encryptedFile.c_str(), O_RDONLY | O_CLOEXEC));
- ASSERT_NE(encryptedFd.get(), -1);
- FdBuffer output;
- sp<ProtoFileReader> reader2 = new ProtoFileReader(encryptedFd.get());
- ProtoDecryptor decryptor(reader2, reader2->size());
- decryptor.decryptAndFlush(&output);
-
- auto decryptedReader = output.data()->read();
-
- // Check the content.
- int count = 0;
- while (decryptedReader->hasNext()) {
- if (decryptedReader->next() == 'a') {
- count++;
- }
- }
-
- EXPECT_EQ(msg1Size, count);
-} \ No newline at end of file
diff --git a/config/hiddenapi-greylist-max-p.txt b/config/hiddenapi-greylist-max-p.txt
index 4c643e1c6831..141e8e669dac 100644
--- a/config/hiddenapi-greylist-max-p.txt
+++ b/config/hiddenapi-greylist-max-p.txt
@@ -73,5 +73,5 @@ Lcom/android/internal/telephony/IPhoneSubInfo$Stub;-><init>()V
Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCallForwardingChanged(Z)V
Lcom/android/internal/telephony/ITelephonyRegistry;->notifyCellLocation(Landroid/os/Bundle;)V
Lcom/android/internal/telephony/ITelephonyRegistry;->notifyDataActivity(I)V
-Lcom/android/internal/telephony/ITelephonyRegistry;->notifyOtaspChanged(I)V
+Lcom/android/internal/telephony/ITelephonyRegistry;->notifyOtaspChanged(II)V
Lcom/android/internal/view/BaseIWindow;-><init>()V
diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java
index ce71db630499..90d312e47bdc 100644
--- a/core/java/android/hardware/display/ColorDisplayManager.java
+++ b/core/java/android/hardware/display/ColorDisplayManager.java
@@ -162,6 +162,20 @@ public final class ColorDisplayManager {
*/
public static final int COLOR_MODE_AUTOMATIC = 3;
+ /**
+ * Display color mode range reserved for vendor customizations by the RenderIntent definition in
+ * hardware/interfaces/graphics/common/1.1/types.hal. These are NOT directly related to (but ARE
+ * mutually exclusive with) the {@link ColorMode} constants, but ARE directly related (and ARE
+ * mutually exclusive with) the DISPLAY_COLOR_* constants in DisplayTransformManager.
+ *
+ * @hide
+ */
+ public static final int VENDOR_COLOR_MODE_RANGE_MIN = 256; // 0x100
+ /**
+ * @hide
+ */
+ public static final int VENDOR_COLOR_MODE_RANGE_MAX = 511; // 0x1ff
+
private final ColorDisplayManagerInternal mManager;
private MetricsLogger mMetricsLogger;
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 0e10de8c4e3f..a69ca99500d6 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3449,6 +3449,10 @@ public class ConnectivityManager {
final NetworkCallback callback;
synchronized (sCallbacks) {
callback = sCallbacks.get(request);
+ if (message.what == CALLBACK_UNAVAIL) {
+ sCallbacks.remove(request);
+ callback.networkRequest = ALREADY_UNREGISTERED;
+ }
}
if (DBG) {
Log.d(TAG, getCallbackName(message.what) + " for network " + network);
@@ -3995,8 +3999,10 @@ public class ConnectivityManager {
synchronized (sCallbacks) {
Preconditions.checkArgument(networkCallback.networkRequest != null,
"NetworkCallback was not registered");
- Preconditions.checkArgument(networkCallback.networkRequest != ALREADY_UNREGISTERED,
- "NetworkCallback was already unregistered");
+ if (networkCallback.networkRequest == ALREADY_UNREGISTERED) {
+ Log.d(TAG, "NetworkCallback was already unregistered");
+ return;
+ }
for (Map.Entry<NetworkRequest, NetworkCallback> e : sCallbacks.entrySet()) {
if (e.getValue() == networkCallback) {
reqs.add(e.getKey());
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 7d34381f73fa..83813da80c44 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,11 +19,13 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -749,6 +751,7 @@ public final class IpSecManager {
* @hide
*/
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public void addAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
try {
@@ -771,6 +774,7 @@ public final class IpSecManager {
* @hide
*/
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public void removeAddress(@NonNull InetAddress address, int prefixLen) throws IOException {
try {
@@ -886,6 +890,7 @@ public final class IpSecManager {
*/
@SystemApi
@NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public IpSecTunnelInterface createIpSecTunnelInterface(@NonNull InetAddress localAddress,
@NonNull InetAddress remoteAddress, @NonNull Network underlyingNetwork)
@@ -916,6 +921,7 @@ public final class IpSecManager {
* @hide
*/
@SystemApi
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public void applyTunnelModeTransform(@NonNull IpSecTunnelInterface tunnel,
@PolicyDirection int direction, @NonNull IpSecTransform transform) throws IOException {
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index e519fdf65e50..36111f2a372d 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -21,9 +21,11 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -483,6 +485,7 @@ public final class IpSecTransform implements AutoCloseable {
*/
@SystemApi
@NonNull
+ @RequiresFeature(PackageManager.FEATURE_IPSEC_TUNNELS)
@RequiresPermission(android.Manifest.permission.MANAGE_IPSEC_TUNNELS)
public IpSecTransform buildTunnelModeTransform(
@NonNull InetAddress sourceAddress,
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 7dbc16a56a7b..1868d0596acc 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -618,28 +618,36 @@ public class GraphicsEnvironment {
return false;
}
- final String anglePkgName = getAnglePackageName(pm);
- if (anglePkgName.isEmpty()) {
- Log.e(TAG, "Failed to find ANGLE package.");
- return false;
- }
+ ApplicationInfo angleInfo = null;
- final ApplicationInfo angleInfo;
- String angleDebugPackage = getAngleDebugPackage(context, bundle);
- if (!angleDebugPackage.isEmpty()) {
- Log.i(TAG, "ANGLE debug package enabled: " + angleDebugPackage);
+ // If the developer has specified a debug package over ADB, attempt to find it
+ String anglePkgName = getAngleDebugPackage(context, bundle);
+ if (!anglePkgName.isEmpty()) {
+ Log.i(TAG, "ANGLE debug package enabled: " + anglePkgName);
try {
// Note the debug package does not have to be pre-installed
- angleInfo = pm.getApplicationInfo(angleDebugPackage, 0);
+ angleInfo = pm.getApplicationInfo(anglePkgName, 0);
} catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "ANGLE debug package '" + angleDebugPackage + "' not installed");
+ Log.w(TAG, "ANGLE debug package '" + anglePkgName + "' not installed");
return false;
}
- } else {
- try {
- angleInfo = pm.getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
+ }
+
+ // Otherwise, check to see if ANGLE is properly installed
+ if (angleInfo == null) {
+ anglePkgName = getAnglePackageName(pm);
+ if (!anglePkgName.isEmpty()) {
+ Log.i(TAG, "ANGLE package enabled: " + anglePkgName);
+ try {
+ // Production ANGLE libraries must be pre-installed as a system app
+ angleInfo = pm.getApplicationInfo(anglePkgName,
+ PackageManager.MATCH_SYSTEM_ONLY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "ANGLE package '" + anglePkgName + "' not installed");
+ return false;
+ }
+ } else {
+ Log.e(TAG, "Failed to find ANGLE package.");
return false;
}
}
@@ -700,7 +708,7 @@ public class GraphicsEnvironment {
private boolean setupAndUseAngle(Context context, String packageName) {
// Need to make sure we are evaluating ANGLE usage for the correct circumstances
if (!setupAngle(context, null, context.getPackageManager(), packageName)) {
- Log.v(TAG, "Package '" + packageName + "' should use not ANGLE");
+ Log.v(TAG, "Package '" + packageName + "' should not use ANGLE");
return false;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c5a1fb5f787..de888d3f9a7f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3455,10 +3455,25 @@ public final class Settings {
*/
public static final String DISPLAY_COLOR_MODE = "display_color_mode";
- private static final Validator DISPLAY_COLOR_MODE_VALIDATOR =
- new SettingsValidators.InclusiveIntegerRangeValidator(
- ColorDisplayManager.COLOR_MODE_NATURAL,
- ColorDisplayManager.COLOR_MODE_AUTOMATIC);
+ private static final Validator DISPLAY_COLOR_MODE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(@Nullable String value) {
+ // Assume the actual validation that this device can properly handle this kind of
+ // color mode further down in ColorDisplayManager / ColorDisplayService.
+ try {
+ final int setting = Integer.parseInt(value);
+ final boolean isInFrameworkRange =
+ setting >= ColorDisplayManager.COLOR_MODE_NATURAL
+ && setting <= ColorDisplayManager.COLOR_MODE_AUTOMATIC;
+ final boolean isInVendorRange =
+ setting >= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN
+ && setting <= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX;
+ return isInFrameworkRange || isInVendorRange;
+ } catch (NumberFormatException | NullPointerException e) {
+ return false;
+ }
+ }
+ };
/**
* The user selected peak refresh rate in frames per second.
@@ -6484,6 +6499,21 @@ public final class Settings {
new SettingsValidators.ComponentNameListValidator(":");
/**
+ * Whether the Global Actions Panel is enabled.
+ * @hide
+ */
+ public static final String GLOBAL_ACTIONS_PANEL_ENABLED = "global_actions_panel_enabled";
+
+ private static final Validator GLOBAL_ACTIONS_PANEL_ENABLED_VALIDATOR = BOOLEAN_VALIDATOR;
+
+ /**
+ * Whether the Global Actions Panel can be toggled on or off in Settings.
+ * @hide
+ */
+ public static final String GLOBAL_ACTIONS_PANEL_AVAILABLE =
+ "global_actions_panel_available";
+
+ /**
* Whether the hush gesture has ever been used
* @hide
*/
@@ -8243,6 +8273,16 @@ public final class Settings {
BOOLEAN_VALIDATOR;
/**
+ * Whether or not the face unlock education screen has been shown to the user.
+ * @hide
+ */
+ public static final String FACE_UNLOCK_EDUCATION_INFO_DISPLAYED =
+ "face_unlock_education_info_displayed";
+
+ private static final Validator FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR =
+ BOOLEAN_VALIDATOR;
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
@@ -8922,7 +8962,8 @@ public final class Settings {
SILENCE_NOTIFICATION_GESTURE_COUNT,
SILENCE_CALL_GESTURE_COUNT,
SILENCE_TIMER_GESTURE_COUNT,
- DARK_MODE_DIALOG_SEEN
+ DARK_MODE_DIALOG_SEEN,
+ GLOBAL_ACTIONS_PANEL_ENABLED
};
/**
@@ -9048,6 +9089,8 @@ public final class Settings {
VALIDATORS.put(FACE_UNLOCK_APP_ENABLED, FACE_UNLOCK_APP_ENABLED_VALIDATOR);
VALIDATORS.put(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION_VALIDATOR);
+ VALIDATORS.put(FACE_UNLOCK_EDUCATION_INFO_DISPLAYED,
+ FACE_UNLOCK_EDUCATION_INFO_DISPLAYED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_ENABLED, ASSIST_GESTURE_ENABLED_VALIDATOR);
VALIDATORS.put(ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
ASSIST_GESTURE_SILENCE_ALERTS_ENABLED_VALIDATOR);
@@ -9106,6 +9149,7 @@ public final class Settings {
VALIDATORS.put(ODI_CAPTIONS_ENABLED, ODI_CAPTIONS_ENABLED_VALIDATOR);
VALIDATORS.put(DARK_MODE_DIALOG_SEEN, BOOLEAN_VALIDATOR);
VALIDATORS.put(UI_NIGHT_MODE, UI_NIGHT_MODE_VALIDATOR);
+ VALIDATORS.put(GLOBAL_ACTIONS_PANEL_ENABLED, GLOBAL_ACTIONS_PANEL_ENABLED_VALIDATOR);
}
/**
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 5872d3f7f785..35ea8964a342 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -48,6 +48,7 @@ import android.service.autofill.FillEventHistory;
import android.service.autofill.UserData;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.DebugUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -229,7 +230,8 @@ public final class AutofillManager {
/** @hide */ public static final int FLAG_ADD_CLIENT_VERBOSE = 0x4;
/** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY = 0x8;
- /** @hide */ public static final int FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
+ // NOTE: flag below is used by the session start receiver only, hence it can have values above
+ /** @hide */ public static final int RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY = 0x1;
/** @hide */
public static final int DEFAULT_LOGGING_LEVEL = Build.IS_DEBUGGABLE
@@ -521,7 +523,7 @@ public final class AutofillManager {
private boolean mForAugmentedAutofillOnly;
/**
- * When set, standard autofill is enabled, but sessions can still be created for augmented
+ * When set, standard autofill is disabled, but sessions can still be created for augmented
* autofill only.
*/
@GuardedBy("mLock")
@@ -969,6 +971,13 @@ public final class AutofillManager {
startSessionLocked(id, null, value, flags);
} else {
// Update focus on existing session.
+ if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+ if (sDebug) {
+ Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+ + "mForAugmentedAutofillOnly on manual request");
+ }
+ mForAugmentedAutofillOnly = false;
+ }
updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
}
addEnteredIdLocked(id);
@@ -1126,6 +1135,13 @@ public final class AutofillManager {
startSessionLocked(id, bounds, null, flags);
} else {
// Update focus on existing session.
+ if (mForAugmentedAutofillOnly && (flags & FLAG_MANUAL_REQUEST) != 0) {
+ if (sDebug) {
+ Log.d(TAG, "notifyViewEntered(" + id + "): resetting "
+ + "mForAugmentedAutofillOnly on manual request");
+ }
+ mForAugmentedAutofillOnly = false;
+ }
updateSessionLocked(id, bounds, null, ACTION_VIEW_ENTERED, flags);
}
addEnteredIdLocked(id);
@@ -1695,6 +1711,16 @@ public final class AutofillManager {
+ ", enabledAugmentedOnly=" + mEnabledForAugmentedAutofillOnly
+ ", enteredIds=" + mEnteredIds);
}
+ // We need to reset the augmented-only state when a manual request is made, as it's possible
+ // that the service returned null for the first request and now the user is manually
+ // requesting autofill to trigger a custom UI provided by the service.
+ if (mForAugmentedAutofillOnly && !mEnabledForAugmentedAutofillOnly
+ && (flags & FLAG_MANUAL_REQUEST) != 0) {
+ if (sVerbose) {
+ Log.v(TAG, "resetting mForAugmentedAutofillOnly on manual autofill request");
+ }
+ mForAugmentedAutofillOnly = false;
+ }
if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
if (sVerbose) {
Log.v(TAG, "not automatically starting session for " + id
@@ -1717,7 +1743,7 @@ public final class AutofillManager {
mState = STATE_ACTIVE;
}
final int extraFlags = receiver.getOptionalExtraIntResult(0);
- if ((extraFlags & FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
+ if ((extraFlags & RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) != 0) {
if (sDebug) Log.d(TAG, "startSession(" + componentName + "): for augmented only");
mForAugmentedAutofillOnly = true;
}
@@ -2011,10 +2037,20 @@ public final class AutofillManager {
public static final int SET_STATE_FLAG_DEBUG = 0x08;
/** @hide */
public static final int SET_STATE_FLAG_VERBOSE = 0x10;
+ /** @hide */
+ public static final int SET_STATE_FLAG_FOR_AUTOFILL_ONLY = 0x20;
private void setState(int flags) {
- if (sVerbose) Log.v(TAG, "setState(" + flags + ")");
+ if (sVerbose) {
+ Log.v(TAG, "setState(" + flags + ": " + DebugUtils.flagsToString(AutofillManager.class,
+ "SET_STATE_FLAG_", flags) + ")");
+ }
synchronized (mLock) {
+ if ((flags & SET_STATE_FLAG_FOR_AUTOFILL_ONLY) != 0) {
+ mForAugmentedAutofillOnly = true;
+ // NOTE: returning right away as this is the only flag set, at least currently...
+ return;
+ }
mEnabled = (flags & SET_STATE_FLAG_ENABLED) != 0;
if (!mEnabled || (flags & SET_STATE_FLAG_RESET_SESSION) != 0) {
// Reset the session state
@@ -2390,7 +2426,7 @@ public final class AutofillManager {
}
}
- if (sessionFinishedState != 0) {
+ if (sessionFinishedState != STATE_UNKNOWN) {
// Callback call was "hijacked" to also update the session state.
setSessionFinished(sessionFinishedState, /* autofillableIds= */ null);
}
diff --git a/core/java/android/webkit/WebViewLibraryLoader.java b/core/java/android/webkit/WebViewLibraryLoader.java
index 5a6aebaaad51..be49fc434c79 100644
--- a/core/java/android/webkit/WebViewLibraryLoader.java
+++ b/core/java/android/webkit/WebViewLibraryLoader.java
@@ -181,9 +181,9 @@ public class WebViewLibraryLoader {
boolean is64Bit = VMRuntime.getRuntime().is64Bit();
// On 64-bit address space is really cheap and we can reserve 1GB which is plenty.
// On 32-bit it's fairly scarce and we should keep it to a realistic number that
- // permits some future growth but doesn't hog space: we use 100MB which is more than 2x
- // the current requirement.
- long addressSpaceToReserve = is64Bit ? 1 * 1024 * 1024 * 1024 : 100 * 1024 * 1024;
+ // permits some future growth but doesn't hog space: we use 130MB which is roughly
+ // what was calculated on older OS versions in practice.
+ long addressSpaceToReserve = is64Bit ? 1 * 1024 * 1024 * 1024 : 130 * 1024 * 1024;
sAddressSpaceReserved = nativeReserveAddressSpace(addressSpaceToReserve);
if (sAddressSpaceReserved) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index b7d838edadc5..740753d9e5f1 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -779,7 +779,7 @@ public class ChooserActivity extends ResolverActivity {
}
}
} catch (SecurityException | NullPointerException e) {
- Log.w(TAG, "Error loading file preview", e);
+ logContentPreviewWarning(uri);
}
if (TextUtils.isEmpty(fileName)) {
@@ -793,6 +793,14 @@ public class ChooserActivity extends ResolverActivity {
return new FileInfo(fileName, hasThumbnail);
}
+ private void logContentPreviewWarning(Uri uri) {
+ // The ContentResolver already logs the exception. Log something more informative.
+ Log.w(TAG, "Could not load (" + uri.toString() + ") thumbnail/name for preview. If "
+ + "desired, consider using Intent#createChooser to launch the ChooserActivity, "
+ + "and set your Intent's clipData and flags in accordance with that method's "
+ + "documentation");
+ }
+
private ViewGroup displayFileContentPreview(Intent targetIntent, LayoutInflater layoutInflater,
ViewGroup convertView, ViewGroup parent) {
@@ -1664,7 +1672,7 @@ public class ChooserActivity extends ResolverActivity {
try {
return ImageUtils.loadThumbnail(getContentResolver(), uri, size);
} catch (IOException | NullPointerException | SecurityException ex) {
- Log.w(TAG, "Error loading preview thumbnail for uri: " + uri.toString(), ex);
+ logContentPreviewWarning(uri);
}
return null;
}
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 15944023fb14..1160b357a1fb 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2364,4 +2364,9 @@ enum PageId {
// Settings > Apps and notifications > Notifications > Gentle notifications
GENTLE_NOTIFICATIONS_SCREEN = 1715;
+
+ // OPEN: Settings > System > Gestures > Global Actions Panel
+ // CATEGORY: SETTINGS
+ // OS: Q
+ GLOBAL_ACTIONS_PANEL_SETTINGS = 1800;
}
diff --git a/core/res/res/layout-car/car_preference.xml b/core/res/res/layout-car/car_preference.xml
index ae3d63bd2d6a..b138f4d7cf50 100644
--- a/core/res/res/layout-car/car_preference.xml
+++ b/core/res/res/layout-car/car_preference.xml
@@ -27,20 +27,20 @@
<com.android.internal.widget.PreferenceImageView
android:id="@id/icon"
- android:layout_width="@*android:dimen/car_primary_icon_size"
- android:layout_height="@*android:dimen/car_primary_icon_size"
+ android:layout_width="@dimen/car_preference_icon_size"
+ android:layout_height="@dimen/car_preference_icon_size"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
- android:layout_marginBottom="@*android:dimen/car_padding_2"
+ android:layout_marginBottom="@dimen/car_preference_row_vertical_margin"
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
- android:layout_marginTop="@android:dimen/car_padding_2"/>
+ android:layout_marginTop="@dimen/car_preference_row_vertical_margin"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
- android:layout_marginBottom="@*android:dimen/car_padding_2"
- android:layout_marginTop="@*android:dimen/car_padding_2"
+ android:layout_marginBottom="@dimen/car_preference_row_vertical_margin"
+ android:layout_marginTop="@dimen/car_preference_row_vertical_margin"
android:layout_toEndOf="@id/icon"
android:layout_toStartOf="@id/widget_frame"
android:orientation="vertical">
diff --git a/core/res/res/layout-car/car_preference_category.xml b/core/res/res/layout-car/car_preference_category.xml
index d1f73421e185..b674487cffa7 100644
--- a/core/res/res/layout-car/car_preference_category.xml
+++ b/core/res/res/layout-car/car_preference_category.xml
@@ -22,25 +22,25 @@
android:background="?android:attr/selectableItemBackground"
android:focusable="true"
android:gravity="center_vertical"
- android:minHeight="@*android:dimen/car_card_header_height"
+ android:minHeight="@dimen/car_card_header_height"
android:orientation="horizontal"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart">
<com.android.internal.widget.PreferenceImageView
android:id="@id/icon"
- android:layout_width="@*android:dimen/car_primary_icon_size"
- android:layout_height="@*android:dimen/car_primary_icon_size"
+ android:layout_width="@dimen/car_preference_category_icon_size"
+ android:layout_height="@dimen/car_preference_category_icon_size"
android:layout_gravity="center_vertical"
- android:layout_marginBottom="@dimen/car_padding_2"
+ android:layout_marginBottom="@dimen/car_preference_row_vertical_margin"
android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd"
- android:layout_marginTop="@*android:dimen/car_padding_2"/>
+ android:layout_marginTop="@dimen/car_preference_row_vertical_margin"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="@*android:dimen/car_padding_2"
- android:layout_marginTop="@*android:dimen/car_padding_2"
+ android:layout_marginBottom="@dimen/car_preference_row_vertical_margin"
+ android:layout_marginTop="@dimen/car_preference_row_vertical_margin"
android:orientation="vertical">
<TextView
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index f22a91ff75c1..880f9ccebd6e 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -137,4 +137,8 @@
<!-- Dialog image margin start -->
<dimen name="image_margin_start">@*android:dimen/car_keyline_1</dimen>
+ <dimen name="car_preference_icon_size">@dimen/car_primary_icon_size</dimen>
+ <dimen name="car_preference_category_icon_size">@dimen/car_primary_icon_size</dimen>
+ <dimen name="car_preference_row_vertical_margin">@dimen/car_padding_2</dimen>
+
</resources>
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index e76754582fe9..9fa04ef2dda2 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -636,6 +636,7 @@ public class SettingsBackupTest {
Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
Settings.Secure.ENABLED_PRINT_SERVICES,
+ Settings.Secure.GLOBAL_ACTIONS_PANEL_AVAILABLE,
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR,
Settings.Secure.INPUT_METHOD_SELECTOR_VISIBILITY,
@@ -720,7 +721,8 @@ public class SettingsBackupTest {
Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS,
Settings.Secure.BIOMETRIC_DEBUG_ENABLED,
Settings.Secure.FACE_UNLOCK_ATTENTION_REQUIRED,
- Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED);
+ Settings.Secure.FACE_UNLOCK_DIVERSITY_REQUIRED,
+ Settings.Secure.FACE_UNLOCK_EDUCATION_INFO_DISPLAYED);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
@@ -798,4 +800,3 @@ public class SettingsBackupTest {
}
}
-
diff --git a/libs/protoutil/src/ProtoFileReader.cpp b/libs/protoutil/src/ProtoFileReader.cpp
index c7f1129fbbaa..bbb1fe374f0e 100644
--- a/libs/protoutil/src/ProtoFileReader.cpp
+++ b/libs/protoutil/src/ProtoFileReader.cpp
@@ -99,7 +99,6 @@ ProtoFileReader::next()
// Shouldn't get to here. Always call hasNext() before calling next().
return 0;
}
- mPos++;
return mBuffer[mOffset++];
}
@@ -131,7 +130,6 @@ ProtoFileReader::move(size_t amt)
const size_t chunk =
mMaxOffset - mOffset > amt ? amt : mMaxOffset - mOffset;
mOffset += chunk;
- mPos += chunk;
amt -= chunk;
}
}
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index b4be21987cf7..04e7bab0542b 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -357,8 +357,10 @@ public class GpsNetInitiatedHandler {
}
}
- // Sets the NI notification.
- private synchronized void setNiNotification(GpsNiNotification notif) {
+ /**
+ * Posts a notification in the status bar using the contents in {@code notif} object.
+ */
+ public synchronized void setNiNotification(GpsNiNotification notif) {
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
@@ -539,14 +541,14 @@ public class GpsNetInitiatedHandler {
*/
static private String decodeString(String original, boolean isHex, int coding)
{
+ if (coding == GPS_ENC_NONE) {
+ return original;
+ }
+
String decoded = original;
byte[] input = stringToByteArray(original, isHex);
switch (coding) {
- case GPS_ENC_NONE:
- decoded = original;
- break;
-
case GPS_ENC_SUPL_GSM_DEFAULT:
decoded = decodeGSMPackedString(input);
break;
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index eeb7655abff9..ce9b07dd0c0e 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -838,6 +838,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
}
if (mAudioCapturePolicy != null) {
AudioManager.unregisterAudioPolicyAsyncStatic(mAudioCapturePolicy);
+ mAudioCapturePolicy = null;
}
native_release();
mState = STATE_UNINITIALIZED;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index e02709e10e83..5eaa163db639 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -707,7 +707,9 @@ public class ApplicationsState {
private long getTotalInternalSize(PackageStats ps) {
if (ps != null) {
- return ps.codeSize + ps.dataSize;
+ // We subtract the cache size because the system can clear it automatically and
+ // |dataSize| is a superset of |cacheSize|.
+ return ps.codeSize + ps.dataSize - ps.cacheSize;
}
return SIZE_INVALID;
}
@@ -715,7 +717,7 @@ public class ApplicationsState {
private long getTotalExternalSize(PackageStats ps) {
if (ps != null) {
// We also include the cache size here because for non-emulated
- // we don't automtically clean cache files.
+ // we don't automatically clean cache files.
return ps.externalCodeSize + ps.externalDataSize
+ ps.externalCacheSize
+ ps.externalMediaSize + ps.externalObbSize;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index b27efd0edc8b..f8697a19c7ab 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -191,8 +191,9 @@ public class ApplicationsStateRoboTest {
shadowContext.setSystemService(Context.STORAGE_STATS_SERVICE, mStorageStatsManager);
StorageStats storageStats = new StorageStats();
storageStats.codeBytes = 10;
- storageStats.dataBytes = 20;
storageStats.cacheBytes = 30;
+ // Data bytes are a superset of cache bytes.
+ storageStats.dataBytes = storageStats.cacheBytes + 20;
when(mStorageStatsManager.queryStatsForPackage(any(UUID.class),
anyString(), any(UserHandle.class))).thenReturn(storageStats);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 348f01eaa004..715e1ebe31ac 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -229,4 +229,10 @@
<!-- Default for Settings.Secure.AWARE_ENABLED -->
<bool name="def_aware_enabled">false</bool>
+
+ <!-- Default for Settings.Secure.SKIP_GESTURE -->
+ <bool name="def_skip_gesture">false</bool>
+
+ <!-- Default for Settings.Secure.SILENCE_GESTURE -->
+ <bool name="def_silence_gesture">false</bool>
</resources>
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 2f3a42fdcc3f..82592ceeb710 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3237,7 +3237,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 178;
+ private static final int SETTINGS_VERSION = 179;
private final int mUserId;
@@ -4356,6 +4356,37 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 178;
}
+ if (currentVersion == 178) {
+ // Version 178: Set the default value for Secure Settings:
+ // SKIP_GESTURE & SILENCE_GESTURE
+
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+
+ final Setting skipGesture = secureSettings.getSettingLocked(
+ Secure.SKIP_GESTURE);
+
+ if (skipGesture.isNull()) {
+ final boolean defSkipGesture = getContext().getResources().getBoolean(
+ R.bool.def_skip_gesture);
+ secureSettings.insertSettingLocked(
+ Secure.SKIP_GESTURE, defSkipGesture ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ final Setting silenceGesture = secureSettings.getSettingLocked(
+ Secure.SILENCE_GESTURE);
+
+ if (silenceGesture.isNull()) {
+ final boolean defSilenceGesture = getContext().getResources().getBoolean(
+ R.bool.def_silence_gesture);
+ secureSettings.insertSettingLocked(
+ Secure.SILENCE_GESTURE, defSilenceGesture ? "1" : "0",
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ currentVersion = 179;
+ }
+
// vXXX: Add new settings above this point.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
index 61aa60bb9675..90fc86bc5b51 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@@ -13,18 +13,28 @@
*/
package com.android.systemui.plugins;
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
import android.view.View;
+import com.android.systemui.plugins.annotations.ProvidesInterface;
+
@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
public interface OverlayPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
- int VERSION = 2;
+ int VERSION = 3;
+ /**
+ * Setup overlay plugin
+ */
void setup(View statusBar, View navBar);
+ /**
+ * Setup overlay plugin with callback
+ */
+ default void setup(View statusBar, View navBar, Callback callback) {
+ setup(statusBar, navBar);
+ }
+
default boolean holdStatusBarOpen() {
return false;
}
@@ -34,4 +44,11 @@ public interface OverlayPlugin extends Plugin {
*/
default void setCollapseDesired(boolean collapseDesired) {
}
+
+ /**
+ * Used to update system ui whether to hold status bar open
+ */
+ interface Callback {
+ void onHoldStatusBarOpenChange();
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index 814db19c56b7..06ae399a5e4e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -354,6 +354,7 @@ public class TaskStackChangeListeners extends TaskStackListener {
mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
(RunningTaskInfo) msg.obj);
}
+ break;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 12f40f385cb2..61a0f72315ea 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -205,7 +205,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
final Handler mainHandler = new Handler(Looper.getMainLooper());
Dependency.get(PluginManager.class).addPluginListener(
new PluginListener<OverlayPlugin>() {
- private ArraySet<OverlayPlugin> mOverlays;
+ private ArraySet<OverlayPlugin> mOverlays = new ArraySet<>();
@Override
public void onPluginConnected(OverlayPlugin plugin, Context pluginContext) {
@@ -215,18 +215,7 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
StatusBar statusBar = getComponent(StatusBar.class);
if (statusBar != null) {
plugin.setup(statusBar.getStatusBarWindow(),
- statusBar.getNavigationBarView());
- }
- // Lazy init.
- if (mOverlays == null) mOverlays = new ArraySet<>();
- if (plugin.holdStatusBarOpen()) {
- mOverlays.add(plugin);
- Dependency.get(StatusBarWindowController.class)
- .setStateListener(b -> mOverlays.forEach(
- o -> o.setCollapseDesired(b)));
- Dependency.get(StatusBarWindowController.class)
- .setForcePluginOpen(mOverlays.size() != 0);
-
+ statusBar.getNavigationBarView(), new Callback(plugin));
}
}
});
@@ -243,6 +232,33 @@ public class SystemUIApplication extends Application implements SysUiServiceProv
}
});
}
+
+ class Callback implements OverlayPlugin.Callback {
+ private final OverlayPlugin mPlugin;
+
+ Callback(OverlayPlugin plugin) {
+ mPlugin = plugin;
+ }
+
+ @Override
+ public void onHoldStatusBarOpenChange() {
+ if (mPlugin.holdStatusBarOpen()) {
+ mOverlays.add(mPlugin);
+ } else {
+ mOverlays.remove(mPlugin);
+ }
+ mainHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Dependency.get(StatusBarWindowController.class)
+ .setStateListener(b -> mOverlays.forEach(
+ o -> o.setCollapseDesired(b)));
+ Dependency.get(StatusBarWindowController.class)
+ .setForcePluginOpen(mOverlays.size() != 0);
+ }
+ });
+ }
+ }
}, OverlayPlugin.class, true /* Allow multiple plugins */);
mServicesStarted = true;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 7094d28c29f5..ac4a93ba7fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -46,7 +46,7 @@ class Bubble {
private long mLastUpdated;
private long mLastAccessed;
- private static String groupId(NotificationEntry entry) {
+ public static String groupId(NotificationEntry entry) {
UserHandle user = entry.notification.getUser();
return user.getIdentifier() + "|" + entry.notification.getPackageName();
}
@@ -120,11 +120,28 @@ class Bubble {
}
}
+ /**
+ * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
+ */
public long getLastActivity() {
return Math.max(mLastUpdated, mLastAccessed);
}
/**
+ * @return the timestamp in milliseconds of the most recent notification entry for this bubble
+ */
+ public long getLastUpdateTime() {
+ return mLastUpdated;
+ }
+
+ /**
+ * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
+ */
+ public long getLastAccessTime() {
+ return mLastAccessed;
+ }
+
+ /**
* Should be invoked whenever a Bubble is accessed (selected while expanded).
*/
void markAsAccessedAt(long lastAccessedMillis) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7d189b28aa5e..2d0944ad246f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -29,6 +29,9 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.Nullable;
@@ -73,6 +76,7 @@ import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.util.List;
import javax.inject.Inject;
@@ -88,11 +92,12 @@ import javax.inject.Singleton;
public class BubbleController implements ConfigurationController.ConfigurationListener {
private static final String TAG = "BubbleController";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@Retention(SOURCE)
@IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE})
+ @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {}
static final int DISMISS_USER_GESTURE = 1;
@@ -510,6 +515,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onOrderChanged(List<Bubble> bubbles) {
+ if (mStackView != null) {
+ mStackView.updateBubbleOrder(bubbles);
+ }
}
@Override
@@ -527,13 +535,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
@Override
- public void showFlyoutText(Bubble bubble, String text) {
- if (mStackView != null) {
- mStackView.animateInFlyoutForBubble(bubble);
- }
- }
-
- @Override
public void apply() {
mNotificationEntryManager.updateNotifications();
updateVisibility();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index f15ba6ee673b..9156e06fe54e 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -23,6 +23,7 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.util.Log;
+import android.util.Pair;
import androidx.annotation.Nullable;
@@ -53,10 +54,10 @@ public class BubbleData {
private static final int MAX_BUBBLES = 5;
- private static final Comparator<Bubble> BUBBLES_BY_LAST_ACTIVITY_DESCENDING =
- Comparator.comparing(Bubble::getLastActivity).reversed();
+ private static final Comparator<Bubble> BUBBLES_BY_SORT_KEY_DESCENDING =
+ Comparator.comparing(BubbleData::sortKey).reversed();
- private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_LAST_ACTIVITY_DESCENDING =
+ private static final Comparator<Map.Entry<String, Long>> GROUPS_BY_MAX_SORT_KEY_DESCENDING =
Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();
/**
@@ -105,9 +106,6 @@ public class BubbleData {
*/
void onExpandedChanged(boolean expanded);
- /** Flyout text should animate in, showing the given text. */
- void showFlyoutText(Bubble bubble, String text);
-
/** Commit any pending operations (since last call of apply()) */
void apply();
}
@@ -121,15 +119,19 @@ public class BubbleData {
private Bubble mSelectedBubble;
private boolean mExpanded;
- // TODO: ensure this is invalidated at the appropriate time
- private int mSelectedBubbleExpandedPosition = -1;
+ // State tracked during an operation -- keeps track of what listener events to dispatch.
+ private boolean mExpandedChanged;
+ private boolean mOrderChanged;
+ private boolean mSelectionChanged;
+ private Bubble mUpdatedBubble;
+ private Bubble mAddedBubble;
+ private final List<Pair<Bubble, Integer>> mRemovedBubbles = new ArrayList<>();
private TimeSource mTimeSource = System::currentTimeMillis;
@Nullable
private Listener mListener;
- @VisibleForTesting
@Inject
public BubbleData(Context context) {
mContext = context;
@@ -154,18 +156,19 @@ public class BubbleData {
}
public void setExpanded(boolean expanded) {
- if (setExpandedInternal(expanded)) {
- dispatchApply();
+ if (DEBUG) {
+ Log.d(TAG, "setExpanded: " + expanded);
}
+ setExpandedInternal(expanded);
+ dispatchPendingChanges();
}
public void setSelectedBubble(Bubble bubble) {
if (DEBUG) {
Log.d(TAG, "setSelectedBubble: " + bubble);
}
- if (setSelectedBubbleInternal(bubble)) {
- dispatchApply();
- }
+ setSelectedBubbleInternal(bubble);
+ dispatchPendingChanges();
}
public void notificationEntryUpdated(NotificationEntry entry) {
@@ -177,12 +180,12 @@ public class BubbleData {
// Create a new bubble
bubble = new Bubble(entry, this::onBubbleBlocked);
doAdd(bubble);
- dispatchOnBubbleAdded(bubble);
+ trim();
} else {
// Updates an existing bubble
bubble.setEntry(entry);
doUpdate(bubble);
- dispatchOnBubbleUpdated(bubble);
+ mUpdatedBubble = bubble;
}
if (shouldAutoExpand(entry)) {
setSelectedBubbleInternal(bubble);
@@ -192,7 +195,15 @@ public class BubbleData {
} else if (mSelectedBubble == null) {
setSelectedBubbleInternal(bubble);
}
- dispatchApply();
+ dispatchPendingChanges();
+ }
+
+ public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
+ if (DEBUG) {
+ Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
+ }
+ doRemove(entry.key, reason);
+ dispatchPendingChanges();
}
private void doAdd(Bubble bubble) {
@@ -202,14 +213,21 @@ public class BubbleData {
int minInsertPoint = 0;
boolean newGroup = !hasBubbleWithGroupId(bubble.getGroupId());
if (isExpanded()) {
- // first bubble of a group goes to the end, otherwise it goes within the existing group
- minInsertPoint =
- newGroup ? mBubbles.size() : findFirstIndexForGroup(bubble.getGroupId());
+ // first bubble of a group goes to the beginning, otherwise within the existing group
+ minInsertPoint = newGroup ? 0 : findFirstIndexForGroup(bubble.getGroupId());
+ }
+ if (insertBubble(minInsertPoint, bubble) < mBubbles.size() - 1) {
+ mOrderChanged = true;
}
- insertBubble(minInsertPoint, bubble);
+ mAddedBubble = bubble;
if (!isExpanded()) {
- packGroup(findFirstIndexForGroup(bubble.getGroupId()));
+ mOrderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
+ // Top bubble becomes selected.
+ setSelectedBubbleInternal(mBubbles.get(0));
}
+ }
+
+ private void trim() {
if (mBubbles.size() > MAX_BUBBLES) {
mBubbles.stream()
// sort oldest first (ascending lastActivity)
@@ -217,10 +235,7 @@ public class BubbleData {
// skip the selected bubble
.filter((b) -> !b.equals(mSelectedBubble))
.findFirst()
- .ifPresent((b) -> {
- doRemove(b.getKey(), BubbleController.DISMISS_AGED);
- dispatchApply();
- });
+ .ifPresent((b) -> doRemove(b.getKey(), BubbleController.DISMISS_AGED));
}
}
@@ -229,43 +244,48 @@ public class BubbleData {
Log.d(TAG, "doUpdate: " + bubble);
}
if (!isExpanded()) {
- // while collapsed, update causes re-sort
+ // while collapsed, update causes re-pack
+ int prevPos = mBubbles.indexOf(bubble);
mBubbles.remove(bubble);
- insertBubble(0, bubble);
- packGroup(findFirstIndexForGroup(bubble.getGroupId()));
- }
- }
-
- public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
- if (DEBUG) {
- Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
+ int newPos = insertBubble(0, bubble);
+ if (prevPos != newPos) {
+ packGroup(newPos);
+ mOrderChanged = true;
+ }
+ setSelectedBubbleInternal(mBubbles.get(0));
}
- doRemove(entry.key, reason);
- dispatchApply();
}
private void doRemove(String key, @DismissReason int reason) {
int indexToRemove = indexForKey(key);
- if (indexToRemove >= 0) {
- Bubble bubbleToRemove = mBubbles.get(indexToRemove);
- if (mBubbles.size() == 1) {
- // Going to become empty, handle specially.
- setExpandedInternal(false);
- setSelectedBubbleInternal(null);
- }
- mBubbles.remove(indexToRemove);
- dispatchOnBubbleRemoved(bubbleToRemove, reason);
-
- // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
- if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
- // Move selection to the new bubble at the same position.
- int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
- Bubble newSelected = mBubbles.get(newIndex);
- setSelectedBubbleInternal(newSelected);
- }
- bubbleToRemove.setDismissed();
- maybeSendDeleteIntent(reason, bubbleToRemove.entry);
+ if (indexToRemove == -1) {
+ return;
+ }
+ Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ if (mBubbles.size() == 1) {
+ // Going to become empty, handle specially.
+ setExpandedInternal(false);
+ setSelectedBubbleInternal(null);
+ }
+ if (indexToRemove < mBubbles.size() - 1) {
+ // Removing anything but the last bubble means positions will change.
+ mOrderChanged = true;
+ }
+ mBubbles.remove(indexToRemove);
+ mRemovedBubbles.add(Pair.create(bubbleToRemove, reason));
+ if (!isExpanded()) {
+ mOrderChanged |= repackAll();
+ }
+
+ // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
+ if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
+ // Move selection to the new bubble at the same position.
+ int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
+ Bubble newSelected = mBubbles.get(newIndex);
+ setSelectedBubbleInternal(newSelected);
}
+ bubbleToRemove.setDismissed();
+ maybeSendDeleteIntent(reason, bubbleToRemove.entry);
}
public void dismissAll(@DismissReason int reason) {
@@ -281,56 +301,71 @@ public class BubbleData {
Bubble bubble = mBubbles.remove(0);
bubble.setDismissed();
maybeSendDeleteIntent(reason, bubble.entry);
- dispatchOnBubbleRemoved(bubble, reason);
+ mRemovedBubbles.add(Pair.create(bubble, reason));
}
- dispatchApply();
+ dispatchPendingChanges();
}
- private void dispatchApply() {
- if (mListener != null) {
- mListener.apply();
+
+ private void dispatchPendingChanges() {
+ if (mListener == null) {
+ mExpandedChanged = false;
+ mAddedBubble = null;
+ mSelectionChanged = false;
+ mRemovedBubbles.clear();
+ mUpdatedBubble = null;
+ mOrderChanged = false;
+ return;
}
- }
+ boolean anythingChanged = false;
- private void dispatchOnBubbleAdded(Bubble bubble) {
- if (mListener != null) {
- mListener.onBubbleAdded(bubble);
+ if (mAddedBubble != null) {
+ mListener.onBubbleAdded(mAddedBubble);
+ mAddedBubble = null;
+ anythingChanged = true;
}
- }
- private void dispatchOnBubbleRemoved(Bubble bubble, @DismissReason int reason) {
- if (mListener != null) {
- mListener.onBubbleRemoved(bubble, reason);
+ // Compat workaround: Always collapse first.
+ if (mExpandedChanged && !mExpanded) {
+ mListener.onExpandedChanged(mExpanded);
+ mExpandedChanged = false;
+ anythingChanged = true;
}
- }
- private void dispatchOnExpandedChanged(boolean expanded) {
- if (mListener != null) {
- mListener.onExpandedChanged(expanded);
+ if (mSelectionChanged) {
+ mListener.onSelectionChanged(mSelectedBubble);
+ mSelectionChanged = false;
+ anythingChanged = true;
}
- }
- private void dispatchOnSelectionChanged(@Nullable Bubble bubble) {
- if (mListener != null) {
- mListener.onSelectionChanged(bubble);
+ if (!mRemovedBubbles.isEmpty()) {
+ for (Pair<Bubble, Integer> removed : mRemovedBubbles) {
+ mListener.onBubbleRemoved(removed.first, removed.second);
+ }
+ mRemovedBubbles.clear();
+ anythingChanged = true;
}
- }
- private void dispatchOnBubbleUpdated(Bubble bubble) {
- if (mListener != null) {
- mListener.onBubbleUpdated(bubble);
+ if (mUpdatedBubble != null) {
+ mListener.onBubbleUpdated(mUpdatedBubble);
+ mUpdatedBubble = null;
+ anythingChanged = true;
}
- }
- private void dispatchOnOrderChanged(List<Bubble> bubbles) {
- if (mListener != null) {
- mListener.onOrderChanged(bubbles);
+ if (mOrderChanged) {
+ mListener.onOrderChanged(mBubbles);
+ mOrderChanged = false;
+ anythingChanged = true;
}
- }
- private void dispatchShowFlyoutText(Bubble bubble, String text) {
- if (mListener != null) {
- mListener.showFlyoutText(bubble, text);
+ if (mExpandedChanged) {
+ mListener.onExpandedChanged(mExpanded);
+ mExpandedChanged = false;
+ anythingChanged = true;
+ }
+
+ if (anythingChanged) {
+ mListener.apply();
}
}
@@ -339,29 +374,25 @@ public class BubbleData {
* the value changes.
*
* @param bubble the new selected bubble
- * @return true if the state changed as a result
*/
- private boolean setSelectedBubbleInternal(@Nullable Bubble bubble) {
+ private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
if (DEBUG) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
if (Objects.equals(bubble, mSelectedBubble)) {
- return false;
+ return;
}
if (bubble != null && !mBubbles.contains(bubble)) {
Log.e(TAG, "Cannot select bubble which doesn't exist!"
+ " (" + bubble + ") bubbles=" + mBubbles);
- return false;
+ return;
}
if (mExpanded && bubble != null) {
bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
}
mSelectedBubble = bubble;
- dispatchOnSelectionChanged(mSelectedBubble);
- if (!mExpanded || mSelectedBubble == null) {
- mSelectedBubbleExpandedPosition = -1;
- }
- return true;
+ mSelectionChanged = true;
+ return;
}
/**
@@ -369,37 +400,53 @@ public class BubbleData {
* the value changes.
*
* @param shouldExpand the new requested state
- * @return true if the state changed as a result
*/
- private boolean setExpandedInternal(boolean shouldExpand) {
+ private void setExpandedInternal(boolean shouldExpand) {
if (DEBUG) {
Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
}
if (mExpanded == shouldExpand) {
- return false;
- }
- if (mSelectedBubble != null) {
- mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+ return;
}
if (shouldExpand) {
if (mBubbles.isEmpty()) {
Log.e(TAG, "Attempt to expand stack when empty!");
- return false;
+ return;
}
if (mSelectedBubble == null) {
Log.e(TAG, "Attempt to expand stack without selected bubble!");
- return false;
+ return;
+ }
+ mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
+ mOrderChanged |= repackAll();
+ } else if (!mBubbles.isEmpty()) {
+ // Apply ordering and grouping rules from expanded -> collapsed, then save
+ // the result.
+ mOrderChanged |= repackAll();
+ // Save the state which should be returned to when expanded (with no other changes)
+
+ if (mBubbles.indexOf(mSelectedBubble) > 0) {
+ // Move the selected bubble to the top while collapsed.
+ if (!mSelectedBubble.isOngoing() && mBubbles.get(0).isOngoing()) {
+ // The selected bubble cannot be raised to the first position because
+ // there is an ongoing bubble there. Instead, force the top ongoing bubble
+ // to become selected.
+ setSelectedBubbleInternal(mBubbles.get(0));
+ } else {
+ // Raise the selected bubble (and it's group) up to the front so the selected
+ // bubble remains on top.
+ mBubbles.remove(mSelectedBubble);
+ mBubbles.add(0, mSelectedBubble);
+ packGroup(0);
+ }
}
- } else {
- repackAll();
}
mExpanded = shouldExpand;
- dispatchOnExpandedChanged(mExpanded);
- return true;
+ mExpandedChanged = true;
}
private static long sortKey(Bubble bubble) {
- long key = bubble.getLastActivity();
+ long key = bubble.getLastUpdateTime();
if (bubble.isOngoing()) {
// Set 2nd highest bit (signed long int), to partition between ongoing and regular
key |= 0x4000000000000000L;
@@ -456,8 +503,9 @@ public class BubbleData {
* unchanged. Relative order of any other bubbles are also unchanged.
*
* @param position the position of the first bubble for the group
+ * @return true if the position of any bubbles has changed as a result
*/
- private void packGroup(int position) {
+ private boolean packGroup(int position) {
if (DEBUG) {
Log.d(TAG, "packGroup: position=" + position);
}
@@ -471,16 +519,27 @@ public class BubbleData {
moving.add(0, mBubbles.get(i));
}
}
+ if (moving.isEmpty()) {
+ return false;
+ }
mBubbles.removeAll(moving);
mBubbles.addAll(position + 1, moving);
+ return true;
}
- private void repackAll() {
+ /**
+ * This applies a full sort and group pass to all existing bubbles. The bubbles are grouped
+ * by groupId. Each group is then sorted by the max(lastUpdated) time of it's bubbles. Bubbles
+ * within each group are then sorted by lastUpdated descending.
+ *
+ * @return true if the position of any bubbles changed as a result
+ */
+ private boolean repackAll() {
if (DEBUG) {
Log.d(TAG, "repackAll()");
}
if (mBubbles.isEmpty()) {
- return;
+ return false;
}
Map<String, Long> groupLastActivity = new HashMap<>();
for (Bubble bubble : mBubbles) {
@@ -494,7 +553,7 @@ public class BubbleData {
// Sort groups by their most recently active bubble
List<String> groupsByMostRecentActivity =
groupLastActivity.entrySet().stream()
- .sorted(GROUPS_BY_LAST_ACTIVITY_DESCENDING)
+ .sorted(GROUPS_BY_MAX_SORT_KEY_DESCENDING)
.map(Map.Entry::getKey)
.collect(toList());
@@ -504,10 +563,14 @@ public class BubbleData {
for (String appId : groupsByMostRecentActivity) {
mBubbles.stream()
.filter((b) -> b.getGroupId().equals(appId))
- .sorted(BUBBLES_BY_LAST_ACTIVITY_DESCENDING)
+ .sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
.forEachOrdered(repacked::add);
}
+ if (repacked.equals(mBubbles)) {
+ return false;
+ }
mBubbles = repacked;
+ return true;
}
private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
@@ -527,21 +590,25 @@ public class BubbleData {
}
private void onBubbleBlocked(NotificationEntry entry) {
- boolean changed = false;
- final String blockedPackage = entry.notification.getPackageName();
+ final String blockedGroupId = Bubble.groupId(entry);
+ int selectedIndex = mBubbles.indexOf(mSelectedBubble);
for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) {
Bubble bubble = i.next();
- if (bubble.getPackageName().equals(blockedPackage)) {
+ if (bubble.getGroupId().equals(blockedGroupId)) {
+ mRemovedBubbles.add(Pair.create(bubble, BubbleController.DISMISS_BLOCKED));
i.remove();
- // TODO: handle removal of selected bubble, and collapse safely if emptied (see
- // dismissAll)
- dispatchOnBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED);
- changed = true;
}
}
- if (changed) {
- dispatchApply();
- }
+ if (mBubbles.isEmpty()) {
+ setExpandedInternal(false);
+ setSelectedBubbleInternal(null);
+ } else if (!mBubbles.contains(mSelectedBubble)) {
+ // choose a new one
+ int newIndex = Math.min(selectedIndex, mBubbles.size() - 1);
+ Bubble newSelected = mBubbles.get(newIndex);
+ setSelectedBubbleInternal(newSelected);
+ }
+ dispatchPendingChanges();
}
private int indexForKey(String key) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 123d73dc6432..35dc1775cb7f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -549,6 +549,7 @@ public class BubbleStackView extends FrameLayout {
mBubbleContainer.addView(bubble.iconView, 0,
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
ViewClippingUtil.setClippingDeactivated(bubble.iconView, true, mClippingParameters);
+ animateInFlyoutForBubble(bubble);
requestUpdate();
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
}
@@ -570,10 +571,19 @@ public class BubbleStackView extends FrameLayout {
// via BubbleData.Listener
void updateBubble(Bubble bubble) {
+ animateInFlyoutForBubble(bubble);
requestUpdate();
logBubbleEvent(bubble, StatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
}
+ public void updateBubbleOrder(List<Bubble> bubbles) {
+ for (int i = 0; i < bubbles.size(); i++) {
+ Bubble bubble = bubbles.get(i);
+ mBubbleContainer.moveViewTo(bubble.iconView, i);
+ }
+ }
+
+
/**
* Changes the currently selected bubble. If the stack is already expanded, the newly selected
* bubble will be shown immediately. This does not change the expanded state or change the
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
index 9052093346cb..a4bd24416f61 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingManagerImpl.java
@@ -22,6 +22,7 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
+import android.hardware.biometrics.BiometricSourceType;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
@@ -33,6 +34,8 @@ import android.view.MotionEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.Dependency;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.analytics.DataCollector;
@@ -80,6 +83,7 @@ public class FalsingManagerImpl implements FalsingManagerFactory.FalsingManager
private boolean mBouncerOffOnDown = false;
private boolean mSessionActive = false;
private boolean mIsTouchScreen = true;
+ private boolean mJustUnlockedWithFace = false;
private int mState = StatusBarState.SHADE;
private boolean mScreenOn;
private boolean mShowingAod;
@@ -120,6 +124,17 @@ public class FalsingManagerImpl implements FalsingManagerFactory.FalsingManager
updateConfiguration();
}
};
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onBiometricAuthenticated(int userId,
+ BiometricSourceType biometricSourceType) {
+ if (userId == KeyguardUpdateMonitor.getCurrentUser()
+ && biometricSourceType == BiometricSourceType.FACE) {
+ mJustUnlockedWithFace = true;
+ }
+ }
+ };
FalsingManagerImpl(Context context) {
mContext = context;
@@ -138,6 +153,7 @@ public class FalsingManagerImpl implements FalsingManagerFactory.FalsingManager
updateConfiguration();
Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
+ KeyguardUpdateMonitor.getInstance(context).registerCallback(mKeyguardUpdateCallback);
}
private void updateConfiguration() {
@@ -199,6 +215,7 @@ public class FalsingManagerImpl implements FalsingManagerFactory.FalsingManager
}
mBouncerOn = false;
mSessionActive = true;
+ mJustUnlockedWithFace = false;
mIsFalseTouchCalls = 0;
if (mHumanInteractionClassifier.isEnabled()) {
@@ -285,6 +302,11 @@ public class FalsingManagerImpl implements FalsingManagerFactory.FalsingManager
// anti-falsed.
return false;
}
+ if (mJustUnlockedWithFace) {
+ // Unlocking with face is a strong user presence signal, we can assume the user
+ // is present until the next session starts.
+ return false;
+ }
mIsFalseTouchCalls++;
boolean isFalse = mHumanInteractionClassifier.isFalseTouch();
if (!isFalse) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index acacc8fbb917..033c4fbed6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -40,6 +40,7 @@ import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.FloatProperty;
import android.util.Log;
import android.util.Property;
@@ -73,7 +74,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
* want to scale them (in a way that doesn't require an asset dump) down 2dp. So
* 17dp * (15 / 17) = 15dp, the new height.
*/
- private static final float SYSTEM_ICON_SCALE = 15.f / 17.f;
+ private static final float SYSTEM_ICON_DESIRED_HEIGHT = 15f;
+ private static final float SYSTEM_ICON_INTRINSIC_HEIGHT = 17f;
+ private static final float SYSTEM_ICON_SCALE =
+ SYSTEM_ICON_DESIRED_HEIGHT / SYSTEM_ICON_INTRINSIC_HEIGHT;
private final int ANIMATION_DURATION_FAST = 100;
public static final int STATE_ICON = 0;
@@ -202,8 +206,25 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
updatePivot();
}
+ // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
+ // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
private void updateIconScaleForSystemIcons() {
- mIconScale = SYSTEM_ICON_SCALE;
+ float iconHeight = getIconHeightInDps();
+ if (iconHeight != 0) {
+ mIconScale = SYSTEM_ICON_DESIRED_HEIGHT / iconHeight;
+ } else {
+ mIconScale = SYSTEM_ICON_SCALE;
+ }
+ }
+
+ private float getIconHeightInDps() {
+ Drawable d = getDrawable();
+ if (d != null) {
+ return ((float) getDrawable().getIntrinsicHeight() * DisplayMetrics.DENSITY_DEFAULT)
+ / mDensity;
+ } else {
+ return SYSTEM_ICON_INTRINSIC_HEIGHT;
+ }
}
public float getIconScaleFullyDark() {
@@ -221,8 +242,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
if (density != mDensity) {
mDensity = density;
reloadDimens();
- maybeUpdateIconScaleDimens();
updateDrawable();
+ maybeUpdateIconScaleDimens();
}
boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
@@ -305,6 +326,8 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
if (!updateDrawable(false /* no clear */)) return false;
// we have to clear the grayscale tag since it may have changed
setTag(R.id.icon_is_grayscale, null);
+ // Maybe set scale based on icon height
+ maybeUpdateIconScaleDimens();
}
if (!levelEquals) {
setImageLevel(icon.iconLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 27368deac847..50e406f5936e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -86,7 +86,7 @@ public class PhoneStatusBarPolicy
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final int LOCATION_STATUS_ICON_ID = R.drawable.stat_sys_location;
+ public static final int LOCATION_STATUS_ICON_ID = PrivacyType.TYPE_LOCATION.getIconId();
private final String mSlotCast;
private final String mSlotHotspot;
@@ -230,10 +230,10 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotDataSaver, false);
// privacy items
- mIconController.setIcon(mSlotMicrophone, R.drawable.stat_sys_mic_none,
+ mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
PrivacyType.TYPE_MICROPHONE.getName(mContext));
mIconController.setIconVisibility(mSlotMicrophone, false);
- mIconController.setIcon(mSlotCamera, R.drawable.stat_sys_camera,
+ mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
PrivacyType.TYPE_CAMERA.getName(mContext));
mIconController.setIconVisibility(mSlotCamera, false);
mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2d697e34c626..35a15167d207 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -296,22 +296,22 @@ public class BubbleControllerTest extends SysuiTestCase {
BubbleStackView stackView = mBubbleController.getStackView();
mBubbleController.expandStack();
assertTrue(mBubbleController.isStackExpanded());
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
-
- // First added is the one that is expanded
- assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
- // Switch which bubble is expanded
- mBubbleController.selectBubble(mRow2.getEntry().key);
- stackView.setExpandedBubble(mRow2.getEntry());
+ // Last added is the one that is expanded
assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
assertFalse(mRow2.getEntry().showInShadeWhenBubble());
+ // Switch which bubble is expanded
+ mBubbleController.selectBubble(mRow.getEntry().key);
+ stackView.setExpandedBubble(mRow.getEntry());
+ assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
+ assertFalse(mRow.getEntry().showInShadeWhenBubble());
+
// collapse for previous bubble
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
// expand for selected bubble
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
// Collapse
mBubbleController.collapseStack();
@@ -352,27 +352,27 @@ public class BubbleControllerTest extends SysuiTestCase {
mBubbleController.expandStack();
assertTrue(mBubbleController.isStackExpanded());
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
- // First added is the one that is expanded
- assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
- assertFalse(mRow.getEntry().showInShadeWhenBubble());
+ // Last added is the one that is expanded
+ assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
+ assertFalse(mRow2.getEntry().showInShadeWhenBubble());
// Dismiss currently expanded
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
BubbleController.DISMISS_USER_GESTURE);
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
- // Make sure next bubble is selected
- assertEquals(mRow2.getEntry(), stackView.getExpandedBubbleView().getEntry());
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().key);
+ // Make sure first bubble is selected
+ assertEquals(mRow.getEntry(), stackView.getExpandedBubbleView().getEntry());
+ verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().key);
// Dismiss that one
mBubbleController.removeBubble(stackView.getExpandedBubbleView().getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().key);
+ verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getEntry().key);
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
assertFalse(mBubbleController.hasBubbles());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index d6dac2f36ba1..33b2e6e59470 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -16,12 +16,18 @@
package com.android.systemui.bubbles;
+import static com.android.systemui.bubbles.BubbleController.DISMISS_AGED;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,6 +54,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -108,61 +116,11 @@ public class BubbleDataTest extends SysuiTestCase {
// Used by BubbleData to set lastAccessedTime
when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
mBubbleData.setTimeSource(mTimeSource);
- }
-
- private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
- return createBubbleEntry(userId, notifKey, packageName, 1000);
- }
-
- private void setPostTime(NotificationEntry entry, long postTime) {
- when(entry.notification.getPostTime()).thenReturn(postTime);
- }
-
- private void setOngoing(NotificationEntry entry, boolean ongoing) {
- if (ongoing) {
- entry.notification.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- } else {
- entry.notification.getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
- }
- }
- /**
- * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
- * required for BubbleData functionality and verification. NotificationTestHelper is used only
- * as a convenience to create a Notification w/BubbleMetadata.
- */
- private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
- long postTime) {
- // BubbleMetadata
- Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
- .setIntent(mExpandIntent)
- .setDeleteIntent(mDeleteIntent)
- .setIcon(Icon.createWithResource("", 0))
- .build();
- // Notification -> BubbleMetadata
- Notification notification = mNotificationTestHelper.createNotification(false,
- null /* groupKey */, bubbleMetadata);
-
- // StatusBarNotification
- StatusBarNotification sbn = mock(StatusBarNotification.class);
- when(sbn.getKey()).thenReturn(notifKey);
- when(sbn.getUser()).thenReturn(new UserHandle(userId));
- when(sbn.getPackageName()).thenReturn(packageName);
- when(sbn.getPostTime()).thenReturn(postTime);
- when(sbn.getNotification()).thenReturn(notification);
-
- // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
- return new NotificationEntry(sbn);
- }
-
- private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
- setPostTime(entry, postTime);
- mBubbleData.notificationEntryUpdated(entry);
- }
-
- private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
- when(mTimeSource.currentTimeMillis()).thenReturn(time);
- mBubbleData.setExpanded(shouldBeExpanded);
+ // Assert baseline starting state
+ assertThat(mBubbleData.hasBubbles()).isFalse();
+ assertThat(mBubbleData.isExpanded()).isFalse();
+ assertThat(mBubbleData.getSelectedBubble()).isNull();
}
@Test
@@ -171,8 +129,7 @@ public class BubbleDataTest extends SysuiTestCase {
mBubbleData.setListener(mListener);
// Test
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
// Verify
verify(mListener).onBubbleAdded(eq(mBubbleA1));
@@ -183,9 +140,9 @@ public class BubbleDataTest extends SysuiTestCase {
@Test
public void testRemoveBubble() {
// Setup
- mBubbleData.notificationEntryUpdated(mEntryA1);
- mBubbleData.notificationEntryUpdated(mEntryA2);
- mBubbleData.notificationEntryUpdated(mEntryA3);
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryA3, 3000);
mBubbleData.setListener(mListener);
// Test
@@ -193,515 +150,657 @@ public class BubbleDataTest extends SysuiTestCase {
// Verify
verify(mListener).onBubbleRemoved(eq(mBubbleA1), eq(BubbleController.DISMISS_USER_GESTURE));
- verify(mListener).onSelectionChanged(eq(mBubbleA2));
verify(mListener).apply();
}
+ // COLLAPSED / ADD
+
+ /**
+ * Verifies that the number of bubbles is not allowed to exceed the maximum. The limit is
+ * enforced by expiring the bubble which was least recently updated (lowest timestamp).
+ */
@Test
- public void test_collapsed_addBubble_atMaxBubbles_expiresLeastActive() {
- // Given
+ public void test_collapsed_addBubble_atMaxBubbles_expiresOldest() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
sendUpdatedEntryAtTime(mEntryA3, 3000);
sendUpdatedEntryAtTime(mEntryB1, 4000);
sendUpdatedEntryAtTime(mEntryB2, 5000);
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+ mBubbleData.setListener(mListener);
- // When
+ // Test
sendUpdatedEntryAtTime(mEntryC1, 6000);
-
- // Then
- // A2 is removed. A1 is oldest but is the selected bubble.
- assertThat(mBubbleData.getBubbles()).doesNotContain(mBubbleA2);
- }
-
- @Test
- public void test_collapsed_expand_whenEmpty_doesNothing() {
- assertThat(mBubbleData.hasBubbles()).isFalse();
- changeExpandedStateAtTime(true, 2000L);
-
- verify(mListener, never()).onExpandedChanged(anyBoolean());
- verify(mListener, never()).apply();
+ verify(mListener).onBubbleRemoved(eq(mBubbleA1), eq(DISMISS_AGED));
}
- // New bubble while stack is collapsed
+ /**
+ * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
+ * <p>
+ * Placement within the list is based on lastUpdate (post time of the notification), descending
+ * order (with most recent first).
+ *
+ * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
@Test
- public void test_collapsed_addBubble() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
+ public void test_collapsed_addBubble_sortAndGrouping() {
+ // Setup
+ mBubbleData.setListener(mListener);
- // When
+ // Test
sendUpdatedEntryAtTime(mEntryA1, 1000);
+ verify(mListener, never()).onOrderChanged(anyList());
+
+ reset(mListener);
sendUpdatedEntryAtTime(mEntryB1, 2000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA1)));
+
+ reset(mListener);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA1)));
- // Then
- // New bubbles move to front when collapsed, bringing bubbles from the same app along
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryA2, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1)));
}
- // New bubble while collapsed with ongoing bubble present
+ /**
+ * Verifies that new bubbles insert to the left when collapsed, carrying along grouped bubbles.
+ * Additionally, any bubble which is ongoing is considered "newer" than any non-ongoing bubble.
+ * <p>
+ * Because of the ongoing bubble, the new bubble cannot be placed in the first position. This
+ * causes the 'B' group to remain last, despite having a new button added.
+ *
+ * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
@Test
- public void test_collapsed_addBubble_withOngoing() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
+ public void test_collapsed_addBubble_sortAndGrouping_withOngoing() {
+ // Setup
+ mBubbleData.setListener(mListener);
- // When
+ // Test
setOngoing(mEntryA1, true);
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
- setPostTime(mEntryB2, 3000);
- mBubbleData.notificationEntryUpdated(mEntryB2);
- setPostTime(mEntryA2, 4000);
- mBubbleData.notificationEntryUpdated(mEntryA2);
-
- // Then
- // New bubbles move to front, but stay behind any ongoing bubbles.
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1));
- }
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ verify(mListener, never()).onOrderChanged(anyList());
- // Remove the selected bubble (middle bubble), while the stack is collapsed.
- @Test
- public void test_collapsed_removeBubble_selected() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ verify(mListener, never()).onOrderChanged(eq(listOf(mBubbleA1, mBubbleB1)));
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryB2, 3000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleB2, mBubbleB1)));
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryA2, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1)));
+ }
- setPostTime(mEntryB2, 3000);
- mBubbleData.notificationEntryUpdated(mEntryB2);
+ /**
+ * Verifies that new bubbles become the selected bubble when they appear when the stack is in
+ * the collapsed state.
+ *
+ * @see #test_collapsed_updateBubble_selectionChanges()
+ * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
+ */
+ @Test
+ public void test_collapsed_addBubble_selectionChanges() {
+ // Setup
+ mBubbleData.setListener(mListener);
- setPostTime(mEntryA2, 4000);
- mBubbleData.notificationEntryUpdated(mEntryA2);
+ // Test
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ verify(mListener).onSelectionChanged(eq(mBubbleA1));
- mBubbleData.setSelectedBubble(mBubbleB2);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ verify(mListener).onSelectionChanged(eq(mBubbleB1));
- // When
- mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryB2, 3000);
+ verify(mListener).onSelectionChanged(eq(mBubbleB2));
- // Then
- // (Selection remains in the same position)
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryA2, 4000);
+ verify(mListener).onSelectionChanged(eq(mBubbleA2));
}
-
- // Remove the selected bubble (last bubble), while the stack is collapsed.
+ /**
+ * Verifies that while collapsed, the selection will not change if the selected bubble is
+ * ongoing. It remains the top bubble and as such remains selected.
+ *
+ * @see #test_collapsed_addBubble_selectionChanges()
+ */
@Test
- public void test_collapsed_removeSelectedBubble_inLastPosition() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_collapsed_addBubble_noSelectionChanges_withOngoing() {
+ // Setup
+ setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
+ assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+ mBubbleData.setListener(mListener);
+
+ // Test
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- mBubbleData.setSelectedBubble(mBubbleB1);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
-
- // When
- mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
-
- // Then
- // (Selection is forced to move to previous)
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB2);
+ verify(mListener, never()).onSelectionChanged(any(Bubble.class));
+ assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged
}
- @Test
- public void test_collapsed_addBubble_ongoing() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
- // When
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
-
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
-
- setPostTime(mEntryB2, 3000);
- setOngoing(mEntryB2, true);
- mBubbleData.notificationEntryUpdated(mEntryB2);
-
- setPostTime(mEntryA2, 4000);
- mBubbleData.notificationEntryUpdated(mEntryA2);
-
- // Then
- // New bubbles move to front, but stay behind any ongoing bubbles.
- // Does not break grouping. (A2 is inserted after B1, even though it's newer).
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
- }
+ // COLLAPSED / REMOVE
+ /**
+ * Verifies that groups may reorder when bubbles are removed, while the stack is in the
+ * collapsed state.
+ */
@Test
- public void test_collapsed_removeBubble() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_collapsed_removeBubble_sortAndGrouping() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- // When
- mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ mBubbleData.setListener(mListener);
- // Then
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB1));
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA1)));
}
- @Test
- public void test_collapsed_updateBubble() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
+ /**
+ * Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
+ * cause other bubbles to change position.
+ */
+ @Test
+ public void test_collapsed_removeOldestBubble_doesNotCallOnOrderChanged() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
-
- // When
- sendUpdatedEntryAtTime(mEntryB2, 5000);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ mBubbleData.setListener(mListener);
- // Then
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener, never()).onOrderChanged(anyList());
}
+ /**
+ * Verifies that bubble ordering reverts to normal when an ongoing bubble is removed. A group
+ * which has a newer bubble may move to the front after the ongoing bubble is removed.
+ */
@Test
- public void test_collapsed_updateBubble_withOngoing() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
-
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
-
- setPostTime(mEntryB2, 3000);
- mBubbleData.notificationEntryUpdated(mEntryB2);
-
- setOngoing(mEntryA2, true);
- setPostTime(mEntryA2, 4000);
- mBubbleData.notificationEntryUpdated(mEntryA2);
-
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
-
- // When
- setPostTime(mEntryB1, 5000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
+ public void test_collapsed_removeBubble_sortAndGrouping_withOngoing() {
+ // Setup
+ setOngoing(mEntryA1, true);
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000); // [A1*, A2, B2, B1]
+ mBubbleData.setListener(mListener);
- // Then
- // A2 remains in first position, due to being ongoing. B1 moves before B2, Group A
- // remains before group B.
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2));
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB2, mBubbleB1, mBubbleA2)));
}
+ /**
+ * Verifies that when the selected bubble is removed with the stack in the collapsed state,
+ * the selection moves to the next most-recently updated bubble.
+ */
@Test
- public void test_collapse_afterUpdateWhileExpanded() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_collapsed_removeBubble_selectionChanges() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ mBubbleData.setListener(mListener);
- changeExpandedStateAtTime(true, 5000L);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
+ verify(mListener).onSelectionChanged(eq(mBubbleB2));
+ }
- sendUpdatedEntryAtTime(mEntryB1, 6000);
+ // COLLAPSED / UPDATE
- // (No reordering while expanded)
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ /**
+ * Verifies that bubble and group ordering may change with updates while the stack is in the
+ * collapsed state.
+ */
+ @Test
+ public void test_collapsed_updateBubble_orderAndGrouping() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryB2, 3000);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ mBubbleData.setListener(mListener);
- // When
- changeExpandedStateAtTime(false, 7000L);
+ // Test
+ sendUpdatedEntryAtTime(mEntryB1, 5000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1)));
- // Then
- // A1 moves to front on collapse, since it is the selected bubble (and most recently
- // accessed).
- // A2 moves next to A1 to maintain grouping.
- // B1 moves in front of B2, since it received an update while expanded
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2));
+ reset(mListener);
+ sendUpdatedEntryAtTime(mEntryA1, 6000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2)));
}
+ /**
+ * Verifies that selection tracks the most recently updated bubble while in the collapsed state.
+ */
@Test
- public void test_collapse_afterUpdateWhileExpanded_withOngoing() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_collapsed_updateBubble_selectionChanges() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
-
- setOngoing(mEntryB2, true);
sendUpdatedEntryAtTime(mEntryB2, 3000);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ mBubbleData.setListener(mListener);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
-
- changeExpandedStateAtTime(true, 5000L);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+ // Test
+ sendUpdatedEntryAtTime(mEntryB1, 5000);
+ verify(mListener).onSelectionChanged(eq(mBubbleB1));
+ reset(mListener);
sendUpdatedEntryAtTime(mEntryA1, 6000);
-
- // No reordering if expanded
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
-
- // When
- changeExpandedStateAtTime(false, 7000L);
-
- // Then
- // B2 remains in first position because it is ongoing.
- // B1 remains grouped with B2
- // A1 moves in front of A2, since it is more recently updated (and is selected).
- // B1 moves in front of B2, since it has more recent activity.
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA1, mBubbleA2));
+ verify(mListener).onSelectionChanged(eq(mBubbleA1));
}
+ /**
+ * Verifies that selection does not change in response to updates when collapsed, if the
+ * selected bubble is ongoing.
+ */
@Test
- public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
+ // Setup
+ setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
-
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
-
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
- mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
- mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
+ mBubbleData.setListener(mListener);
- assertThat(mBubbleData.getSelectedBubble()).isNull();
+ // Test
+ sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
+ verify(mListener, never()).onSelectionChanged(any(Bubble.class));
}
+ /**
+ * Verifies that a request to expand the stack has no effect if there are no bubbles.
+ */
@Test
- public void test_expanded_addBubble_atMaxBubbles_expiresLeastActive() {
- // Given
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
-
+ public void test_collapsed_expansion_whenEmpty_doesNothing() {
+ assertThat(mBubbleData.hasBubbles()).isFalse();
changeExpandedStateAtTime(true, 2000L);
- assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(2000);
- sendUpdatedEntryAtTime(mEntryA2, 3000);
- sendUpdatedEntryAtTime(mEntryA3, 4000);
- sendUpdatedEntryAtTime(mEntryB1, 5000);
- sendUpdatedEntryAtTime(mEntryB2, 6000);
- sendUpdatedEntryAtTime(mEntryB3, 7000);
-
-
- // Then
- // A1 would be removed, but it is selected and expanded, so it should not go away.
- // Instead, fall through to removing A2 (the next oldest).
- assertThat(mBubbleData.getBubbles()).doesNotContain(mEntryA2);
+ verify(mListener, never()).onExpandedChanged(anyBoolean());
+ verify(mListener, never()).apply();
}
@Test
- public void test_expanded_removeLastBubble_collapsesStack() {
- // Given
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
+ public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
- setPostTime(mEntryB2, 3000);
- mBubbleData.notificationEntryUpdated(mEntryC1);
+ // Verify the selection was cleared.
+ verify(mListener).onSelectionChanged(isNull());
+ }
- mBubbleData.setExpanded(true);
+ // EXPANDED / ADD
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
- mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
- mBubbleData.notificationEntryRemoved(mEntryC1, BubbleController.DISMISS_USER_GESTURE);
+ /**
+ * Verifies that bubbles added as part of a new group insert before existing groups while
+ * expanded.
+ * <p>
+ * Placement within the list is based on lastUpdate (post time of the notification), descending
+ * order (with most recent first).
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
+ @Test
+ public void test_expanded_addBubble_sortAndGrouping_newGroup() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
+ changeExpandedStateAtTime(true, 4000L);
+ mBubbleData.setListener(mListener);
- assertThat(mBubbleData.isExpanded()).isFalse();
- assertThat(mBubbleData.getSelectedBubble()).isNull();
+ // Test
+ sendUpdatedEntryAtTime(mEntryC1, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1)));
}
- // Bubbles do not reorder while expanded
+ /**
+ * Verifies that bubbles added as part of a new group insert before existing groups while
+ * expanded, but not before any groups with ongoing bubbles.
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping_withOngoing()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
@Test
- public void test_expanded_selection_collapseToTop() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
-
+ public void test_expanded_addBubble_sortAndGrouping_newGroup_withOngoing() {
+ // Setup
+ setOngoing(mEntryA1, true);
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
- sendUpdatedEntryAtTime(mEntryB1, 3000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
+ changeExpandedStateAtTime(true, 4000L);
+ mBubbleData.setListener(mListener);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB1, mBubbleA2, mBubbleA1));
+ // Test
+ sendUpdatedEntryAtTime(mEntryC1, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA1, mBubbleA2, mBubbleC1, mBubbleB1)));
+ }
+ /**
+ * Verifies that bubbles added as part of an existing group insert to the beginning of that
+ * group. The order of groups within the list must not change while in the expanded state.
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping()
+ * @see #test_expanded_addBubble_sortAndGrouping_newGroup()
+ */
+ @Test
+ public void test_expanded_addBubble_sortAndGrouping_existingGroup() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000); // [B1, A2, A1]
changeExpandedStateAtTime(true, 4000L);
+ mBubbleData.setListener(mListener);
- // regrouping only happens when collapsed (after new or update) or expanded->collapsed
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB1, mBubbleA2, mBubbleA1));
+ // Test
+ sendUpdatedEntryAtTime(mEntryA3, 4000);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1)));
+ }
- changeExpandedStateAtTime(false, 6000L);
+ // EXPANDED / UPDATE
- // A1 is still selected and it's lastAccessed time has been updated
- // on collapse, sorting is applied, keeping the selected bubble at the front
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA1, mBubbleA2, mBubbleB1));
+ /**
+ * Verifies that updates to bubbles while expanded do not result in any change to sorting
+ * or grouping of bubbles or sorting of groups.
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
+ @Test
+ public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
+ changeExpandedStateAtTime(true, 5000L);
+ mBubbleData.setListener(mListener);
+
+ // Test
+ sendUpdatedEntryAtTime(mEntryA1, 4000);
+ verify(mListener, never()).onOrderChanged(anyList());
}
- // New bubble from new app while stack is expanded
+ /**
+ * Verifies that updates to bubbles while expanded do not result in any change to selection.
+ *
+ * @see #test_collapsed_addBubble_selectionChanges()
+ * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
+ */
@Test
- public void test_expanded_addBubble_newApp() {
- // Given
+ public void test_expanded_updateBubble_noSelectionChanges() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
- sendUpdatedEntryAtTime(mEntryA3, 3000);
- sendUpdatedEntryAtTime(mEntryB1, 4000);
- sendUpdatedEntryAtTime(mEntryB2, 5000);
+ sendUpdatedEntryAtTime(mEntryB1, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
+ changeExpandedStateAtTime(true, 5000L);
+ mBubbleData.setListener(mListener);
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
+ // Test
+ sendUpdatedEntryAtTime(mEntryA1, 6000);
+ sendUpdatedEntryAtTime(mEntryA2, 7000);
+ sendUpdatedEntryAtTime(mEntryB1, 8000);
+ verify(mListener, never()).onSelectionChanged(any(Bubble.class));
+ }
- changeExpandedStateAtTime(true, 6000L);
+ // EXPANDED / REMOVE
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
- assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(6000L);
+ /**
+ * Verifies that removing a bubble while expanded does not result in reordering of groups
+ * or any of the remaining bubbles.
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
+ @Test
+ public void test_expanded_removeBubble_sortAndGrouping() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
+ changeExpandedStateAtTime(true, 5000L);
+ mBubbleData.setListener(mListener);
- // regrouping only happens when collapsed (after new or update) or expanded->collapsed
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1));
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleA2, mBubbleA1)));
+ }
+
+ /**
+ * Verifies that removing the selected bubble while expanded causes another bubble to become
+ * selected. The replacement selection is the bubble which appears at the same index as the
+ * previous one, or the previous index if this was the last position.
+ *
+ * @see #test_collapsed_addBubble_sortAndGrouping()
+ * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ */
+ @Test
+ public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000);
+ changeExpandedStateAtTime(true, 5000L);
+ mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
+ mBubbleData.setListener(mListener);
- // When
- sendUpdatedEntryAtTime(mEntryC1, 7000);
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener).onSelectionChanged(mBubbleA1);
- // Then
- // A2 is expired. A1 was oldest, but lastActivityTime is reset when expanded, since A1 is
- // selected.
- // C1 is added at the end since bubbles are expanded.
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA3, mBubbleA1, mBubbleC1));
+ reset(mListener);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener).onSelectionChanged(mBubbleB1);
}
- // New bubble from existing app while stack is expanded
@Test
- public void test_expanded_addBubble_existingApp() {
- // Given
- sendUpdatedEntryAtTime(mEntryB1, 1000);
- sendUpdatedEntryAtTime(mEntryB2, 2000);
- sendUpdatedEntryAtTime(mEntryA1, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000);
- sendUpdatedEntryAtTime(mEntryA3, 5000);
-
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
+ public void test_expandAndCollapse_callsOnExpandedChanged() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ mBubbleData.setListener(mListener);
- changeExpandedStateAtTime(true, 6000L);
+ // Test
+ changeExpandedStateAtTime(true, 3000L);
+ verify(mListener).onExpandedChanged(eq(true));
- // B1 is first (newest, since it's just been expanded and is selected)
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleB1);
- assertThat(mBubbleData.getSelectedBubble().getLastActivity()).isEqualTo(6000L);
+ reset(mListener);
+ changeExpandedStateAtTime(false, 4000L);
+ verify(mListener).onExpandedChanged(eq(false));
+ }
- // regrouping only happens when collapsed (after new or update) or while collapsing
+ /**
+ * Verifies that transitions between the collapsed and expanded state maintain sorting and
+ * grouping rules.
+ * <p>
+ * While collapsing, sorting is applied since no sorting happens while expanded. The resulting
+ * state is the new expanded ordering. This state is saved and restored if possible when next
+ * expanded.
+ * <p>
+ * When the stack transitions to the collapsed state, the selected bubble is brought to the top.
+ * Bubbles within the same group should move up with it.
+ * <p>
+ * When the stack transitions back to the expanded state, the previous ordering is restored, as
+ * long as no changes have been made (adds, removes or updates) while in the collapsed state.
+ */
+ @Test
+ public void test_expansionChanges() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
+ sendUpdatedEntryAtTime(mEntryB2, 4000);
+ changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
+ sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=6000*, A2=3000, A1=1000]
+ setCurrentTime(7000);
+ mBubbleData.setSelectedBubble(mBubbleA2);
+ mBubbleData.setListener(mListener);
assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA3, mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ listOf(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
- // When
- sendUpdatedEntryAtTime(mEntryB3, 7000);
+ // Test
- // Then
- // (B2 is expired, B1 was oldest, but it's lastActivityTime is updated at the point when
- // the stack was expanded, since it is the selected bubble.
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA3, mBubbleA2, mBubbleA1, mBubbleB3, mBubbleB1));
+ // At this point, B1 has been updated but sorting has not been changed because the
+ // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
+ // to moving the selected bubble to the top (first).
+ //
+ // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
+ //
+ // That state is restored as long as no changes occur (add/remove/update) while in
+ // the collapsed state.
+ //
+ // collapse -> selected bubble (A2) moves first.
+ changeExpandedStateAtTime(false, 8000L);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2)));
+
+ // expand -> "original" order/grouping restored
+ reset(mListener);
+ changeExpandedStateAtTime(true, 10000L);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1)));
}
- // Updated bubble from existing app while stack is expanded
+ /**
+ * When a change occurs while collapsed (any update, add, remove), the previous expanded
+ * order and grouping becomes invalidated, and the order and grouping when next expanded will
+ * remain the same as collapsed.
+ */
@Test
- public void test_expanded_updateBubble_existingApp() {
+ public void test_expansionChanges_withUpdatesWhileCollapsed() {
+ // Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
- sendUpdatedEntryAtTime(mEntryA2, 2000);
- sendUpdatedEntryAtTime(mEntryB1, 3000);
+ sendUpdatedEntryAtTime(mEntryB1, 2000);
+ sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000);
+ changeExpandedStateAtTime(true, 5000L); // [B2=4000, B1=2000, A2=3000, A1=1000]
+ sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, B1=*6000, A2=3000, A1=1000]
+ setCurrentTime(7000);
+ mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
+ mBubbleData.setListener(mListener);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
- mBubbleData.setExpanded(true);
-
- sendUpdatedEntryAtTime(mEntryA1, 5000);
+ // Test
- // Does not reorder while expanded (for an update).
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+ // At this point, B1 has been updated but sorting has not been changed because the
+ // stack is expanded. When next collapsed, sorting will be applied and saved, just prior
+ // to moving the selected bubble to the top (first).
+ //
+ // In this case, the expected re-expand state will be: [B1, B2, A2*, A1]
+ //
+ // That state is restored as long as no changes occur (add/remove/update) while in
+ // the collapsed state.
+ //
+ // collapse -> selected bubble (A2) moves first.
+ changeExpandedStateAtTime(false, 8000L);
+ verify(mListener).onOrderChanged(eq(listOf(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2)));
+
+ // An update occurs, which causes sorting, and this invalidates the previously saved order.
+ sendUpdatedEntryAtTime(mEntryA2, 9000);
+
+ // No order changes when expanding because the new sorted order remains.
+ reset(mListener);
+ changeExpandedStateAtTime(true, 10000L);
+ verify(mListener, never()).onOrderChanged(anyList());
}
@Test
- public void test_expanded_updateBubble() {
- // Given
- assertThat(mBubbleData.hasBubbles()).isFalse();
- assertThat(mBubbleData.isExpanded()).isFalse();
+ public void test_expanded_removeLastBubble_collapsesStack() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ changeExpandedStateAtTime(true, 2000);
+ mBubbleData.setListener(mListener);
- setPostTime(mEntryA1, 1000);
- mBubbleData.notificationEntryUpdated(mEntryA1);
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ verify(mListener).onExpandedChanged(eq(false));
+ }
- setPostTime(mEntryB1, 2000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
+ private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName) {
+ return createBubbleEntry(userId, notifKey, packageName, 1000);
+ }
- setPostTime(mEntryB2, 3000);
- mBubbleData.notificationEntryUpdated(mEntryB2);
+ private void setPostTime(NotificationEntry entry, long postTime) {
+ when(entry.notification.getPostTime()).thenReturn(postTime);
+ }
- setPostTime(mEntryA2, 4000);
- mBubbleData.notificationEntryUpdated(mEntryA2);
+ private void setOngoing(NotificationEntry entry, boolean ongoing) {
+ if (ongoing) {
+ entry.notification.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
+ } else {
+ entry.notification.getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
+ }
+ }
- mBubbleData.setExpanded(true);
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ /**
+ * No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
+ * required for BubbleData functionality and verification. NotificationTestHelper is used only
+ * as a convenience to create a Notification w/BubbleMetadata.
+ */
+ private NotificationEntry createBubbleEntry(int userId, String notifKey, String packageName,
+ long postTime) {
+ // BubbleMetadata
+ Notification.BubbleMetadata bubbleMetadata = new Notification.BubbleMetadata.Builder()
+ .setIntent(mExpandIntent)
+ .setDeleteIntent(mDeleteIntent)
+ .setIcon(Icon.createWithResource("", 0))
+ .build();
+ // Notification -> BubbleMetadata
+ Notification notification = mNotificationTestHelper.createNotification(false,
+ null /* groupKey */, bubbleMetadata);
- // When
- setPostTime(mEntryB1, 5000);
- mBubbleData.notificationEntryUpdated(mEntryB1);
+ // StatusBarNotification
+ StatusBarNotification sbn = mock(StatusBarNotification.class);
+ when(sbn.getKey()).thenReturn(notifKey);
+ when(sbn.getUser()).thenReturn(new UserHandle(userId));
+ when(sbn.getPackageName()).thenReturn(packageName);
+ when(sbn.getPostTime()).thenReturn(postTime);
+ when(sbn.getNotification()).thenReturn(notification);
- // Then
- // B1 remains in the same place due to being expanded
- assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1));
+ // NotificationEntry -> StatusBarNotification -> Notification -> BubbleMetadata
+ return new NotificationEntry(sbn);
+ }
+
+ private void setCurrentTime(long time) {
+ when(mTimeSource.currentTimeMillis()).thenReturn(time);
+ }
+
+ private void sendUpdatedEntryAtTime(NotificationEntry entry, long postTime) {
+ setPostTime(entry, postTime);
+ mBubbleData.notificationEntryUpdated(entry);
+ }
+
+ private void changeExpandedStateAtTime(boolean shouldBeExpanded, long time) {
+ setCurrentTime(time);
+ mBubbleData.setExpanded(shouldBeExpanded);
+ }
+
+ /** Syntactic sugar to keep assertions more readable */
+ private static <T> List<T> listOf(T... a) {
+ return ImmutableList.copyOf(a);
}
}
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 6bb1cfaf11c2..1bd5201f5b26 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -20,8 +20,8 @@ import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED;
import static android.view.autofill.AutofillManager.FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY;
-import static android.view.autofill.AutofillManager.FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY;
import static android.view.autofill.AutofillManager.NO_SESSION;
+import static android.view.autofill.AutofillManager.RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
@@ -283,7 +283,7 @@ final class AutofillManagerServiceImpl
*
* @return {@code long} whose right-most 32 bits represent the session id (which is always
* non-negative), and the left-most contains extra flags (currently either {@code 0} or
- * {@link AutofillManager#FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY}).
+ * {@link AutofillManager#RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY}).
*/
@GuardedBy("mLock")
long startSessionLocked(@NonNull IBinder activityToken, int taskId, int uid,
@@ -357,7 +357,8 @@ final class AutofillManagerServiceImpl
if (forAugmentedAutofillOnly) {
// Must embed the flag in the response, at the high-end side of the long.
// (session is always positive, so we don't have to worry about the signal bit)
- final long extraFlags = ((long) FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) << 32;
+ final long extraFlags =
+ ((long) RECEIVER_FLAG_SESSION_FOR_AUGMENTED_AUTOFILL_ONLY) << 32;
final long result = extraFlags | newSession.id;
return result;
} else {
@@ -1051,6 +1052,14 @@ final class AutofillManagerServiceImpl
}
}
+ @GuardedBy("mLock")
+ void destroySessionsForAugmentedAutofillOnlyLocked() {
+ final int sessionCount = mSessions.size();
+ for (int i = sessionCount - 1; i >= 0; i--) {
+ mSessions.valueAt(i).forceRemoveSelfIfForAugmentedAutofillOnlyLocked();
+ }
+ }
+
// TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
@GuardedBy("mLock")
void destroyFinishedSessionsLocked() {
@@ -1070,9 +1079,18 @@ final class AutofillManagerServiceImpl
@GuardedBy("mLock")
void listSessionsLocked(ArrayList<String> output) {
final int numSessions = mSessions.size();
+ if (numSessions <= 0) return;
+
+ final String fmt = "%d:%s:%s";
for (int i = 0; i < numSessions; i++) {
- output.add((mInfo != null ? mInfo.getServiceInfo().getComponentName()
- : null) + ":" + mSessions.keyAt(i));
+ final int id = mSessions.keyAt(i);
+ final String service = mInfo == null
+ ? "no_svc"
+ : mInfo.getServiceInfo().getComponentName().flattenToShortString();
+ final String augmentedService = mRemoteAugmentedAutofillServiceInfo == null
+ ? "no_aug"
+ : mRemoteAugmentedAutofillServiceInfo.getComponentName().flattenToShortString();
+ output.add(String.format(fmt, id, service, augmentedService));
}
}
@@ -1136,6 +1154,7 @@ final class AutofillManagerServiceImpl
Slog.v(TAG, "updateRemoteAugmentedAutofillService(): "
+ "destroying old remote service");
}
+ destroySessionsForAugmentedAutofillOnlyLocked();
mRemoteAugmentedAutofillService.destroy();
mRemoteAugmentedAutofillService = null;
mRemoteAugmentedAutofillServiceInfo = null;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index e61fa326215f..66b5437f0a7d 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2407,7 +2407,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
- if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(id)) {
+ if ((flags & FLAG_MANUAL_REQUEST) == 0 && mAugmentedAutofillableIds != null
+ && mAugmentedAutofillableIds.contains(id)) {
// View was already reported when server could not handle a response, but it
// triggered augmented autofill
@@ -2538,7 +2539,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
try {
if (mHasCallback) {
mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinishedState);
- } else if (sessionFinishedState != 0) {
+ } else if (sessionFinishedState != AutofillManager.STATE_UNKNOWN) {
mClient.setSessionFinished(sessionFinishedState, autofillableIds);
}
} catch (RemoteException e) {
@@ -2693,6 +2694,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
+ "it can be augmented. AutofillableIds: " + autofillableIds);
}
mAugmentedAutofillableIds = autofillableIds;
+ try {
+ mClient.setState(AutofillManager.SET_STATE_FLAG_FOR_AUTOFILL_ONLY);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error setting client to autofill-only", e);
+ }
}
}
@@ -3270,6 +3276,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
+ void forceRemoveSelfIfForAugmentedAutofillOnlyLocked() {
+ if (sVerbose) {
+ Slog.v(TAG, "forceRemoveSelfIfForAugmentedAutofillOnly(" + this.id + "): "
+ + mForAugmentedAutofillOnly);
+ }
+ if (!mForAugmentedAutofillOnly) return;
+
+ forceRemoveSelfLocked();
+ }
+
+ @GuardedBy("mLock")
void forceRemoveSelfLocked(int clientState) {
if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 4399e4267fda..67c3d01cb86b 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -54,6 +54,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.contentcapture.ContentCaptureCondition;
import android.view.contentcapture.DataRemovalRequest;
@@ -552,6 +553,39 @@ final class ContentCapturePerUserService
+ " for user " + mUserId);
}
mMaster.mGlobalContentCaptureOptions.setWhitelist(mUserId, packages, activities);
+
+ // Must disable session that are not the whitelist anymore...
+ final int numSessions = mSessions.size();
+ if (numSessions <= 0) return;
+
+ // ...but without holding the lock on mGlobalContentCaptureOptions
+ final SparseBooleanArray blacklistedSessions = new SparseBooleanArray(numSessions);
+
+ for (int i = 0; i < numSessions; i++) {
+ final ContentCaptureServerSession session = mSessions.valueAt(i);
+ final boolean whitelisted = mMaster.mGlobalContentCaptureOptions
+ .isWhitelisted(mUserId, session.appComponentName);
+ if (!whitelisted) {
+ final int sessionId = mSessions.keyAt(i);
+ if (mMaster.debug) {
+ Slog.d(TAG, "marking session " + sessionId + " (" + session.appComponentName
+ + ") for un-whitelisting");
+ }
+ blacklistedSessions.append(sessionId, true);
+ }
+ }
+ final int numBlacklisted = blacklistedSessions.size();
+
+ if (numBlacklisted <= 0) return;
+
+ synchronized (mLock) {
+ for (int i = 0; i < numBlacklisted; i++) {
+ final int sessionId = blacklistedSessions.keyAt(i);
+ if (mMaster.debug) Slog.d(TAG, "un-whitelisting " + sessionId);
+ final ContentCaptureServerSession session = mSessions.get(sessionId);
+ session.setContentCaptureEnabledLocked(false);
+ }
+ }
}
@Override
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
index 2643db1d5851..aa63e40747ee 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureServerSession.java
@@ -72,6 +72,8 @@ final class ContentCaptureServerSession {
private final Object mLock;
+ public final ComponentName appComponentName;
+
ContentCaptureServerSession(@NonNull Object lock, @NonNull IBinder activityToken,
@NonNull ContentCapturePerUserService service, @NonNull ComponentName appComponentName,
@NonNull IResultReceiver sessionStateReceiver, int taskId, int displayId, int sessionId,
@@ -79,6 +81,7 @@ final class ContentCaptureServerSession {
Preconditions.checkArgument(sessionId != NO_SESSION_ID);
mLock = lock;
mActivityToken = activityToken;
+ this.appComponentName = appComponentName;
mService = service;
mId = sessionId;
mUid = uid;
@@ -228,6 +231,7 @@ final class ContentCaptureServerSession {
pw.print(prefix); pw.print("uid: "); pw.print(mUid); pw.println();
pw.print(prefix); pw.print("context: "); mContentCaptureContext.dump(pw); pw.println();
pw.print(prefix); pw.print("activity token: "); pw.println(mActivityToken);
+ pw.print(prefix); pw.print("app component: "); pw.println(appComponentName);
pw.print(prefix); pw.print("has autofill callback: ");
}
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2055b64483d9..fe22dcda9683 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -30,6 +30,7 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.IIpSecService;
import android.net.INetd;
import android.net.IpSecAlgorithm;
@@ -1276,7 +1277,7 @@ public class IpSecService extends IIpSecService.Stub {
public synchronized IpSecTunnelInterfaceResponse createTunnelInterface(
String localAddr, String remoteAddr, Network underlyingNetwork, IBinder binder,
String callingPackage) {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
checkNotNull(binder, "Null Binder passed to createTunnelInterface");
checkNotNull(underlyingNetwork, "No underlying network was specified");
checkInetAddress(localAddr);
@@ -1362,7 +1363,7 @@ public class IpSecService extends IIpSecService.Stub {
@Override
public synchronized void addAddressToTunnelInterface(
int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
// Get tunnelInterface record; if no such interface is found, will throw
@@ -1391,7 +1392,7 @@ public class IpSecService extends IIpSecService.Stub {
@Override
public synchronized void removeAddressFromTunnelInterface(
int tunnelResourceId, LinkAddress localAddr, String callingPackage) {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
// Get tunnelInterface record; if no such interface is found, will throw
@@ -1420,7 +1421,7 @@ public class IpSecService extends IIpSecService.Stub {
@Override
public synchronized void deleteTunnelInterface(
int resourceId, String callingPackage) throws RemoteException {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
UserRecord userRecord = mUserResourceTracker.getUserRecord(Binder.getCallingUid());
releaseResource(userRecord.mTunnelInterfaceRecords, resourceId);
}
@@ -1549,7 +1550,12 @@ public class IpSecService extends IIpSecService.Stub {
private static final String TUNNEL_OP = AppOpsManager.OPSTR_MANAGE_IPSEC_TUNNELS;
- private void enforceTunnelPermissions(String callingPackage) {
+ private void enforceTunnelFeatureAndPermissions(String callingPackage) {
+ if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS)) {
+ throw new UnsupportedOperationException(
+ "IPsec Tunnel Mode requires PackageManager.FEATURE_IPSEC_TUNNELS");
+ }
+
checkNotNull(callingPackage, "Null calling package cannot create IpSec tunnels");
switch (getAppOpsManager().noteOp(TUNNEL_OP, Binder.getCallingUid(), callingPackage)) {
case AppOpsManager.MODE_DEFAULT:
@@ -1621,7 +1627,7 @@ public class IpSecService extends IIpSecService.Stub {
IpSecConfig c, IBinder binder, String callingPackage) throws RemoteException {
checkNotNull(c);
if (c.getMode() == IpSecTransform.MODE_TUNNEL) {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
}
checkIpSecConfig(c);
checkNotNull(binder, "Null Binder passed to createTransform");
@@ -1729,7 +1735,7 @@ public class IpSecService extends IIpSecService.Stub {
public synchronized void applyTunnelModeTransform(
int tunnelResourceId, int direction,
int transformResourceId, String callingPackage) throws RemoteException {
- enforceTunnelPermissions(callingPackage);
+ enforceTunnelFeatureAndPermissions(callingPackage);
checkDirection(direction);
int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 103a0c7bafdb..32671144aee3 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -684,13 +684,13 @@ public class PackageWatchdog {
}
}
- /** Adds a {@link DeviceConfig#OnPropertyChangedListener}. */
+ /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
private void setPropertyChangedListenerLocked() {
- DeviceConfig.addOnPropertyChangedListener(
+ DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_ROLLBACK,
mContext.getMainExecutor(),
- (namespace, name, value) -> {
- if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(namespace)) {
+ (properties) -> {
+ if (!DeviceConfig.NAMESPACE_ROLLBACK.equals(properties.getNamespace())) {
return;
}
updateConfigs();
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 225c08092b49..1a6faecaecfd 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -201,7 +201,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int[] mDataConnectionNetworkType;
- private int mOtaspMode = TelephonyManager.OTASP_UNKNOWN;
+ private int[] mOtaspMode;
private ArrayList<List<CellInfo>> mCellInfo = null;
@@ -209,13 +209,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private Map<Integer, List<EmergencyNumber>> mEmergencyNumberList;
- private CallQuality mCallQuality = new CallQuality();
+ private CallQuality[] mCallQuality;
- private CallAttributes mCallAttributes = new CallAttributes(new PreciseCallState(),
- TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ private CallAttributes[] mCallAttributes;
// network type of the call associated with the mCallAttributes and mCallQuality
- private int mCallNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private int[] mCallNetworkType;
private int[] mSrvccState;
@@ -223,19 +222,19 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private int mDefaultPhoneId = SubscriptionManager.INVALID_PHONE_INDEX;
- private int mRingingCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ private int[] mRingingCallState;
- private int mForegroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ private int[] mForegroundCallState;
- private int mBackgroundCallState = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ private int[] mBackgroundCallState;
- private PreciseCallState mPreciseCallState = new PreciseCallState();
+ private PreciseCallState[] mPreciseCallState;
- private int mCallDisconnectCause = DisconnectCause.NOT_VALID;
+ private int[] mCallDisconnectCause;
private List<ImsReasonInfo> mImsReasonInfo = null;
- private int mCallPreciseDisconnectCause = PreciseDisconnectCause.NOT_VALID;
+ private int[] mCallPreciseDisconnectCause;
private boolean mCarrierNetworkChangeState = false;
@@ -250,8 +249,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
private final LocalLog mListenLog = new LocalLog(100);
- private PreciseDataConnectionState mPreciseDataConnectionState =
- new PreciseDataConnectionState();
+ private PreciseDataConnectionState[] mPreciseDataConnectionState;
// Nothing here yet, but putting it here in case we want to add more in the future.
static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -389,10 +387,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mMessageWaiting = new boolean[numPhones];
mCallForwarding = new boolean[numPhones];
mCellLocation = new Bundle[numPhones];
- mCellInfo = new ArrayList<List<CellInfo>>();
mSrvccState = new int[numPhones];
- mImsReasonInfo = new ArrayList<ImsReasonInfo>();
- mPhysicalChannelConfigs = new ArrayList<List<PhysicalChannelConfig>>();
+ mOtaspMode = new int[numPhones];
+ mPreciseCallState = new PreciseCallState[numPhones];
+ mForegroundCallState = new int[numPhones];
+ mBackgroundCallState = new int[numPhones];
+ mRingingCallState = new int[numPhones];
+ mCallDisconnectCause = new int[numPhones];
+ mCallPreciseDisconnectCause = new int[numPhones];
+ mCallQuality = new CallQuality[numPhones];
+ mCallNetworkType = new int[numPhones];
+ mCallAttributes = new CallAttributes[numPhones];
+ mPreciseDataConnectionState = new PreciseDataConnectionState[numPhones];
+ mCellInfo = new ArrayList<>();
+ mImsReasonInfo = new ArrayList<>();
+ mPhysicalChannelConfigs = new ArrayList<>();
mEmergencyNumberList = new HashMap<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
@@ -410,7 +419,19 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mCellInfo.add(i, null);
mImsReasonInfo.add(i, null);
mSrvccState[i] = TelephonyManager.SRVCC_STATE_HANDOVER_NONE;
- mPhysicalChannelConfigs.add(i, new ArrayList<PhysicalChannelConfig>());
+ mPhysicalChannelConfigs.add(i, new ArrayList<>());
+ mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN;
+ mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
+ mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
+ mCallQuality[i] = new CallQuality();
+ mCallAttributes[i] = new CallAttributes(new PreciseCallState(),
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality());
+ mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ mPreciseCallState[i] = new PreciseCallState();
+ mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
+ mPreciseDataConnectionState[i] = new PreciseDataConnectionState();
}
// Note that location can be null for non-phone builds like
@@ -731,7 +752,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
try {
- r.callback.onOtaspChanged(mOtaspMode);
+ r.callback.onOtaspChanged(mOtaspMode[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -749,15 +770,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((events & PhoneStateListener.LISTEN_PRECISE_CALL_STATE) != 0) {
try {
- r.callback.onPreciseCallStateChanged(mPreciseCallState);
+ r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
}
if ((events & PhoneStateListener.LISTEN_CALL_DISCONNECT_CAUSES) != 0) {
try {
- r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause,
- mCallPreciseDisconnectCause);
+ r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
+ mCallPreciseDisconnectCause[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -772,7 +793,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
if ((events & PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE) != 0) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState);
+ mPreciseDataConnectionState[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -854,7 +875,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
if ((events & PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED) != 0) {
try {
- r.callback.onCallAttributesChanged(mCallAttributes);
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -1392,12 +1413,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
LinkProperties linkProperties,
NetworkCapabilities networkCapabilities, int networkType,
boolean roaming) {
- notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
+ notifyDataConnectionForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, state,
isDataAllowed, apn, apnType, linkProperties,
networkCapabilities, networkType, roaming);
}
- public void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataAllowed,
+ public void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
+ boolean isDataAllowed,
String apn, String apnType,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int networkType, boolean roaming) {
@@ -1410,7 +1433,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
+ "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
+ " mRecords.size()=" + mRecords.size());
}
- int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
@@ -1425,8 +1447,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mLocalLog.log(str);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) &&
- idMatch(r.subId, subId, phoneId)) {
+ PhoneStateListener.LISTEN_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
try {
if (DBG) {
log("Notify data connection state changed on sub: " + subId);
@@ -1442,15 +1464,17 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
mDataConnectionState[phoneId] = state;
mDataConnectionNetworkType[phoneId] = networkType;
}
- mPreciseDataConnectionState = new PreciseDataConnectionState(state, networkType,
+ mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
+ state, networkType,
ApnSetting.getApnTypesBitmaskFromString(apnType), apn,
linkProperties, DataFailCause.NONE);
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
try {
r.callback.onPreciseDataConnectionStateChanged(
- mPreciseDataConnectionState);
+ mPreciseDataConnectionState[phoneId]);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -1466,11 +1490,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
public void notifyDataConnectionFailed(String apnType) {
- notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
+ notifyDataConnectionFailedForSubscriber(SubscriptionManager.DEFAULT_PHONE_INDEX,
+ SubscriptionManager.DEFAULT_SUBSCRIPTION_ID,
apnType);
}
- public void notifyDataConnectionFailedForSubscriber(int subId, String apnType) {
+ public void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1479,20 +1504,25 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
+ " apnType=" + apnType);
}
synchronized (mRecords) {
- mPreciseDataConnectionState = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
- DataFailCause.NONE);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
- try {
- r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (validatePhoneId(phoneId)) {
+ mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN,TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ DataFailCause.NONE);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseDataConnectionStateChanged(
+ mPreciseDataConnectionState[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
+
handleRemoveListLocked();
}
broadcastDataConnectionFailed(apnType, subId);
@@ -1539,18 +1569,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyOtaspChanged(int otaspMode) {
+ public void notifyOtaspChanged(int subId, int otaspMode) {
if (!checkNotifyPermission("notifyOtaspChanged()" )) {
return;
}
+ int phoneId = SubscriptionManager.getPhoneId(subId);
synchronized (mRecords) {
- mOtaspMode = otaspMode;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)) {
- try {
- r.callback.onOtaspChanged(otaspMode);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (validatePhoneId(phoneId)) {
+ mOtaspMode[phoneId] = otaspMode;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_OTASP_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onOtaspChanged(otaspMode);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
@@ -1558,49 +1592,55 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
- int backgroundCallState, int phoneId) {
+ public void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState) {
if (!checkNotifyPermission("notifyPreciseCallState()")) {
return;
}
synchronized (mRecords) {
- mRingingCallState = ringingCallState;
- mForegroundCallState = foregroundCallState;
- mBackgroundCallState = backgroundCallState;
- mPreciseCallState = new PreciseCallState(ringingCallState, foregroundCallState,
- backgroundCallState,
- DisconnectCause.NOT_VALID,
- PreciseDisconnectCause.NOT_VALID);
- boolean notifyCallAttributes = true;
- if (mCallQuality == null) {
- log("notifyPreciseCallState: mCallQuality is null, skipping call attributes");
- notifyCallAttributes = false;
- } else {
- // If the precise call state is no longer active, reset the call network type and
- // call quality.
- if (mPreciseCallState.getForegroundCallState()
- != PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
- mCallNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
- mCallQuality = new CallQuality();
+ if (validatePhoneId(phoneId)) {
+ mRingingCallState[phoneId] = ringingCallState;
+ mForegroundCallState[phoneId] = foregroundCallState;
+ mBackgroundCallState[phoneId] = backgroundCallState;
+ mPreciseCallState[phoneId] = new PreciseCallState(
+ ringingCallState, foregroundCallState,
+ backgroundCallState,
+ DisconnectCause.NOT_VALID,
+ PreciseDisconnectCause.NOT_VALID);
+ boolean notifyCallAttributes = true;
+ if (mCallQuality == null) {
+ log("notifyPreciseCallState: mCallQuality is null, "
+ + "skipping call attributes");
+ notifyCallAttributes = false;
+ } else {
+ // If the precise call state is no longer active, reset the call network type
+ // and call quality.
+ if (mPreciseCallState[phoneId].getForegroundCallState()
+ != PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+ mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ mCallQuality[phoneId] = new CallQuality();
+ }
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ mCallNetworkType[phoneId], mCallQuality[phoneId]);
}
- mCallAttributes = new CallAttributes(mPreciseCallState, mCallNetworkType,
- mCallQuality);
- }
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)) {
- try {
- r.callback.onPreciseCallStateChanged(mPreciseCallState);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_PRECISE_CALL_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseCallStateChanged(mPreciseCallState[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
- }
- if (notifyCallAttributes && r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)) {
- try {
- r.callback.onCallAttributesChanged(mCallAttributes);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (notifyCallAttributes && r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
@@ -1610,21 +1650,24 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
backgroundCallState);
}
- public void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause) {
+ public void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
+ int preciseDisconnectCause) {
if (!checkNotifyPermission("notifyDisconnectCause()")) {
return;
}
synchronized (mRecords) {
- mCallDisconnectCause = disconnectCause;
- mCallPreciseDisconnectCause = preciseDisconnectCause;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(PhoneStateListener
- .LISTEN_CALL_DISCONNECT_CAUSES)) {
- try {
- r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause,
- mCallPreciseDisconnectCause);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (validatePhoneId(phoneId)) {
+ mCallDisconnectCause[phoneId] = disconnectCause;
+ mCallPreciseDisconnectCause[phoneId] = preciseDisconnectCause;
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(PhoneStateListener
+ .LISTEN_CALL_DISCONNECT_CAUSES) && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onCallDisconnectCauseChanged(mCallDisconnectCause[phoneId],
+ mCallPreciseDisconnectCause[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
@@ -1660,25 +1703,30 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyPreciseDataConnectionFailed(String apnType,
+ public void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType,
String apn, @DataFailCause.FailCause int failCause) {
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
}
synchronized (mRecords) {
- mPreciseDataConnectionState = new PreciseDataConnectionState(
- TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)) {
- try {
- r.callback.onPreciseDataConnectionStateChanged(mPreciseDataConnectionState);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (validatePhoneId(phoneId)) {
+ mPreciseDataConnectionState[phoneId] = new PreciseDataConnectionState(
+ TelephonyManager.DATA_UNKNOWN, TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ ApnSetting.getApnTypesBitmaskFromString(apnType), apn, null, failCause);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onPreciseDataConnectionStateChanged(
+ mPreciseDataConnectionState[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
+
handleRemoveListLocked();
}
broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN,
@@ -1716,24 +1764,25 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
+ public void notifyOemHookRawEventForSubscriber(int phoneId, int subId, byte[] rawData) {
if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) {
return;
}
synchronized (mRecords) {
- for (Record r : mRecords) {
- if (VDBG) {
- log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId);
- }
- if ((r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) &&
- ((r.subId == subId) ||
- (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) {
- try {
- r.callback.onOemHookRawEvent(rawData);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ if (validatePhoneId(phoneId)) {
+ for (Record r : mRecords) {
+ if (VDBG) {
+ log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId);
+ }
+ if ((r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT))
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onOemHookRawEvent(rawData);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
@@ -1804,87 +1853,98 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
- public void notifyRadioPowerStateChanged(@TelephonyManager.RadioPowerState int state) {
+ public void notifyRadioPowerStateChanged(int phoneId, int subId,
+ @TelephonyManager.RadioPowerState int state) {
if (!checkNotifyPermission("notifyRadioPowerStateChanged()")) {
return;
}
if (VDBG) {
- log("notifyRadioPowerStateChanged: state= " + state);
+ log("notifyRadioPowerStateChanged: state= " + state + " subId=" + subId);
}
synchronized (mRecords) {
- mRadioPowerState = state;
+ if (validatePhoneId(phoneId)) {
+ mRadioPowerState = state;
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED)) {
- try {
- r.callback.onRadioPowerStateChanged(state);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onRadioPowerStateChanged(state);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
+
}
handleRemoveListLocked();
}
}
@Override
- public void notifyEmergencyNumberList() {
+ public void notifyEmergencyNumberList(int phoneId, int subId) {
if (!checkNotifyPermission("notifyEmergencyNumberList()")) {
return;
}
synchronized (mRecords) {
- TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
- mEmergencyNumberList = tm.getEmergencyNumberList();
+ if (validatePhoneId(phoneId)) {
+ TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ mEmergencyNumberList = tm.getEmergencyNumberList();
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST)) {
- try {
- r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
- if (VDBG) {
- log("notifyEmergencyNumberList: emergencyNumberList= "
- + mEmergencyNumberList);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onEmergencyNumberListChanged(mEmergencyNumberList);
+ if (VDBG) {
+ log("notifyEmergencyNumberList: emergencyNumberList= "
+ + mEmergencyNumberList);
+ }
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
}
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
}
}
}
+
handleRemoveListLocked();
}
}
@Override
- public void notifyCallQualityChanged(CallQuality callQuality, int phoneId,
+ public void notifyCallQualityChanged(CallQuality callQuality, int phoneId, int subId,
int callNetworkType) {
if (!checkNotifyPermission("notifyCallQualityChanged()")) {
return;
}
- // merge CallQuality with PreciseCallState and network type
- mCallQuality = callQuality;
- mCallNetworkType = callNetworkType;
- mCallAttributes = new CallAttributes(mPreciseCallState, callNetworkType, callQuality);
-
synchronized (mRecords) {
- TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
- Context.TELEPHONY_SERVICE);
+ if (validatePhoneId(phoneId)) {
+ // merge CallQuality with PreciseCallState and network type
+ mCallQuality[phoneId] = callQuality;
+ mCallNetworkType[phoneId] = callNetworkType;
+ mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId],
+ callNetworkType, callQuality);
- for (Record r : mRecords) {
- if (r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)) {
- try {
- r.callback.onCallAttributesChanged(mCallAttributes);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_CALL_ATTRIBUTES_CHANGED)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onCallAttributesChanged(mCallAttributes[phoneId]);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
}
}
}
+
handleRemoveListLocked();
}
}
@@ -1904,6 +1964,11 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("Phone Id=" + i);
pw.increaseIndent();
pw.println("mCallState=" + mCallState[i]);
+ pw.println("mRingingCallState=" + mRingingCallState[i]);
+ pw.println("mForegroundCallState=" + mForegroundCallState[i]);
+ pw.println("mBackgroundCallState=" + mBackgroundCallState[i]);
+ pw.println("mPreciseCallState=" + mPreciseCallState[i]);
+ pw.println("mCallDisconnectCause=" + mCallDisconnectCause[i]);
pw.println("mCallIncomingNumber=" + mCallIncomingNumber[i]);
pw.println("mServiceState=" + mServiceState[i]);
pw.println("mVoiceActivationState= " + mVoiceActivationState[i]);
@@ -1917,24 +1982,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
pw.println("mCellLocation=" + mCellLocation[i]);
pw.println("mCellInfo=" + mCellInfo.get(i));
pw.println("mImsCallDisconnectCause=" + mImsReasonInfo.get(i));
+ pw.println("mSrvccState=" + mSrvccState[i]);
+ pw.println("mOtaspMode=" + mOtaspMode[i]);
+ pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause[i]);
+ pw.println("mCallQuality=" + mCallQuality[i]);
+ pw.println("mCallAttributes=" + mCallAttributes[i]);
+ pw.println("mCallNetworkType=" + mCallNetworkType[i]);
+ pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState[i]);
pw.decreaseIndent();
}
- pw.println("mCallNetworkType=" + mCallNetworkType);
- pw.println("mPreciseDataConnectionState=" + mPreciseDataConnectionState);
- pw.println("mPreciseCallState=" + mPreciseCallState);
- pw.println("mCallDisconnectCause=" + mCallDisconnectCause);
- pw.println("mCallPreciseDisconnectCause=" + mCallPreciseDisconnectCause);
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
- pw.println("mRingingCallState=" + mRingingCallState);
- pw.println("mForegroundCallState=" + mForegroundCallState);
- pw.println("mBackgroundCallState=" + mBackgroundCallState);
- pw.println("mSrvccState=" + mSrvccState);
+
pw.println("mPhoneCapability=" + mPhoneCapability);
pw.println("mActiveDataSubId=" + mActiveDataSubId);
pw.println("mRadioPowerState=" + mRadioPowerState);
pw.println("mEmergencyNumberList=" + mEmergencyNumberList);
- pw.println("mCallQuality=" + mCallQuality);
- pw.println("mCallAttributes=" + mCallAttributes);
pw.println("mDefaultPhoneId=" + mDefaultPhoneId);
pw.println("mDefaultSubId=" + mDefaultSubId);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d5109123ab12..ad5f4e6e33c2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -36,6 +36,8 @@ import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IUidObserver;
import android.app.NotificationManager;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
@@ -158,6 +160,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* The implementation of the volume manager service.
@@ -889,9 +892,48 @@ public class AudioService extends IAudioService.Stub
0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
initA11yMonitoring();
+
+ mRoleObserver = new RoleObserver();
+ mRoleObserver.register();
+
onIndicateSystemReady();
}
+ RoleObserver mRoleObserver;
+
+ class RoleObserver implements OnRoleHoldersChangedListener {
+ private RoleManager mRm;
+ private final Executor mExecutor;
+
+ RoleObserver() {
+ mExecutor = mContext.getMainExecutor();
+ }
+
+ public void register() {
+ mRm = (RoleManager) mContext.getSystemService(Context.ROLE_SERVICE);
+ if (mRm != null) {
+ mRm.addOnRoleHoldersChangedListenerAsUser(mExecutor, this, UserHandle.ALL);
+ updateAssistantUId(true);
+ }
+ }
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ if (RoleManager.ROLE_ASSISTANT.equals(roleName)) {
+ updateAssistantUId(false);
+ }
+ }
+
+ public String getAssistantRoleHolder() {
+ String assitantPackage = "";
+ if (mRm != null) {
+ List<String> assistants = mRm.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+ assitantPackage = assistants.size() == 0 ? "" : assistants.get(0);
+ }
+ return assitantPackage;
+ }
+ }
+
void onIndicateSystemReady() {
if (AudioSystem.systemReady() == AudioSystem.SUCCESS) {
return;
@@ -1391,21 +1433,33 @@ public class AudioService extends IAudioService.Stub
int assistantUid = 0;
// Consider assistants in the following order of priority:
- // 1) voice interaction service
- // 2) assistant
- String assistantName = Settings.Secure.getStringForUser(
+ // 1) apk in assistant role
+ // 2) voice interaction service
+ // 3) assistant service
+
+ String packageName = "";
+ if (mRoleObserver != null) {
+ packageName = mRoleObserver.getAssistantRoleHolder();
+ }
+ if (TextUtils.isEmpty(packageName)) {
+ String assistantName = Settings.Secure.getStringForUser(
+ mContentResolver,
+ Settings.Secure.VOICE_INTERACTION_SERVICE, UserHandle.USER_CURRENT);
+ if (TextUtils.isEmpty(assistantName)) {
+ assistantName = Settings.Secure.getStringForUser(
mContentResolver,
- Settings.Secure.VOICE_INTERACTION_SERVICE, UserHandle.USER_CURRENT);
- if (TextUtils.isEmpty(assistantName)) {
- assistantName = Settings.Secure.getStringForUser(
- mContentResolver,
- Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT);
- }
- if (!TextUtils.isEmpty(assistantName)) {
- String packageName = ComponentName.unflattenFromString(assistantName).getPackageName();
- if (!TextUtils.isEmpty(packageName)) {
+ Settings.Secure.ASSISTANT, UserHandle.USER_CURRENT);
+ }
+ if (!TextUtils.isEmpty(assistantName)) {
+ packageName = ComponentName.unflattenFromString(assistantName).getPackageName();
+ }
+ }
+ if (!TextUtils.isEmpty(packageName)) {
+ PackageManager pm = mContext.getPackageManager();
+ if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName)
+ == PackageManager.PERMISSION_GRANTED) {
try {
- assistantUid = mContext.getPackageManager().getPackageUid(packageName, 0);
+ assistantUid = pm.getPackageUid(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG,
"updateAssistantUId() could not find UID for package: " + packageName);
diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
index d6aa2ba02f1f..5ff45a97706e 100644
--- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java
@@ -89,12 +89,6 @@ public class DisplayTransformManager {
private static final int DISPLAY_COLOR_MANAGED = 0;
private static final int DISPLAY_COLOR_UNMANAGED = 1;
private static final int DISPLAY_COLOR_ENHANCED = 2;
- /**
- * Display color mode range reserved for vendor customizations by the RenderIntent definition in
- * hardware/interfaces/graphics/common/1.1/types.hal.
- */
- private static final int VENDOR_MODE_RANGE_MIN = 256; // 0x100
- private static final int VENDOR_MODE_RANGE_MAX = 511; // 0x1ff
/**
* Map of level -> color transformation matrix.
@@ -270,7 +264,8 @@ public class DisplayTransformManager {
} else if (colorMode == ColorDisplayManager.COLOR_MODE_AUTOMATIC) {
applySaturation(COLOR_SATURATION_NATURAL);
setDisplayColor(DISPLAY_COLOR_ENHANCED);
- } else if (colorMode >= VENDOR_MODE_RANGE_MIN && colorMode <= VENDOR_MODE_RANGE_MAX) {
+ } else if (colorMode >= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN
+ && colorMode <= ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX) {
applySaturation(COLOR_SATURATION_NATURAL);
setDisplayColor(colorMode);
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 5f1f20294bc1..7ab46f60cf90 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -2055,7 +2055,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
setupNativeGnssService(/* reinitializeGnssServiceHandle = */ false);
if (native_is_gnss_visibility_control_supported()) {
- mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper);
+ mGnssVisibilityControl = new GnssVisibilityControl(mContext, mLooper, mNIHandler);
}
// load default GPS configuration
diff --git a/services/core/java/com/android/server/location/GnssVisibilityControl.java b/services/core/java/com/android/server/location/GnssVisibilityControl.java
index c3626d2373ea..860138675529 100644
--- a/services/core/java/com/android/server/location/GnssVisibilityControl.java
+++ b/services/core/java/com/android/server/location/GnssVisibilityControl.java
@@ -33,6 +33,9 @@ import android.util.ArrayMap;
import android.util.Log;
import android.util.StatsLog;
+import com.android.internal.R;
+import com.android.internal.location.GpsNetInitiatedHandler;
+
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -65,6 +68,7 @@ class GnssVisibilityControl {
private final Handler mHandler;
private final Context mContext;
+ private final GpsNetInitiatedHandler mNiHandler;
private boolean mIsGpsEnabled;
@@ -76,11 +80,12 @@ class GnssVisibilityControl {
private PackageManager.OnPermissionsChangedListener mOnPermissionsChangedListener =
uid -> runOnHandler(() -> handlePermissionsChanged(uid));
- GnssVisibilityControl(Context context, Looper looper) {
+ GnssVisibilityControl(Context context, Looper looper, GpsNetInitiatedHandler niHandler) {
mContext = context;
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
mHandler = new Handler(looper);
+ mNiHandler = niHandler;
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPackageManager = mContext.getPackageManager();
@@ -250,6 +255,9 @@ class GnssVisibilityControl {
private static final byte NFW_RESPONSE_TYPE_ACCEPTED_NO_LOCATION_PROVIDED = 1;
private static final byte NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED = 2;
+ // This must match with NfwProtocolStack enum in IGnssVisibilityControlCallback.hal.
+ private static final byte NFW_PROTOCOL_STACK_SUPL = 1;
+
private final String mProxyAppPackageName;
private final byte mProtocolStack;
private final String mOtherProtocolStackName;
@@ -299,6 +307,10 @@ class GnssVisibilityControl {
return mResponseType != NfwNotification.NFW_RESPONSE_TYPE_REJECTED;
}
+ private boolean isLocationProvided() {
+ return mResponseType == NfwNotification.NFW_RESPONSE_TYPE_ACCEPTED_LOCATION_PROVIDED;
+ }
+
private boolean isRequestAttributedToProxyApp() {
return !TextUtils.isEmpty(mProxyAppPackageName);
}
@@ -306,6 +318,10 @@ class GnssVisibilityControl {
private boolean isEmergencyRequestNotification() {
return mInEmergencyMode && !isRequestAttributedToProxyApp();
}
+
+ private boolean isRequestTypeSupl() {
+ return mProtocolStack == NFW_PROTOCOL_STACK_SUPL;
+ }
}
private void handlePermissionsChanged(int uid) {
@@ -430,16 +446,15 @@ class GnssVisibilityControl {
return;
}
- Log.e(TAG, "ProxyAppPackageName field is not set. AppOps service not notified "
- + "for non-framework location access notification: " + nfwNotification);
+ Log.e(TAG, "ProxyAppPackageName field is not set. AppOps service not notified"
+ + " for notification: " + nfwNotification);
return;
}
if (isLocationPermissionEnabled == null) {
- Log.w(TAG, "Could not find proxy app with name: " + proxyAppPkgName + " in the "
- + "value specified for config parameter: "
- + GnssConfiguration.CONFIG_NFW_PROXY_APPS + ". AppOps service not notified "
- + "for non-framework location access notification: " + nfwNotification);
+ Log.w(TAG, "Could not find proxy app " + proxyAppPkgName + " in the value specified for"
+ + " config parameter: " + GnssConfiguration.CONFIG_NFW_PROXY_APPS
+ + ". AppOps service not notified for notification: " + nfwNotification);
return;
}
@@ -447,8 +462,7 @@ class GnssVisibilityControl {
final ApplicationInfo proxyAppInfo = getProxyAppInfo(proxyAppPkgName);
if (proxyAppInfo == null) {
Log.e(TAG, "Proxy app " + proxyAppPkgName + " is not found. AppOps service not "
- + "notified for non-framework location access notification: "
- + nfwNotification);
+ + "notified for notification: " + nfwNotification);
return;
}
@@ -465,13 +479,40 @@ class GnssVisibilityControl {
}
private void handleEmergencyNfwNotification(NfwNotification nfwNotification) {
- boolean isPermissionMismatched =
- (nfwNotification.mResponseType == NfwNotification.NFW_RESPONSE_TYPE_REJECTED);
- if (isPermissionMismatched) {
+ boolean isPermissionMismatched = false;
+ if (!nfwNotification.isRequestAccepted()) {
Log.e(TAG, "Emergency non-framework location request incorrectly rejected."
+ " Notification: " + nfwNotification);
+ isPermissionMismatched = true;
+ }
+
+ if (!mNiHandler.getInEmergency()) {
+ Log.w(TAG, "Emergency state mismatch. Device currently not in user initiated emergency"
+ + " session. Notification: " + nfwNotification);
+ isPermissionMismatched = true;
}
+
logEvent(nfwNotification, isPermissionMismatched);
+
+ if (nfwNotification.isLocationProvided()) {
+ // Emulate deprecated IGnssNi.hal user notification of emergency NI requests.
+ GpsNetInitiatedHandler.GpsNiNotification notification =
+ new GpsNetInitiatedHandler.GpsNiNotification();
+ notification.notificationId = 0;
+ notification.niType = nfwNotification.isRequestTypeSupl()
+ ? GpsNetInitiatedHandler.GPS_NI_TYPE_EMERGENCY_SUPL
+ : GpsNetInitiatedHandler.GPS_NI_TYPE_UMTS_CTRL_PLANE;
+ notification.needNotify = true;
+ notification.needVerify = false;
+ notification.privacyOverride = false;
+ notification.timeout = 0;
+ notification.defaultResponse = GpsNetInitiatedHandler.GPS_NI_RESPONSE_NORESP;
+ notification.requestorId = nfwNotification.mRequestorId;
+ notification.requestorIdEncoding = GpsNetInitiatedHandler.GPS_ENC_NONE;
+ notification.text = mContext.getString(R.string.global_action_emergency);
+ notification.textEncoding = GpsNetInitiatedHandler.GPS_ENC_NONE;
+ mNiHandler.setNiNotification(notification);
+ }
}
private void logEvent(NfwNotification notification, boolean isPermissionMismatched) {
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 497385fef39c..5430f4c8daa0 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -22,21 +22,22 @@ import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.IApexService;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
-import android.os.HandlerThread;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
-import android.os.SystemClock;
import android.sysprop.ApexProperties;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.SystemService;
import java.io.File;
import java.io.PrintWriter;
@@ -45,108 +46,75 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.CountDownLatch;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* ApexManager class handles communications with the apex service to perform operation and queries,
* as well as providing caching to avoid unnecessary calls to the service.
- *
- * @hide
*/
-public final class ApexManager extends SystemService {
- private static final String TAG = "ApexManager";
- private IApexService mApexService;
-
- private final CountDownLatch mActivePackagesCacheLatch = new CountDownLatch(1);
+class ApexManager {
+ static final String TAG = "ApexManager";
+ private final IApexService mApexService;
+ private final Context mContext;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
private Map<String, PackageInfo> mActivePackagesCache;
- private final CountDownLatch mApexFilesCacheLatch = new CountDownLatch(1);
- private ApexInfo[] mApexFiles;
-
- public ApexManager(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
+ ApexManager(Context context) {
try {
mApexService = IApexService.Stub.asInterface(
- ServiceManager.getServiceOrThrow("apexservice"));
+ ServiceManager.getServiceOrThrow("apexservice"));
} catch (ServiceNotFoundException e) {
throw new IllegalStateException("Required service apexservice not available");
}
- publishLocalService(ApexManager.class, this);
- HandlerThread oneShotThread = new HandlerThread("ApexManagerOneShotHandler");
- oneShotThread.start();
- oneShotThread.getThreadHandler().post(this::initSequence);
- oneShotThread.quitSafely();
- }
-
- private void initSequence() {
- populateApexFilesCache();
- parseApexFiles();
+ mContext = context;
}
- private void populateApexFilesCache() {
- if (mApexFiles != null) {
- return;
- }
- long startTimeMicros = SystemClock.currentTimeMicro();
- Slog.i(TAG, "Starting to populate apex files cache");
- try {
- mApexFiles = mApexService.getActivePackages();
- Slog.i(TAG, "IPC to apexd finished in " + (SystemClock.currentTimeMicro()
- - startTimeMicros) + " μs");
- } catch (RemoteException re) {
- // TODO: make sure this error is propagated to system server.
- Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
- re.rethrowAsRuntimeException();
- }
- mApexFilesCacheLatch.countDown();
- Slog.i(TAG, "Finished populating apex files cache in " + (SystemClock.currentTimeMicro()
- - startTimeMicros) + " μs");
+ void systemReady() {
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ onBootCompleted();
+ mContext.unregisterReceiver(this);
+ }
+ }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
- private void parseApexFiles() {
- waitForLatch(mApexFilesCacheLatch);
- if (mApexFiles == null) {
- throw new IllegalStateException("mApexFiles must be populated");
- }
- long startTimeMicros = SystemClock.currentTimeMicro();
- Slog.i(TAG, "Starting to parse apex files");
- List<PackageInfo> list = new ArrayList<>();
- // TODO: this can be parallelized.
- for (ApexInfo ai : mApexFiles) {
+ private void populateActivePackagesCacheIfNeeded() {
+ synchronized (mLock) {
+ if (mActivePackagesCache != null) {
+ return;
+ }
try {
- // If the device is using flattened APEX, don't report any APEX
- // packages since they won't be managed or updated by PackageManager.
- if ((new File(ai.packagePath)).isDirectory()) {
- break;
- }
- list.add(PackageParser.generatePackageInfoFromApex(
- new File(ai.packagePath), PackageManager.GET_META_DATA
+ List<PackageInfo> list = new ArrayList<>();
+ final ApexInfo[] activePkgs = mApexService.getActivePackages();
+ for (ApexInfo ai : activePkgs) {
+ // If the device is using flattened APEX, don't report any APEX
+ // packages since they won't be managed or updated by PackageManager.
+ if ((new File(ai.packagePath)).isDirectory()) {
+ break;
+ }
+ try {
+ list.add(PackageParser.generatePackageInfoFromApex(
+ new File(ai.packagePath), PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES));
- } catch (PackageParserException pe) {
- // TODO: make sure this error is propagated to system server.
- throw new IllegalStateException("Unable to parse: " + ai, pe);
+ } catch (PackageParserException pe) {
+ throw new IllegalStateException("Unable to parse: " + ai, pe);
+ }
+ }
+ mActivePackagesCache = list.stream().collect(
+ Collectors.toMap(p -> p.packageName, Function.identity()));
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
+ throw new RuntimeException(re);
}
}
- mActivePackagesCache = list.stream().collect(
- Collectors.toMap(p -> p.packageName, Function.identity()));
- mActivePackagesCacheLatch.countDown();
- Slog.i(TAG, "Finished parsing apex files in " + (SystemClock.currentTimeMicro()
- - startTimeMicros) + " μs");
}
/**
* Retrieves information about an active APEX package.
*
- * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
- * case {@link #parseApexFiles()}} throws an exception this method will never finish
- * essentially putting device into a boot loop.
- *
* @param packageName the package name to look for. Note that this is the package name reported
* in the APK container manifest (i.e. AndroidManifest.xml), which might
* differ from the one reported in the APEX manifest (i.e.
@@ -155,43 +123,30 @@ public final class ApexManager extends SystemService {
* is not found.
*/
@Nullable PackageInfo getActivePackage(String packageName) {
- waitForLatch(mActivePackagesCacheLatch);
+ populateActivePackagesCacheIfNeeded();
return mActivePackagesCache.get(packageName);
}
/**
* Retrieves information about all active APEX packages.
*
- * <p>This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in
- * case {@link #parseApexFiles()}} throws an exception this method will never finish
- * essentially putting device into a boot loop.
- *
* @return a Collection of PackageInfo object, each one containing information about a different
* active package.
*/
Collection<PackageInfo> getActivePackages() {
- waitForLatch(mActivePackagesCacheLatch);
+ populateActivePackagesCacheIfNeeded();
return mActivePackagesCache.values();
}
/**
* Checks if {@code packageName} is an apex package.
*
- * <p>This method blocks caller thread until {@link #populateApexFilesCache()} succeeds. Note
- * that in case {@link #populateApexFilesCache()} throws an exception this method will never
- * finish essentially putting device into a boot loop.
- *
* @param packageName package to check.
* @return {@code true} if {@code packageName} is an apex package.
*/
boolean isApexPackage(String packageName) {
- waitForLatch(mApexFilesCacheLatch);
- for (ApexInfo ai : mApexFiles) {
- if (ai.packageName.equals(packageName)) {
- return true;
- }
- }
- return false;
+ populateActivePackagesCacheIfNeeded();
+ return mActivePackagesCache.containsKey(packageName);
}
/**
@@ -319,19 +274,6 @@ public final class ApexManager extends SystemService {
}
/**
- * Blocks current thread until {@code latch} has counted down to zero.
- *
- * @throws RuntimeException if thread was interrupted while waiting.
- */
- private void waitForLatch(CountDownLatch latch) {
- try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException("Interrupted waiting for cache to be populated", e);
- }
- }
-
- /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -344,7 +286,7 @@ public final class ApexManager extends SystemService {
ipw.println("Active APEX packages:");
ipw.increaseIndent();
try {
- waitForLatch(mActivePackagesCacheLatch);
+ populateActivePackagesCacheIfNeeded();
for (PackageInfo pi : mActivePackagesCache.values()) {
if (packageName != null && !packageName.equals(pi.packageName)) {
continue;
@@ -389,4 +331,8 @@ public final class ApexManager extends SystemService {
ipw.println("Couldn't communicate with apexd.");
}
}
+
+ public void onBootCompleted() {
+ populateActivePackagesCacheIfNeeded();
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ed83cbced49f..95a88092fd6e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2375,8 +2375,6 @@ public class PackageManagerService extends IPackageManager.Stub
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
- mApexManager = LocalServices.getService(ApexManager.class);
-
LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
@@ -2473,6 +2471,7 @@ public class PackageManagerService extends IPackageManager.Stub
mProtectedPackages = new ProtectedPackages(mContext);
+ mApexManager = new ApexManager(context);
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
@@ -21531,6 +21530,7 @@ public class PackageManagerService extends IPackageManager.Stub
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
+ mApexManager.systemReady();
mPackageDexOptimizer.systemReady();
getStorageManagerInternal().addExternalStoragePolicy(
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4ac8342e6e60..be7dd31380ba 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -115,7 +115,6 @@ import com.android.server.om.OverlayManagerService;
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
-import com.android.server.pm.ApexManager;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -628,12 +627,6 @@ public final class SystemServer {
watchdog.start();
traceEnd();
- // Start ApexManager as early as we can to give it enough time to call apexd and populate
- // cache of known apex packages. Note that calling apexd will happen asynchronously.
- traceBeginAndSlog("StartApexManager");
- mSystemServiceManager.startService(ApexManager.class);
- traceEnd();
-
Slog.i(TAG, "Reading configuration...");
final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
traceBeginAndSlog(TAG_SYSTEM_CONFIG);
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index 3ce28a46a029..271195b78c3e 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -62,6 +62,8 @@ public class PhoneStateListener {
/**
* Stop listening for updates.
+ *
+ * The PhoneStateListener is not tied to any subscription and unregistered for any update.
*/
public static final int LISTEN_NONE = 0;
@@ -444,7 +446,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when device service state changes.
+ * Callback invoked when device service state changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @see ServiceState#STATE_EMERGENCY_ONLY
* @see ServiceState#STATE_IN_SERVICE
@@ -456,7 +464,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when network signal strength changes.
+ * Callback invoked when network signal strength changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @see ServiceState#STATE_EMERGENCY_ONLY
* @see ServiceState#STATE_IN_SERVICE
@@ -470,21 +484,39 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the message-waiting indicator changes.
+ * Callback invoked when the message-waiting indicator changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public void onMessageWaitingIndicatorChanged(boolean mwi) {
// default implementation empty
}
/**
- * Callback invoked when the call-forwarding indicator changes.
+ * Callback invoked when the call-forwarding indicator changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public void onCallForwardingIndicatorChanged(boolean cfi) {
// default implementation empty
}
/**
- * Callback invoked when device cell location changes.
+ * Callback invoked when device cell location changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public void onCellLocationChanged(CellLocation location) {
// default implementation empty
@@ -493,7 +525,14 @@ public class PhoneStateListener {
/**
* Callback invoked when device call state changes.
* <p>
- * Reports the state of Telephony (mobile) calls on the device.
+ * Reports the state of Telephony (mobile) calls on the device for the registered subscription.
+ * <p>
+ * Note: the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
* <p>
* Note: The state returned here may differ from that returned by
* {@link TelephonyManager#getCallState()}. Receivers of this callback should be aware that
@@ -511,7 +550,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when connection state changes.
+ * Callback invoked when connection state changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @see TelephonyManager#DATA_DISCONNECTED
* @see TelephonyManager#DATA_CONNECTING
@@ -529,7 +574,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when data activity state changes.
+ * Callback invoked when data activity state changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @see TelephonyManager#DATA_ACTIVITY_NONE
* @see TelephonyManager#DATA_ACTIVITY_IN
@@ -542,12 +593,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when network signal strengths changes.
- *
- * @see ServiceState#STATE_EMERGENCY_ONLY
- * @see ServiceState#STATE_IN_SERVICE
- * @see ServiceState#STATE_OUT_OF_SERVICE
- * @see ServiceState#STATE_POWER_OFF
+ * Callback invoked when network signal strengths changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*/
public void onSignalStrengthsChanged(SignalStrength signalStrength) {
// default implementation empty
@@ -555,8 +607,15 @@ public class PhoneStateListener {
/**
- * The Over The Air Service Provisioning (OTASP) has changed. Requires
- * the READ_PHONE_STATE permission.
+ * The Over The Air Service Provisioning (OTASP) has changed on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * Requires the READ_PHONE_STATE permission.
* @param otaspMode is integer <code>OTASP_UNKNOWN=1<code>
* means the value is currently unknown and the system should wait until
* <code>OTASP_NEEDED=2<code> or <code>OTASP_NOT_NEEDED=3<code> is received before
@@ -570,15 +629,28 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when a observed cell info has changed,
- * or new cells have been added or removed.
+ * Callback invoked when a observed cell info has changed or new cells have been added
+ * or removed on the registered subscription.
+ * Note, the registration subId s from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param cellInfo is the list of currently visible cells.
*/
public void onCellInfoChanged(List<CellInfo> cellInfo) {
}
/**
- * Callback invoked when precise device call state changes.
+ * Callback invoked when precise device call state changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
* @param callState {@link PreciseCallState}
* @hide
*/
@@ -589,7 +661,14 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when call disconnect cause changes.
+ * Callback invoked when call disconnect cause changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param disconnectCause {@link DisconnectCause}.
* @param preciseDisconnectCause {@link PreciseDisconnectCause}.
*
@@ -602,7 +681,14 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when Ims call disconnect cause changes.
+ * Callback invoked when Ims call disconnect cause changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param imsReasonInfo {@link ImsReasonInfo} contains details on why IMS call failed.
*
* @hide
@@ -614,7 +700,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when data connection state changes with precise information.
+ * Callback invoked when data connection state changes with precise information
+ * on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param dataConnectionState {@link PreciseDataConnectionState}
*
* @hide
@@ -627,7 +721,13 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when data connection state changes with precise information.
+ * Callback invoked when data connection real time info changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @hide
*/
@@ -639,7 +739,15 @@ public class PhoneStateListener {
/**
* Callback invoked when there has been a change in the Single Radio Voice Call Continuity
- * (SRVCC) state for the currently active call.
+ * (SRVCC) state for the currently active call on the registered subscription.
+ *
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @hide
*/
@SystemApi
@@ -648,7 +756,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the SIM voice activation state has changed
+ * Callback invoked when the SIM voice activation state has changed on the registered
+ * subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param state is the current SIM voice activation state
* @hide
*/
@@ -657,7 +773,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the SIM data activation state has changed
+ * Callback invoked when the SIM data activation state has changed on the registered
+ * subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param state is the current SIM data activation state
* @hide
*/
@@ -665,7 +789,14 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the user mobile data state has changed
+ * Callback invoked when the user mobile data state has changed on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
* @param enabled indicates whether the current user mobile data state is enabled or disabled.
*/
public void onUserMobileDataStateChanged(boolean enabled) {
@@ -673,7 +804,14 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the current physical channel configuration has changed
+ * Callback invoked when the current physical channel configuration has changed on the
+ * registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @param configs List of the current {@link PhysicalChannelConfig}s
* @hide
@@ -684,7 +822,14 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the current emergency number list has changed
+ * Callback invoked when the current emergency number list has changed on the registered
+ * subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
*
* @param emergencyNumberList Map including the key as the active subscription ID
* (Note: if there is no active subscription, the key is
@@ -699,8 +844,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when OEM hook raw event is received. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
+ * Callback invoked when OEM hook raw event is received on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * Requires the READ_PRIVILEGED_PHONE_STATE permission.
* @param rawData is the byte array of the OEM hook raw data.
* @hide
*/
@@ -710,8 +862,10 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when phone capability changes. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
+ * Callback invoked when phone capability changes.
+ * Note, this callback triggers regardless of registered subscription.
+ *
+ * Requires the READ_PRIVILEGED_PHONE_STATE permission.
* @param capability the new phone capability
* @hide
*/
@@ -720,8 +874,10 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when active data subId changes. Requires
- * the READ_PHONE_STATE permission.
+ * Callback invoked when active data subId changes.
+ * Note, this callback triggers regardless of registered subscription.
+ *
+ * Requires the READ_PHONE_STATE permission.
* @param subId current subscription used to setup Cellular Internet data.
* For example, it could be the current active opportunistic subscription in use,
* or the subscription user selected as default data subscription in DSDS mode.
@@ -731,8 +887,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when the call attributes changes. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
+ * Callback invoked when the call attributes changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * Requires the READ_PRIVILEGED_PHONE_STATE permission.
* @param callAttributes the call attributes
* @hide
*/
@@ -742,7 +905,15 @@ public class PhoneStateListener {
}
/**
- * Callback invoked when modem radio power state changes. Requires
+ * Callback invoked when modem radio power state changes on the registered subscription.
+ * Note, the registration subId comes from {@link TelephonyManager} object which registers
+ * PhoneStateListener by {@link TelephonyManager#listen(PhoneStateListener, int)}.
+ * If this TelephonyManager object was created with
+ * {@link TelephonyManager#createForSubscriptionId(int)}, then the callback applies to the
+ * subId. Otherwise, this callback applies to
+ * {@link SubscriptionManager#getDefaultSubscriptionId()}.
+ *
+ * Requires
* the READ_PRIVILEGED_PHONE_STATE permission.
* @param state the modem radio power state
* @hide
@@ -758,6 +929,10 @@ public class PhoneStateListener {
* has been requested by an app using
* {@link android.telephony.TelephonyManager#notifyCarrierNetworkChange(boolean)}
*
+ * Note, this callback is pinned to the registered subscription and will be invoked when
+ * the notifying carrier app has carrier privilege rule on the registered
+ * subscription. {@link android.telephony.TelephonyManager#hasCarrierPrivileges}
+ *
* @param active Whether the carrier network change is or shortly
* will be active. This value is true to indicate
* showing alternative UI and false to stop.
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 0610c5d106c3..f2f3c2d85fd4 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -56,38 +56,41 @@ interface ITelephonyRegistry {
void notifyDataConnection(int state, boolean isDataConnectivityPossible,
String apn, String apnType, in LinkProperties linkProperties,
in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
- void notifyDataConnectionForSubscriber(int subId, int state, boolean isDataConnectivityPossible,
+ void notifyDataConnectionForSubscriber(int phoneId, int subId, int state,
+ boolean isDataConnectivityPossible,
String apn, String apnType, in LinkProperties linkProperties,
in NetworkCapabilities networkCapabilities, int networkType, boolean roaming);
@UnsupportedAppUsage
void notifyDataConnectionFailed(String apnType);
- void notifyDataConnectionFailedForSubscriber(int subId, String apnType);
+ void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType);
void notifyCellLocation(in Bundle cellLocation);
void notifyCellLocationForSubscriber(in int subId, in Bundle cellLocation);
- void notifyOtaspChanged(in int otaspMode);
+ void notifyOtaspChanged(in int subId, in int otaspMode);
@UnsupportedAppUsage
void notifyCellInfo(in List<CellInfo> cellInfo);
void notifyPhysicalChannelConfiguration(in List<PhysicalChannelConfig> configs);
void notifyPhysicalChannelConfigurationForSubscriber(in int subId,
in List<PhysicalChannelConfig> configs);
- void notifyPreciseCallState(int ringingCallState, int foregroundCallState,
- int backgroundCallState, int phoneId);
- void notifyDisconnectCause(int disconnectCause, int preciseDisconnectCause);
- void notifyPreciseDataConnectionFailed(String apnType, String apn,
+ void notifyPreciseCallState(int phoneId, int subId, int ringingCallState,
+ int foregroundCallState, int backgroundCallState);
+ void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
+ int preciseDisconnectCause);
+ void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, String apn,
int failCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
void notifySrvccStateChanged(in int subId, in int lteState);
void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
int activationState, int activationType);
- void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
+ void notifyOemHookRawEventForSubscriber(in int phoneId, in int subId, in byte[] rawData);
void notifySubscriptionInfoChanged();
void notifyOpportunisticSubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
void notifyUserMobileDataStateChangedForPhoneId(in int phoneId, in int subId, in boolean state);
void notifyPhoneCapabilityChanged(in PhoneCapability capability);
void notifyActiveDataSubIdChanged(int activeDataSubId);
- void notifyRadioPowerStateChanged(in int state);
- void notifyEmergencyNumberList();
- void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int callNetworkType);
+ void notifyRadioPowerStateChanged(in int phoneId, in int subId, in int state);
+ void notifyEmergencyNumberList(in int phoneId, in int subId);
+ void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
+ int callNetworkType);
void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 363ac9ce1d25..0ef56e553aa9 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -3854,6 +3854,9 @@ public class ConnectivityServiceTest {
networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);
testFactory.waitForRequests();
+ // unregister network callback - a no-op, but should not fail
+ mCm.unregisterNetworkCallback(networkCallback);
+
testFactory.unregister();
handlerThread.quit();
}
diff --git a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
index 7c40adfac002..71b72b84de81 100644
--- a/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceParameterizedTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.net.INetd;
import android.net.IpSecAlgorithm;
import android.net.IpSecConfig;
@@ -57,6 +58,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.net.Inet4Address;
import java.net.Socket;
import java.util.Arrays;
import java.util.Collection;
@@ -119,6 +121,11 @@ public class IpSecServiceParameterizedTest {
}
@Override
+ public PackageManager getPackageManager() {
+ return mMockPkgMgr;
+ }
+
+ @Override
public void enforceCallingOrSelfPermission(String permission, String message) {
if (permission == android.Manifest.permission.MANAGE_IPSEC_TUNNELS) {
return;
@@ -128,6 +135,7 @@ public class IpSecServiceParameterizedTest {
};
INetd mMockNetd;
+ PackageManager mMockPkgMgr;
IpSecService.IpSecServiceConfiguration mMockIpSecSrvConfig;
IpSecService mIpSecService;
Network fakeNetwork = new Network(0xAB);
@@ -152,11 +160,16 @@ public class IpSecServiceParameterizedTest {
@Before
public void setUp() throws Exception {
mMockNetd = mock(INetd.class);
+ mMockPkgMgr = mock(PackageManager.class);
mMockIpSecSrvConfig = mock(IpSecService.IpSecServiceConfiguration.class);
mIpSecService = new IpSecService(mMockContext, mMockIpSecSrvConfig);
// Injecting mock netd
when(mMockIpSecSrvConfig.getNetdInstance()).thenReturn(mMockNetd);
+
+ // PackageManager should always return true (feature flag tests in IpSecServiceTest)
+ when(mMockPkgMgr.hasSystemFeature(anyString())).thenReturn(true);
+
// A package granted the AppOp for MANAGE_IPSEC_TUNNELS will be MODE_ALLOWED.
when(mMockAppOps.noteOp(anyInt(), anyInt(), eq("blessedPackage")))
.thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -709,4 +722,18 @@ public class IpSecServiceParameterizedTest {
} catch (SecurityException expected) {
}
}
+
+ @Test
+ public void testFeatureFlagVerification() throws Exception {
+ when(mMockPkgMgr.hasSystemFeature(eq(PackageManager.FEATURE_IPSEC_TUNNELS)))
+ .thenReturn(false);
+
+ try {
+ String addr = Inet4Address.getLoopbackAddress().getHostAddress();
+ mIpSecService.createTunnelInterface(
+ addr, addr, new Network(0), new Binder(), "blessedPackage");
+ fail("Expected UnsupportedOperationException for disabled feature");
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
}