summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/test-current.txt1
-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/app/ActivityThread.java2
-rw-r--r--core/java/android/content/pm/PackageInstaller.java13
-rw-r--r--core/java/android/content/pm/PackageManager.java5
-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.java9
-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/service/autofill/augmented/AugmentedAutofillService.java63
-rw-r--r--core/java/android/service/autofill/augmented/FillCallback.java6
-rw-r--r--core/java/android/service/autofill/augmented/Helper.java48
-rw-r--r--core/java/android/util/LauncherIcons.java10
-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/java/com/android/internal/policy/DecorView.java54
-rw-r--r--core/proto/android/app/settings_enums.proto5
-rw-r--r--core/res/res/drawable/ic_corp_icon_badge_color.xml19
-rw-r--r--core/res/res/drawable/ic_corp_icon_badge_shadow.xml75
-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/res/res/values/strings.xml6
-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--media/java/android/media/AudioRecordingConfiguration.java2
-rw-r--r--packages/CaptivePortalLogin/AndroidManifest.xml1
-rw-r--r--packages/CaptivePortalLogin/res/drawable/app_icon.xml26
-rw-r--r--packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml27
-rw-r--r--packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java27
-rw-r--r--packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java209
-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/res/drawable/bubble_dismiss_circle.xml27
-rw-r--r--packages/SystemUI/res/drawable/bubble_dismiss_icon.xml26
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_dark_to_checkmark.xml22
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_dark_to_error.xml34
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_error_to_idle.xml34
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_pulse_dark_to_light.xml10
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_pulse_light_to_dark.xml10
-rw-r--r--packages/SystemUI/res/drawable/face_dialog_wink_from_dark.xml10
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml16
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml16
-rw-r--r--packages/SystemUI/res/layout/biometric_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/bubble_dismiss_target.xml66
-rw-r--r--packages/SystemUI/res/values-night/colors.xml6
-rw-r--r--packages/SystemUI/res/values/colors.xml4
-rw-r--r--packages/SystemUI/res/values/dimens.xml9
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-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/biometrics/BiometricDialogView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java319
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java227
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java257
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java183
-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--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java64
-rw-r--r--proto/src/metrics_constants/metrics_constants.proto42
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java53
-rw-r--r--services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java7
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java25
-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.java414
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java80
-rw-r--r--services/core/java/com/android/server/display/BrightnessMappingStrategy.java1
-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/PackageInstallerService.java13
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java11
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java29
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java11
-rw-r--r--services/core/java/com/android/server/policy/PermissionPolicyService.java4
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java27
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java7
-rw-r--r--services/java/com/android/server/SystemServer.java7
-rw-r--r--services/net/java/android/net/ip/IpClientManager.java273
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java8
-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
122 files changed, 3994 insertions, 2096 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index 5dc7929d06df..181932cf1260 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -725,7 +725,6 @@ package android.content.pm {
field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
field public static final int MATCH_KNOWN_PACKAGES = 4202496; // 0x402000
- field public static boolean RESTRICTED_PERMISSIONS_ENABLED;
field public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
field public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
}
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/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 4b37461866d0..bfc8b1204373 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1069,7 +1069,7 @@ public final class ActivityThread extends ClientTransactionHandler {
}
public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
- mH.removeMessages(H.APPLICATION_INFO_CHANGED);
+ mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
sendMessage(H.APPLICATION_INFO_CHANGED, ai);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index a8815ec6cfaa..89eabc285e38 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -1278,7 +1278,7 @@ public class PackageInstaller {
public int mode = MODE_INVALID;
/** {@hide} */
@UnsupportedAppUsage
- public int installFlags;
+ public int installFlags = PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
/** {@hide} */
public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
/** {@hide} */
@@ -1513,18 +1513,21 @@ public class PackageInstaller {
* state of the permission can be determined only at install time and cannot be
* changed on updated or at a later point via the package manager APIs.
*
+ * <p>Initially, all restricted permissions are whitelisted but you can change
+ * which ones are whitelisted by calling this method or the corresponding ones
+ * on the {@link PackageManager}.
+ *
* @see PackageManager#addWhitelistedRestrictedPermission(String, String, int)
* @see PackageManager#removeWhitelistedRestrictedPermission(String, String, int)
*/
public void setWhitelistedRestrictedPermissions(@Nullable Set<String> permissions) {
if (permissions == RESTRICTED_PERMISSIONS_ALL) {
installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
- }
- if (permissions != null) {
- this.whitelistedRestrictedPermissions = new ArrayList<>(permissions);
+ whitelistedRestrictedPermissions = null;
} else {
installFlags &= ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
- this.whitelistedRestrictedPermissions = null;
+ whitelistedRestrictedPermissions = (permissions != null)
+ ? new ArrayList<>(permissions) : null;
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index dd5ca6706316..40561f02d55f 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -86,11 +86,6 @@ public abstract class PackageManager {
/** {@hide} */
public static final boolean APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE = true;
- /** {@hide} */
- @TestApi
- // STOPSHIP: Remove this once we get a Play prebuilt.
- public static boolean RESTRICTED_PERMISSIONS_ENABLED = false;
-
/**
* This exception is thrown when a given package, application, or component
* name cannot be found.
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 1145d5bd4d9a..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 {
@@ -947,7 +953,8 @@ public final class IpSecManager {
throw new IllegalArgumentException(sse);
} else if (sse.errorCode == OsConstants.EAGAIN) {
throw new IllegalStateException(sse);
- } else if (sse.errorCode == OsConstants.EOPNOTSUPP) {
+ } else if (sse.errorCode == OsConstants.EOPNOTSUPP
+ || sse.errorCode == OsConstants.EPROTONOSUPPORT) {
throw new UnsupportedOperationException(sse);
}
}
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/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 656127ad77a9..87e369f20d58 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -15,6 +15,9 @@
*/
package android.service.autofill.augmented;
+import static android.service.autofill.augmented.Helper.logResponse;
+import static android.util.TimeUtils.formatDuration;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.CallSuper;
@@ -38,9 +41,7 @@ import android.os.SystemClock;
import android.service.autofill.augmented.PresentationParams.SystemPopupPresentationParams;
import android.util.Log;
import android.util.Pair;
-import android.util.Slog;
import android.util.SparseArray;
-import android.util.TimeUtils;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
@@ -48,6 +49,7 @@ import android.view.autofill.IAugmentedAutofillManagerClient;
import android.view.autofill.IAutofillWindowPresenter;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -84,6 +86,9 @@ public abstract class AugmentedAutofillService extends Service {
private SparseArray<AutofillProxy> mAutofillProxies;
+ // Used for metrics / debug only
+ private ComponentName mServiceComponentName;
+
private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() {
@Override
@@ -125,6 +130,7 @@ public abstract class AugmentedAutofillService extends Service {
/** @hide */
@Override
public final IBinder onBind(Intent intent) {
+ mServiceComponentName = intent.getComponent();
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return mInterface.asBinder();
}
@@ -215,8 +221,9 @@ public abstract class AugmentedAutofillService extends Service {
final CancellationSignal cancellationSignal = CancellationSignal.fromTransport(transport);
AutofillProxy proxy = mAutofillProxies.get(sessionId);
if (proxy == null) {
- proxy = new AutofillProxy(sessionId, client, taskId, componentName, focusedId,
- focusedValue, requestTime, callback, cancellationSignal);
+ proxy = new AutofillProxy(sessionId, client, taskId, mServiceComponentName,
+ componentName, focusedId, focusedValue, requestTime, callback,
+ cancellationSignal);
mAutofillProxies.put(sessionId, proxy);
} else {
// TODO(b/123099468): figure out if it's ok to reuse the proxy; add logging
@@ -272,6 +279,8 @@ public abstract class AugmentedAutofillService extends Service {
@Override
/** @hide */
protected final void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("Service component: "); pw.println(
+ ComponentName.flattenToShortString(mServiceComponentName));
if (mAutofillProxies != null) {
final int size = mAutofillProxies.size();
pw.print("Number proxies: "); pw.println(size);
@@ -301,12 +310,12 @@ public abstract class AugmentedAutofillService extends Service {
/** @hide */
static final class AutofillProxy {
- static final int REPORT_EVENT_ON_SUCCESS = 1;
+ static final int REPORT_EVENT_NO_RESPONSE = 1;
static final int REPORT_EVENT_UI_SHOWN = 2;
static final int REPORT_EVENT_UI_DESTROYED = 3;
@IntDef(prefix = { "REPORT_EVENT_" }, value = {
- REPORT_EVENT_ON_SUCCESS,
+ REPORT_EVENT_NO_RESPONSE,
REPORT_EVENT_UI_SHOWN,
REPORT_EVENT_UI_DESTROYED
})
@@ -319,6 +328,8 @@ public abstract class AugmentedAutofillService extends Service {
private final int mSessionId;
public final int taskId;
public final ComponentName componentName;
+ // Used for metrics / debug only
+ private String mServicePackageName;
@GuardedBy("mLock")
private AutofillId mFocusedId;
@GuardedBy("mLock")
@@ -349,6 +360,7 @@ public abstract class AugmentedAutofillService extends Service {
private CancellationSignal mCancellationSignal;
private AutofillProxy(int sessionId, @NonNull IBinder client, int taskId,
+ @NonNull ComponentName serviceComponentName,
@NonNull ComponentName componentName, @NonNull AutofillId focusedId,
@Nullable AutofillValue focusedValue, long requestTime,
@NonNull IFillCallback callback, @NonNull CancellationSignal cancellationSignal) {
@@ -357,6 +369,7 @@ public abstract class AugmentedAutofillService extends Service {
mCallback = callback;
this.taskId = taskId;
this.componentName = componentName;
+ mServicePackageName = serviceComponentName.getPackageName();
mFocusedId = focusedId;
mFocusedValue = focusedValue;
mFirstRequestTime = requestTime;
@@ -439,9 +452,9 @@ public abstract class AugmentedAutofillService extends Service {
mCallback.cancel();
}
} catch (RemoteException e) {
- Slog.e(TAG, "failed to check current pending request status", e);
+ Log.e(TAG, "failed to check current pending request status", e);
}
- Slog.d(TAG, "mCallback is updated.");
+ Log.d(TAG, "mCallback is updated.");
}
mCallback = callback;
}
@@ -463,13 +476,17 @@ public abstract class AugmentedAutofillService extends Service {
// Used (mostly) for metrics.
public void report(@ReportEvent int event) {
+ if (sVerbose) Log.v(TAG, "report(): " + event);
+ long duration = -1;
+ int type = MetricsEvent.TYPE_UNKNOWN;
switch (event) {
- case REPORT_EVENT_ON_SUCCESS:
+ case REPORT_EVENT_NO_RESPONSE:
+ type = MetricsEvent.TYPE_SUCCESS;
if (mFirstOnSuccessTime == 0) {
mFirstOnSuccessTime = SystemClock.elapsedRealtime();
+ duration = mFirstOnSuccessTime - mFirstRequestTime;
if (sDebug) {
- Slog.d(TAG, "Service responded in " + TimeUtils.formatDuration(
- mFirstOnSuccessTime - mFirstRequestTime));
+ Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
}
}
try {
@@ -479,27 +496,25 @@ public abstract class AugmentedAutofillService extends Service {
}
break;
case REPORT_EVENT_UI_SHOWN:
+ type = MetricsEvent.TYPE_OPEN;
if (mUiFirstShownTime == 0) {
mUiFirstShownTime = SystemClock.elapsedRealtime();
- if (sDebug) {
- Slog.d(TAG, "UI shown in " + TimeUtils.formatDuration(
- mUiFirstShownTime - mFirstRequestTime));
- }
+ duration = mUiFirstShownTime - mFirstRequestTime;
+ if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration));
}
break;
case REPORT_EVENT_UI_DESTROYED:
+ type = MetricsEvent.TYPE_CLOSE;
if (mUiFirstDestroyedTime == 0) {
mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
- if (sDebug) {
- Slog.d(TAG, "UI destroyed in " + TimeUtils.formatDuration(
- mUiFirstDestroyedTime - mFirstRequestTime));
- }
+ duration = mUiFirstDestroyedTime - mFirstRequestTime;
+ if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration));
}
break;
default:
- Slog.w(TAG, "invalid event reported: " + event);
+ Log.w(TAG, "invalid event reported: " + event);
}
- // TODO(b/122858578): log metrics as well
+ logResponse(type, mServicePackageName, componentName, mSessionId, duration);
}
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
@@ -527,19 +542,19 @@ public abstract class AugmentedAutofillService extends Service {
if (mFirstOnSuccessTime > 0) {
final long responseTime = mFirstOnSuccessTime - mFirstRequestTime;
pw.print(prefix); pw.print("response time: ");
- TimeUtils.formatDuration(responseTime, pw); pw.println();
+ formatDuration(responseTime, pw); pw.println();
}
if (mUiFirstShownTime > 0) {
final long uiRenderingTime = mUiFirstShownTime - mFirstRequestTime;
pw.print(prefix); pw.print("UI rendering time: ");
- TimeUtils.formatDuration(uiRenderingTime, pw); pw.println();
+ formatDuration(uiRenderingTime, pw); pw.println();
}
if (mUiFirstDestroyedTime > 0) {
final long uiTotalTime = mUiFirstDestroyedTime - mFirstRequestTime;
pw.print(prefix); pw.print("UI life time: ");
- TimeUtils.formatDuration(uiTotalTime, pw); pw.println();
+ formatDuration(uiTotalTime, pw); pw.println();
}
}
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index 33e6a8c25ba4..0251386bd7ce 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -50,8 +50,10 @@ public final class FillCallback {
public void onSuccess(@Nullable FillResponse response) {
if (sDebug) Log.d(TAG, "onSuccess(): " + response);
- mProxy.report(AutofillProxy.REPORT_EVENT_ON_SUCCESS);
- if (response == null) return;
+ if (response == null) {
+ mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+ return;
+ }
final FillWindow fillWindow = response.getFillWindow();
if (fillWindow != null) {
diff --git a/core/java/android/service/autofill/augmented/Helper.java b/core/java/android/service/autofill/augmented/Helper.java
new file mode 100644
index 000000000000..501696f99c66
--- /dev/null
+++ b/core/java/android/service/autofill/augmented/Helper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+package android.service.autofill.augmented;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.metrics.LogMaker;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+/** @hide */
+public final class Helper {
+
+ private static final MetricsLogger sMetricsLogger = new MetricsLogger();
+
+ /**
+ * Logs a {@code MetricsEvent.AUTOFILL_AUGMENTED_RESPONSE} event.
+ */
+ public static void logResponse(int type, @NonNull String servicePackageName,
+ @NonNull ComponentName componentName, int mSessionId, long durationMs) {
+ final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_AUGMENTED_RESPONSE)
+ .setType(type)
+ .setComponentName(componentName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SESSION_ID, mSessionId)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, durationMs);
+ System.out.println("LOGGGO: " + log.getEntries()); // felipeal: tmp
+ sMetricsLogger.write(log);
+ }
+
+ private Helper() {
+ throw new UnsupportedOperationException("contains only static methods");
+ }
+}
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index cc9991a9be20..8501eb5883d5 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -15,6 +15,7 @@
*/
package android.util;
+import android.app.ActivityThread;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -102,16 +103,17 @@ public final class LauncherIcons {
}
public Drawable getBadgedDrawable(Drawable base, int foregroundRes, int backgroundColor) {
- Resources sysRes = Resources.getSystem();
+ Resources overlayableRes =
+ ActivityThread.currentActivityThread().getApplication().getResources();
- Drawable badgeShadow = sysRes.getDrawable(
+ Drawable badgeShadow = overlayableRes.getDrawable(
com.android.internal.R.drawable.ic_corp_icon_badge_shadow);
- Drawable badgeColor = sysRes.getDrawable(
+ Drawable badgeColor = overlayableRes.getDrawable(
com.android.internal.R.drawable.ic_corp_icon_badge_color)
.getConstantState().newDrawable().mutate();
- Drawable badgeForeground = sysRes.getDrawable(foregroundRes);
+ Drawable badgeForeground = overlayableRes.getDrawable(foregroundRes);
badgeForeground.setTint(backgroundColor);
Drawable[] drawables = base == null
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/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 31f7d6f51ff8..9a10210739e1 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -630,7 +630,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
}
- Drawable bg = getBackground();
+ // Need to call super here as we pretend to be having the original background.
+ Drawable bg = super.getBackground();
if (bg != null) {
bg.setBounds(drawingBounds);
}
@@ -975,6 +976,18 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
}
}
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+
+ // TODO: This should route through setWindowBackground, but late in the release to make this
+ // change.
+ if (mOriginalBackgroundDrawable != background) {
+ mOriginalBackgroundDrawable = background;
+ updateBackgroundDrawable();
+ drawableChanged();
+ }
+ }
+
public void setWindowFrame(Drawable drawable) {
if (getForeground() != drawable) {
setForeground(drawable);
@@ -1161,12 +1174,16 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
// If we didn't request fullscreen layout, but we still got it because of the
// mForceWindowDrawsBarBackgrounds flag, also consume top inset.
+ // If we should always consume system bars, only consume that if the app wanted to go to
+ // fullscreen, as othrewise we can expect the app to handle it.
+ boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0
+ || (attrs.flags & FLAG_FULLSCREEN) != 0;
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
&& mForceWindowDrawsBarBackgrounds
&& mLastTopInset != 0
- || mLastShouldAlwaysConsumeSystemBars;
+ || (mLastShouldAlwaysConsumeSystemBars && fullscreen);
int consumedTop = consumingStatusBar ? mLastTopInset : 0;
int consumedRight = consumingNavBar ? mLastRightInset : 0;
@@ -1213,14 +1230,22 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
* are set.
*/
private void updateBackgroundDrawable() {
+ // Background insets can be null if super constructor calls setBackgroundDrawable.
+ if (mBackgroundInsets == null) {
+ mBackgroundInsets = Insets.NONE;
+ }
if (mBackgroundInsets.equals(mLastBackgroundInsets)
&& mLastOriginalBackgroundDrawable == mOriginalBackgroundDrawable) {
return;
}
if (mOriginalBackgroundDrawable == null || mBackgroundInsets.equals(Insets.NONE)) {
- setBackground(mOriginalBackgroundDrawable);
+
+ // Call super since we are intercepting setBackground on this class.
+ super.setBackgroundDrawable(mOriginalBackgroundDrawable);
} else {
- setBackground(new InsetDrawable(mOriginalBackgroundDrawable,
+
+ // Call super since we are intercepting setBackground on this class.
+ super.setBackgroundDrawable(new InsetDrawable(mOriginalBackgroundDrawable,
mBackgroundInsets.left, mBackgroundInsets.top,
mBackgroundInsets.right, mBackgroundInsets.bottom) {
@@ -1238,6 +1263,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
mLastOriginalBackgroundDrawable = mOriginalBackgroundDrawable;
}
+ @Override
+ public Drawable getBackground() {
+ return mOriginalBackgroundDrawable;
+ }
+
private int calculateStatusBarColor() {
return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_STATUS,
mSemiTransparentBarColor, mWindow.mStatusBarColor,
@@ -1527,10 +1557,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
return;
}
- setPadding(mFramePadding.left + mBackgroundPadding.left,
- mFramePadding.top + mBackgroundPadding.top,
- mFramePadding.right + mBackgroundPadding.right,
- mFramePadding.bottom + mBackgroundPadding.bottom);
+ // Fields can be null if super constructor calls setBackgroundDrawable.
+ Rect framePadding = mFramePadding != null ? mFramePadding : new Rect();
+ Rect backgroundPadding = mBackgroundPadding != null ? mBackgroundPadding : new Rect();
+
+ setPadding(framePadding.left + backgroundPadding.left,
+ framePadding.top + backgroundPadding.top,
+ framePadding.right + backgroundPadding.right,
+ framePadding.bottom + backgroundPadding.bottom);
requestLayout();
invalidate();
@@ -1550,8 +1584,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
if (bg != null) {
if (fg == null) {
opacity = bg.getOpacity();
- } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
- && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ } else if (framePadding.left <= 0 && framePadding.top <= 0
+ && framePadding.right <= 0 && framePadding.bottom <= 0) {
// If the frame padding is zero, then we can be opaque
// if either the frame -or- the background is opaque.
int fop = fg.getOpacity();
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/drawable/ic_corp_icon_badge_color.xml b/core/res/res/drawable/ic_corp_icon_badge_color.xml
index 6dba2779bc53..bb2d11753303 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_color.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_color.xml
@@ -19,12 +19,15 @@ Copyright (C) 2016 The Android Open Source Project
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
-
- <path
- android:fillColor="#fcfcfc"
- android:strokeColor="#e8eaed"
- android:strokeWidth="0.25"
- android:pathData="M62,50A12,12,0,1,1,50,38,12,12,0,0,1,62,50" />
- <path
- android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
+ <group
+ android:scaleX=".24"
+ android:scaleY=".24"
+ android:translateX="38"
+ android:translateY="38">
+ <path
+ android:fillColor="#fcfcfc"
+ android:strokeColor="#e8eaed"
+ android:strokeWidth="0.25"
+ android:pathData="@string/config_icon_mask" />
+ </group>
</vector> \ No newline at end of file
diff --git a/core/res/res/drawable/ic_corp_icon_badge_shadow.xml b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
index f33ed1f010be..cb29f6c39be4 100644
--- a/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge_shadow.xml
@@ -19,31 +19,52 @@ Copyright (C) 2016 The Android Open Source Project
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
-
- <path
- android:fillColor="#000000"
- android:fillAlpha="0.06"
- android:strokeAlpha="0.06"
- android:strokeWidth="1"
- android:pathData="M62,51.25a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
- <path
- android:pathData="M 0 0 H 64 V 64 H 0 V 0 Z" />
- <path
- android:fillColor="#000000"
- android:fillAlpha="0.06"
- android:strokeAlpha="0.06"
- android:strokeWidth="1"
- android:pathData="M62,52.28A12,12,0,1,1,50.53,39.76,12,12,0,0,1,62,52.28" />
- <path
- android:fillColor="#000000"
- android:fillAlpha="0.06"
- android:strokeAlpha="0.06"
- android:strokeWidth="1"
- android:pathData="M62,50.75a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
- <path
- android:fillColor="#000000"
- android:fillAlpha="0.06"
- android:strokeAlpha="0.06"
- android:strokeWidth="1"
- android:pathData="M62,50.25a12,12,0,1,1-12-12,12,12,0,0,1,12,12" />
+ <group
+ android:scaleX=".24"
+ android:scaleY=".24"
+ android:translateX="38"
+ android:translateY="39.25">
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="@string/config_icon_mask" />
+ </group>
+ <group
+ android:scaleX=".24"
+ android:scaleY=".24"
+ android:translateX="38"
+ android:translateY="39.75">
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="@string/config_icon_mask" />
+ </group>
+ <group
+ android:scaleX=".24"
+ android:scaleY=".24"
+ android:translateX="38"
+ android:translateY="38.75">
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="@string/config_icon_mask" />
+ </group>
+ <group
+ android:scaleX=".24"
+ android:scaleY=".24"
+ android:translateX="38"
+ android:translateY="38.25">
+ <path
+ android:fillColor="#000000"
+ android:fillAlpha="0.06"
+ android:strokeAlpha="0.06"
+ android:strokeWidth="1"
+ android:pathData="@string/config_icon_mask" />
+ </group>
</vector> \ No newline at end of file
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/res/res/values/strings.xml b/core/res/res/values/strings.xml
index dd0a6e605f60..b07e7ef0bd05 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1549,11 +1549,11 @@
<!-- Message shown during acqusition when the user's face is tilted too high or too low [CHAR LIMIT=50] -->
<string name="face_acquired_tilt_too_extreme">Turn your head a little less.</string>
<!-- Message shown during acquisiton when the user's face is tilted too far left or right [CHAR LIMIT=50] -->
- <string name="face_acquired_roll_too_extreme">Please straighten your head vertically.</string>
+ <string name="face_acquired_roll_too_extreme">Turn your head a little less.</string>
<!-- Message shown during acquisition when the user's face is obscured [CHAR LIMIT=50] -->
- <string name="face_acquired_obscured">Clear the space between your head and the phone.</string>
+ <string name="face_acquired_obscured">Remove anything hiding your face.</string>
<!-- Message shown during acquisition when the sensor is dirty [CHAR LIMIT=50] -->
- <string name="face_acquired_sensor_dirty">Please clean the camera.</string>
+ <string name="face_acquired_sensor_dirty">Clean the sensor at the top edge of the screen.</string>
<!-- Array containing custom messages shown during face acquisition from vendor. Vendor is expected to add and translate these strings -->
<string-array name="face_acquired_vendor">
</string-array>
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/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 74e661834818..874a215e4975 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -157,7 +157,7 @@ public final class AudioRecordingConfiguration implements Parcelable {
return new AudioRecordingConfiguration( /*anonymized uid*/ -1,
in.mClientSessionId, in.mClientSource, in.mClientFormat,
in.mDeviceFormat, in.mPatchHandle, "" /*empty package name*/,
- /*anonymized portId*/ -1, in.mClientSilenced, in.mDeviceSource, in.mClientEffects,
+ in.mClientPortId, in.mClientSilenced, in.mDeviceSource, in.mClientEffects,
in.mDeviceEffects);
}
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index a8f9c3b6aa87..86d6d442c672 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -31,6 +31,7 @@
<uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
<application android:label="@string/app_name"
+ android:icon="@drawable/app_icon"
android:usesCleartextTraffic="true"
android:supportsRtl="true" >
<activity
diff --git a/packages/CaptivePortalLogin/res/drawable/app_icon.xml b/packages/CaptivePortalLogin/res/drawable/app_icon.xml
new file mode 100644
index 000000000000..456ca83f5227
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/drawable/app_icon.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background>
+ <color android:color="@*android:color/accent_device_default_light" />
+ </background>
+ <foreground>
+ <inset
+ android:drawable="@drawable/maybe_wifi"
+ android:inset="25%">
+ </inset>
+ </foreground>
+</adaptive-icon>
diff --git a/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml b/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
new file mode 100644
index 000000000000..207aade406ef
--- /dev/null
+++ b/packages/CaptivePortalLogin/res/drawable/maybe_wifi.xml
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="26.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="26.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#4DFFFFFF"
+ android:pathData="M19.1,14l-3.4,0l0,-1.5c0,-1.8 0.8,-2.8 1.5,-3.4C18.1,8.3 19.200001,8 20.6,8c1.2,0 2.3,0.3 3.1,0.8l1.9,-2.3C25.1,6.1 20.299999,2.1 13,2.1S0.9,6.1 0.4,6.5L13,22l0,0l0,0l0,0l0,0l6.5,-8.1L19.1,14z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M19.5,17.799999c0,-0.8 0.1,-1.3 0.2,-1.6c0.2,-0.3 0.5,-0.7 1.1,-1.2c0.4,-0.4 0.7,-0.8 1,-1.1s0.4,-0.8 0.4,-1.2c0,-0.5 -0.1,-0.9 -0.4,-1.2c-0.3,-0.3 -0.7,-0.4 -1.2,-0.4c-0.4,0 -0.8,0.1 -1.1,0.3c-0.3,0.2 -0.4,0.6 -0.4,1.1l-1.9,0c0,-1 0.3,-1.7 1,-2.2c0.6,-0.5 1.5,-0.8 2.5,-0.8c1.1,0 2,0.3 2.6,0.8c0.6,0.5 0.9,1.3 0.9,2.3c0,0.7 -0.2,1.3 -0.6,1.8c-0.4,0.6 -0.9,1.1 -1.5,1.6c-0.3,0.3 -0.5,0.5 -0.6,0.7c-0.1,0.2 -0.1,0.6 -0.1,1L19.5,17.700001zM21.4,21l-1.9,0l0,-1.8l1.9,0L21.4,21z"/>
+</vector>
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index bacec78e5699..d6355bc111c2 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -279,8 +279,8 @@ public class NetworkMonitor extends StateMachine {
private final Context mContext;
private final INetworkMonitorCallbacks mCallback;
+ private final Network mCleartextDnsNetwork;
private final Network mNetwork;
- private final Network mNonPrivateDnsBypassNetwork;
private final TelephonyManager mTelephonyManager;
private final WifiManager mWifiManager;
private final ConnectivityManager mCm;
@@ -370,8 +370,8 @@ public class NetworkMonitor extends StateMachine {
mCallback = cb;
mDependencies = deps;
mDetectionStatsUtils = detectionStatsUtils;
- mNonPrivateDnsBypassNetwork = network;
- mNetwork = deps.getPrivateDnsBypassNetwork(network);
+ mNetwork = network;
+ mCleartextDnsNetwork = deps.getPrivateDnsBypassNetwork(network);
mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mCm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -496,7 +496,7 @@ public class NetworkMonitor extends StateMachine {
@Override
protected void log(String s) {
- if (DBG) Log.d(TAG + "/" + mNetwork.toString(), s);
+ if (DBG) Log.d(TAG + "/" + mCleartextDnsNetwork.toString(), s);
}
private void validationLog(int probeType, Object url, String msg) {
@@ -769,7 +769,7 @@ public class NetworkMonitor extends StateMachine {
case CMD_LAUNCH_CAPTIVE_PORTAL_APP:
final Bundle appExtras = new Bundle();
// OneAddressPerFamilyNetwork is not parcelable across processes.
- final Network network = new Network(mNetwork);
+ final Network network = new Network(mCleartextDnsNetwork);
appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network);
final CaptivePortalProbeResult probeRes = mLastPortalProbeResult;
appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl);
@@ -881,7 +881,7 @@ public class NetworkMonitor extends StateMachine {
CustomIntentReceiver(String action, int token, int what) {
mToken = token;
mWhat = what;
- mAction = action + "_" + mNetwork.getNetworkHandle() + "_" + token;
+ mAction = action + "_" + mCleartextDnsNetwork.getNetworkHandle() + "_" + token;
mContext.registerReceiver(this, new IntentFilter(mAction));
}
public PendingIntent getPendingIntent() {
@@ -994,7 +994,8 @@ public class NetworkMonitor extends StateMachine {
private void resolveStrictModeHostname() {
try {
// Do a blocking DNS resolution using the network-assigned nameservers.
- final InetAddress[] ips = mNetwork.getAllByName(mPrivateDnsProviderHostname);
+ final InetAddress[] ips = mCleartextDnsNetwork.getAllByName(
+ mPrivateDnsProviderHostname);
mPrivateDnsConfig = new PrivateDnsConfig(mPrivateDnsProviderHostname, ips);
validationLog("Strict mode hostname resolved: " + mPrivateDnsConfig);
} catch (UnknownHostException uhe) {
@@ -1033,7 +1034,7 @@ public class NetworkMonitor extends StateMachine {
+ oneTimeHostnameSuffix;
final Stopwatch watch = new Stopwatch().start();
try {
- final InetAddress[] ips = mNonPrivateDnsBypassNetwork.getAllByName(host);
+ final InetAddress[] ips = mNetwork.getAllByName(host);
final long time = watch.stop();
final String strIps = Arrays.toString(ips);
final boolean success = (ips != null && ips.length > 0);
@@ -1506,7 +1507,7 @@ public class NetworkMonitor extends StateMachine {
final int oldTag = TrafficStats.getAndSetThreadStatsTag(
TrafficStatsConstants.TAG_SYSTEM_PROBE);
- mDependencies.getDnsResolver().query(mNetwork, host, DnsResolver.FLAG_EMPTY,
+ mDependencies.getDnsResolver().query(mCleartextDnsNetwork, host, DnsResolver.FLAG_EMPTY,
r -> r.run() /* executor */, null /* cancellationSignal */, callback);
TrafficStats.setThreadStatsTag(oldTag);
@@ -1565,7 +1566,7 @@ public class NetworkMonitor extends StateMachine {
final int oldTag = TrafficStats.getAndSetThreadStatsTag(
TrafficStatsConstants.TAG_SYSTEM_PROBE);
try {
- urlConnection = (HttpURLConnection) mNetwork.openConnection(url);
+ urlConnection = (HttpURLConnection) mCleartextDnsNetwork.openConnection(url);
urlConnection.setInstanceFollowRedirects(probeType == ValidationProbeEvent.PROBE_PAC);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -1814,7 +1815,7 @@ public class NetworkMonitor extends StateMachine {
private void logNetworkEvent(int evtype) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetwork, transports, new NetworkEvent(evtype));
+ mMetricsLog.log(mCleartextDnsNetwork, transports, new NetworkEvent(evtype));
}
private int networkEventType(ValidationStage s, EvaluationResult r) {
@@ -1836,7 +1837,7 @@ public class NetworkMonitor extends StateMachine {
private void maybeLogEvaluationResult(int evtype) {
if (mEvaluationTimer.isRunning()) {
int[] transports = mNetworkCapabilities.getTransportTypes();
- mMetricsLog.log(mNetwork, transports,
+ mMetricsLog.log(mCleartextDnsNetwork, transports,
new NetworkEvent(evtype, mEvaluationTimer.stop()));
mEvaluationTimer.reset();
}
@@ -1850,7 +1851,7 @@ public class NetworkMonitor extends StateMachine {
.setReturnCode(probeResult)
.setDurationMs(durationMs)
.build();
- mMetricsLog.log(mNetwork, transports, ev);
+ mMetricsLog.log(mCleartextDnsNetwork, transports, ev);
}
@VisibleForTesting
diff --git a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
index 0dc1cbf8a984..6f5c27edb9d5 100644
--- a/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -42,10 +42,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,6 +66,7 @@ import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.captiveportal.CaptivePortalProbeResult;
import android.net.metrics.IpConnectivityLog;
+import android.net.shared.PrivateDnsConfig;
import android.net.util.SharedLog;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -73,6 +74,7 @@ import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.provider.Settings;
@@ -131,7 +133,8 @@ public class NetworkMonitorTest {
private @Mock Random mRandom;
private @Mock NetworkMonitor.Dependencies mDependencies;
private @Mock INetworkMonitorCallbacks mCallbacks;
- private @Spy Network mNetwork = new Network(TEST_NETID);
+ private @Spy Network mCleartextDnsNetwork = new Network(TEST_NETID);
+ private @Mock Network mNetwork;
private @Mock DataStallStatsUtils mDataStallStatsUtils;
private @Mock WifiInfo mWifiInfo;
private @Captor ArgumentCaptor<String> mNetworkTestedRedirectUrlCaptor;
@@ -166,35 +169,97 @@ public class NetworkMonitorTest {
private static final NetworkCapabilities NO_INTERNET_CAPABILITIES = new NetworkCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
- private void setDnsAnswers(String[] answers) throws UnknownHostException {
- if (answers == null) {
- doThrow(new UnknownHostException()).when(mNetwork).getAllByName(any());
- doNothing().when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
- return;
+ /**
+ * Fakes DNS responses.
+ *
+ * Allows test methods to configure the IP addresses that will be resolved by
+ * Network#getAllByName and by DnsResolver#query.
+ */
+ class FakeDns {
+ private final ArrayMap<String, List<InetAddress>> mAnswers = new ArrayMap<>();
+ private boolean mNonBypassPrivateDnsWorking = true;
+
+ /** Whether DNS queries on mNonBypassPrivateDnsWorking should succeed. */
+ private void setNonBypassPrivateDnsWorking(boolean working) {
+ mNonBypassPrivateDnsWorking = working;
}
- List<InetAddress> answerList = new ArrayList<>();
- for (String answer : answers) {
- answerList.add(InetAddresses.parseNumericAddress(answer));
+ /** Clears all DNS entries. */
+ private synchronized void clearAll() {
+ mAnswers.clear();
}
- InetAddress[] answerArray = answerList.toArray(new InetAddress[0]);
- doReturn(answerArray).when(mNetwork).getAllByName(any());
+ /** Returns the answer for a given name on the given mock network. */
+ private synchronized List<InetAddress> getAnswer(Object mock, String hostname) {
+ if (mock == mNetwork && !mNonBypassPrivateDnsWorking) {
+ return null;
+ }
+ if (mAnswers.containsKey(hostname)) {
+ return mAnswers.get(hostname);
+ }
+ return mAnswers.get("*");
+ }
- doAnswer((invocation) -> {
- Executor executor = (Executor) invocation.getArgument(3);
- DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
- new Handler(Looper.getMainLooper()).post(() -> {
- executor.execute(() -> callback.onAnswer(answerList, 0));
- });
- return null;
- }).when(mDnsResolver).query(eq(mNetwork), any(), anyInt(), any(), any(), any());
+ /** Sets the answer for a given name. */
+ private synchronized void setAnswer(String hostname, String[] answer)
+ throws UnknownHostException {
+ if (answer == null) {
+ mAnswers.remove(hostname);
+ } else {
+ List<InetAddress> answerList = new ArrayList<>();
+ for (String addr : answer) {
+ answerList.add(InetAddresses.parseNumericAddress(addr));
+ }
+ mAnswers.put(hostname, answerList);
+ }
+ }
+
+ /** Simulates a getAllByName call for the specified name on the specified mock network. */
+ private InetAddress[] getAllByName(Object mock, String hostname)
+ throws UnknownHostException {
+ List<InetAddress> answer = getAnswer(mock, hostname);
+ if (answer == null || answer.size() == 0) {
+ throw new UnknownHostException(hostname);
+ }
+ return answer.toArray(new InetAddress[0]);
+ }
+
+ /** Starts mocking DNS queries. */
+ private void startMocking() throws UnknownHostException {
+ // Queries on mCleartextDnsNetwork using getAllByName.
+ doAnswer(invocation -> {
+ return getAllByName(invocation.getMock(), invocation.getArgument(0));
+ }).when(mCleartextDnsNetwork).getAllByName(any());
+
+ // Queries on mNetwork using getAllByName.
+ doAnswer(invocation -> {
+ return getAllByName(invocation.getMock(), invocation.getArgument(0));
+ }).when(mNetwork).getAllByName(any());
+
+ // Queries on mCleartextDnsNetwork using DnsResolver#query.
+ doAnswer(invocation -> {
+ String hostname = (String) invocation.getArgument(1);
+ Executor executor = (Executor) invocation.getArgument(3);
+ DnsResolver.Callback<List<InetAddress>> callback = invocation.getArgument(5);
+
+ List<InetAddress> answer = getAnswer(invocation.getMock(), hostname);
+ if (answer != null && answer.size() > 0) {
+ new Handler(Looper.getMainLooper()).post(() -> {
+ executor.execute(() -> callback.onAnswer(answer, 0));
+ });
+ }
+ // If no answers, do nothing. sendDnsProbeWithTimeout will time out and throw UHE.
+ return null;
+ }).when(mDnsResolver).query(any(), any(), anyInt(), any(), any(), any());
+ }
}
+ private FakeDns mFakeDns;
+
@Before
public void setUp() throws IOException {
MockitoAnnotations.initMocks(this);
- when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mNetwork);
+ when(mDependencies.getPrivateDnsBypassNetwork(any())).thenReturn(mCleartextDnsNetwork);
when(mDependencies.getDnsResolver()).thenReturn(mDnsResolver);
when(mDependencies.getRandom()).thenReturn(mRandom);
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt()))
@@ -206,7 +271,7 @@ public class NetworkMonitorTest {
when(mDependencies.getSetting(any(), eq(Settings.Global.CAPTIVE_PORTAL_HTTPS_URL), any()))
.thenReturn(TEST_HTTPS_URL);
- doReturn(mNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
+ doReturn(mCleartextDnsNetwork).when(mNetwork).getPrivateDnsBypassingCopy();
when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mCm);
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
@@ -222,6 +287,9 @@ public class NetworkMonitorTest {
setFallbackSpecs(null); // Test with no fallback spec by default
when(mRandom.nextInt()).thenReturn(0);
+ when(mResources.getInteger(eq(R.integer.config_captive_portal_dns_probe_timeout)))
+ .thenReturn(500);
+
doAnswer((invocation) -> {
URL url = invocation.getArgument(0);
switch(url.toString()) {
@@ -237,11 +305,13 @@ public class NetworkMonitorTest {
fail("URL not mocked: " + url.toString());
return null;
}
- }).when(mNetwork).openConnection(any());
+ }).when(mCleartextDnsNetwork).openConnection(any());
when(mHttpConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
when(mHttpsConnection.getRequestProperties()).thenReturn(new ArrayMap<>());
- setDnsAnswers(new String[]{"2001:db8::1", "192.0.2.2"});
+ mFakeDns = new FakeDns();
+ mFakeDns.startMocking();
+ mFakeDns.setAnswer("*", new String[]{"2001:db8::1", "192.0.2.2"});
when(mContext.registerReceiver(any(BroadcastReceiver.class), any())).then((invocation) -> {
mRegisteredReceivers.add(invocation.getArgument(0));
@@ -264,6 +334,7 @@ public class NetworkMonitorTest {
@After
public void tearDown() {
+ mFakeDns.clearAll();
assertTrue(mCreatedNetworkMonitors.size() > 0);
// Make a local copy of mCreatedNetworkMonitors because during the iteration below,
// WrappedNetworkMonitor#onQuitting will delete elements from it on the handler threads.
@@ -284,8 +355,8 @@ public class NetworkMonitorTest {
private final ConditionVariable mQuitCv = new ConditionVariable(false);
WrappedNetworkMonitor() {
- super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger, mDependencies,
- mDataStallStatsUtils);
+ super(mContext, mCallbacks, mNetwork, mLogger, mValidationLogger,
+ mDependencies, mDataStallStatsUtils);
}
@Override
@@ -314,23 +385,22 @@ public class NetworkMonitorTest {
}
}
- private WrappedNetworkMonitor makeMonitor() {
+ private WrappedNetworkMonitor makeMonitor(NetworkCapabilities nc) {
final WrappedNetworkMonitor nm = new WrappedNetworkMonitor();
nm.start();
+ setNetworkCapabilities(nm, nc);
waitForIdle(nm.getHandler());
mCreatedNetworkMonitors.add(nm);
return nm;
}
private WrappedNetworkMonitor makeMeteredNetworkMonitor() {
- final WrappedNetworkMonitor nm = makeMonitor();
- setNetworkCapabilities(nm, METERED_CAPABILITIES);
+ final WrappedNetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
return nm;
}
private WrappedNetworkMonitor makeNotMeteredNetworkMonitor() {
- final WrappedNetworkMonitor nm = makeMonitor();
- setNetworkCapabilities(nm, NOT_METERED_CAPABILITIES);
+ final WrappedNetworkMonitor nm = makeMonitor(NOT_METERED_CAPABILITIES);
return nm;
}
@@ -595,7 +665,7 @@ public class NetworkMonitorTest {
@Test
public void testNoInternetCapabilityValidated() throws Exception {
runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
- verify(mNetwork, never()).openConnection(any());
+ verify(mCleartextDnsNetwork, never()).openConnection(any());
}
@Test
@@ -603,7 +673,7 @@ public class NetworkMonitorTest {
setSslException(mHttpsConnection);
setPortal302(mHttpConnection);
- final NetworkMonitor nm = makeMonitor();
+ final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES);
nm.notifyNetworkConnected(TEST_LINK_PROPERTIES, METERED_CAPABILITIES);
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
@@ -638,6 +708,63 @@ public class NetworkMonitorTest {
}
@Test
+ public void testPrivateDnsSuccess() throws Exception {
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+ mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::53"});
+
+ WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+ wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+ }
+
+ @Test
+ public void testPrivateDnsResolutionRetryUpdate() throws Exception {
+ // Set a private DNS hostname that doesn't resolve and expect validation to fail.
+ mFakeDns.setAnswer("dns.google", new String[0]);
+ setStatus(mHttpsConnection, 204);
+ setStatus(mHttpConnection, 204);
+
+ WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+ wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+ // Fix DNS and retry, expect validation to succeed.
+ reset(mCallbacks);
+ mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
+
+ wnm.forceReevaluation(Process.myUid());
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+
+ // Change configuration to an invalid DNS name, expect validation to fail.
+ reset(mCallbacks);
+ mFakeDns.setAnswer("dns.bad", new String[0]);
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+ // Change configuration back to working again, but make private DNS not work.
+ // Expect validation to fail.
+ reset(mCallbacks);
+ mFakeDns.setNonBypassPrivateDnsWorking(false);
+ wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+
+ // Make private DNS work again. Expect validation to succeed.
+ reset(mCallbacks);
+ mFakeDns.setNonBypassPrivateDnsWorking(true);
+ wnm.forceReevaluation(Process.myUid());
+ verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+ .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+ }
+
+ @Test
public void testDataStall_StallSuspectedAndSendMetrics() throws IOException {
WrappedNetworkMonitor wrappedMonitor = makeNotMeteredNetworkMonitor();
wrappedMonitor.setLastProbeTime(SystemClock.elapsedRealtime() - 1000);
@@ -728,25 +855,27 @@ public class NetworkMonitorTest {
WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
final int shortTimeoutMs = 200;
+ // Clear the wildcard DNS response created in setUp.
+ mFakeDns.setAnswer("*", null);
+
String[] expected = new String[]{"2001:db8::"};
- setDnsAnswers(expected);
+ mFakeDns.setAnswer("www.google.com", expected);
InetAddress[] actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
assertIpAddressArrayEquals(expected, actual);
expected = new String[]{"2001:db8::", "192.0.2.1"};
- setDnsAnswers(expected);
- actual = wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
+ mFakeDns.setAnswer("www.googleapis.com", expected);
+ actual = wnm.sendDnsProbeWithTimeout("www.googleapis.com", shortTimeoutMs);
assertIpAddressArrayEquals(expected, actual);
- expected = new String[0];
- setDnsAnswers(expected);
+ mFakeDns.setAnswer("www.google.com", new String[0]);
try {
wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
fail("No DNS results, expected UnknownHostException");
} catch (UnknownHostException e) {
}
- setDnsAnswers(null);
+ mFakeDns.setAnswer("www.google.com", null);
try {
wnm.sendDnsProbeWithTimeout("www.google.com", shortTimeoutMs);
fail("DNS query timed out, expected UnknownHostException");
@@ -841,7 +970,7 @@ public class NetworkMonitorTest {
}
private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
- final NetworkMonitor monitor = makeMonitor();
+ final NetworkMonitor monitor = makeMonitor(nc);
monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
try {
verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
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/res/drawable/bubble_dismiss_circle.xml b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
new file mode 100644
index 000000000000..1661bb22d148
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_circle.xml
@@ -0,0 +1,27 @@
+<!--
+ 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.
+-->
+<!--
+ The transparent circle outline that encircles the bubbles when they're in the dismiss target.
+-->
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+
+ <stroke
+ android:width="1dp"
+ android:color="#66FFFFFF" />
+
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml b/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml
new file mode 100644
index 000000000000..5c8de581f8d1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_dismiss_icon.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<!-- The 'X' bubble dismiss icon. This is just ic_close with a stroke. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
+ android:fillColor="#FFFFFFFF"
+ android:strokeColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/face_dialog_dark_to_checkmark.xml b/packages/SystemUI/res/drawable/face_dialog_dark_to_checkmark.xml
index e4ace67577c3..fe1951699eaa 100644
--- a/packages/SystemUI/res/drawable/face_dialog_dark_to_checkmark.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_dark_to_checkmark.xml
@@ -40,7 +40,7 @@
<path
android:name="_R_G_L_0_G_D_0_P_0"
android:fillAlpha="0"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M-116 -16.5 C-116,-16.5 -31.25,68.5 -31.25,68.5 C-31.25,68.5 108.75,-71.5 108.75,-71.5 "
android:trimPathStart="0"
@@ -58,7 +58,7 @@
android:pathData=" M-116 -16.5 C-116,-16.5 -31.25,68.5 -31.25,68.5 C-31.25,68.5 108.75,-71.5 108.75,-71.5 "
android:strokeWidth="20"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="0"
android:trimPathOffset="0" />
@@ -68,7 +68,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -85,7 +85,7 @@
android:pathData=" M4.71 1.1 C3.71,2.12 2.32,2.75 0.79,2.75 C-2.25,2.75 -4.7,0.29 -4.7,-2.75 "
android:strokeWidth="2"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -99,7 +99,7 @@
<path
android:name="_R_G_L_0_G_D_4_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.1,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -112,7 +112,7 @@
<path
android:name="_R_G_L_0_G_D_5_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.2,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -125,7 +125,7 @@
<path
android:name="_R_G_L_0_G_D_6_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M2.6 3.25 C2.6,3.25 -2.6,3.25 -2.6,3.25 C-2.6,3.25 -2.6,1.25 -2.6,1.25 C-2.6,1.25 0.6,1.25 0.6,1.25 C0.6,1.25 0.6,-3.25 0.6,-3.25 C0.6,-3.25 2.6,-3.25 2.6,-3.25 C2.6,-3.25 2.6,3.25 2.6,3.25c " />
</group>
@@ -386,8 +386,8 @@
android:duration="67"
android:propertyName="strokeColor"
android:startOffset="0"
- android:valueFrom="?android:attr/colorAccent"
- android:valueTo="?android:attr/colorAccent"
+ android:valueFrom="@color/biometric_dialog_accent"
+ android:valueTo="@color/biometric_dialog_accent"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -397,8 +397,8 @@
android:duration="17"
android:propertyName="strokeColor"
android:startOffset="67"
- android:valueFrom="?android:attr/colorAccent"
- android:valueTo="?android:attr/colorAccent"
+ android:valueFrom="@color/biometric_dialog_accent"
+ android:valueTo="@color/biometric_dialog_accent"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
diff --git a/packages/SystemUI/res/drawable/face_dialog_dark_to_error.xml b/packages/SystemUI/res/drawable/face_dialog_dark_to_error.xml
index a96d21addb2b..0c05019bf199 100644
--- a/packages/SystemUI/res/drawable/face_dialog_dark_to_error.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_dark_to_error.xml
@@ -36,7 +36,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="@color/biometric_face_icon_gray"
+ android:strokeColor="@color/biometric_dialog_gray"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -45,7 +45,7 @@
android:pathData=" M33.75 42.75 C32.75,43.76 31.37,44.39 29.83,44.39 C26.8,44.39 24.34,41.93 24.34,38.9 "
android:strokeWidth="2"
android:strokeAlpha="1"
- android:strokeColor="@color/biometric_face_icon_gray"
+ android:strokeColor="@color/biometric_dialog_gray"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -58,7 +58,7 @@
<path
android:name="_R_G_L_0_G_D_2_P_0"
android:fillAlpha="1"
- android:fillColor="@color/biometric_face_icon_gray"
+ android:fillColor="@color/biometric_dialog_gray"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.1,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -71,7 +71,7 @@
<path
android:name="_R_G_L_0_G_D_3_P_0"
android:fillAlpha="1"
- android:fillColor="@color/biometric_face_icon_gray"
+ android:fillColor="@color/biometric_dialog_gray"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.2,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -82,7 +82,7 @@
<path
android:name="_R_G_L_0_G_D_4_P_0"
android:fillAlpha="1"
- android:fillColor="@color/biometric_face_icon_gray"
+ android:fillColor="@color/biometric_dialog_gray"
android:fillType="nonZero"
android:pathData=" M2.6 3.25 C2.6,3.25 -2.6,3.25 -2.6,3.25 C-2.6,3.25 -2.6,1.25 -2.6,1.25 C-2.6,1.25 0.6,1.25 0.6,1.25 C0.6,1.25 0.6,-3.25 0.6,-3.25 C0.6,-3.25 2.6,-3.25 2.6,-3.25 C2.6,-3.25 2.6,3.25 2.6,3.25c " />
</group>
@@ -99,8 +99,8 @@
android:duration="50"
android:propertyName="strokeColor"
android:startOffset="0"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -110,8 +110,8 @@
android:duration="17"
android:propertyName="strokeColor"
android:startOffset="50"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -127,8 +127,8 @@
android:duration="50"
android:propertyName="strokeColor"
android:startOffset="0"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -138,8 +138,8 @@
android:duration="17"
android:propertyName="strokeColor"
android:startOffset="50"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -369,8 +369,8 @@
android:duration="50"
android:propertyName="fillColor"
android:startOffset="0"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -380,8 +380,8 @@
android:duration="17"
android:propertyName="fillColor"
android:startOffset="50"
- android:valueFrom="@color/biometric_face_icon_gray"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_gray"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
diff --git a/packages/SystemUI/res/drawable/face_dialog_error_to_idle.xml b/packages/SystemUI/res/drawable/face_dialog_error_to_idle.xml
index aca12fce8d2f..d3cee25a2146 100644
--- a/packages/SystemUI/res/drawable/face_dialog_error_to_idle.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_error_to_idle.xml
@@ -29,7 +29,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorError"
+ android:strokeColor="@color/biometric_dialog_error"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -38,7 +38,7 @@
android:pathData=" M34.78 38.76 C33.83,38.75 31.54,38.75 30.01,38.75 C26.97,38.75 26.14,38.75 24.3,38.76 "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorError"
+ android:strokeColor="@color/biometric_dialog_error"
android:trimPathStart="0.34"
android:trimPathEnd="0.5700000000000001"
android:trimPathOffset="0" />
@@ -51,7 +51,7 @@
<path
android:name="_R_G_L_0_G_D_2_P_0"
android:fillAlpha="0"
- android:fillColor="@color/biometric_face_icon_gray"
+ android:fillColor="@color/biometric_dialog_gray"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.1,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -64,7 +64,7 @@
<path
android:name="_R_G_L_0_G_D_3_P_0"
android:fillAlpha="0"
- android:fillColor="@color/biometric_face_icon_gray"
+ android:fillColor="@color/biometric_dialog_gray"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.2,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
@@ -75,7 +75,7 @@
<path
android:name="_R_G_L_0_G_D_4_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorError"
+ android:fillColor="@color/biometric_dialog_error"
android:fillType="nonZero"
android:pathData=" M0.9 3.25 C0.9,3.25 -1.5,3.25 -1.5,3.25 C-1.5,3.25 -1.5,1.25 -1.5,1.25 C-1.5,1.25 -1.5,1.25 -1.5,1.25 C-1.5,1.25 -1.5,-11.71 -1.5,-11.71 C-1.5,-11.71 0.9,-11.71 0.9,-11.71 C0.9,-11.71 0.9,3.25 0.9,3.25c " />
</group>
@@ -91,8 +91,8 @@
android:duration="83"
android:propertyName="strokeColor"
android:startOffset="0"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -102,8 +102,8 @@
android:duration="17"
android:propertyName="strokeColor"
android:startOffset="83"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -119,8 +119,8 @@
android:duration="83"
android:propertyName="strokeColor"
android:startOffset="0"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -130,8 +130,8 @@
android:duration="17"
android:propertyName="strokeColor"
android:startOffset="83"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -427,8 +427,8 @@
android:duration="83"
android:propertyName="fillColor"
android:startOffset="0"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="?android:attr/colorError"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_error"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
@@ -438,8 +438,8 @@
android:duration="17"
android:propertyName="fillColor"
android:startOffset="83"
- android:valueFrom="?android:attr/colorError"
- android:valueTo="@color/biometric_face_icon_gray"
+ android:valueFrom="@color/biometric_dialog_error"
+ android:valueTo="@color/biometric_dialog_gray"
android:valueType="colorType">
<aapt:attr name="android:interpolator">
<pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
diff --git a/packages/SystemUI/res/drawable/face_dialog_pulse_dark_to_light.xml b/packages/SystemUI/res/drawable/face_dialog_pulse_dark_to_light.xml
index 0de856c28dcd..427be1447679 100644
--- a/packages/SystemUI/res/drawable/face_dialog_pulse_dark_to_light.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_pulse_dark_to_light.xml
@@ -18,7 +18,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -27,26 +27,26 @@
android:pathData=" M33.75 42.75 C32.75,43.77 31.37,44.39 29.83,44.39 C26.8,44.39 24.34,41.93 24.34,38.9 "
android:strokeWidth="2"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
<path
android:name="_R_G_L_0_G_D_2_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M39 23.8 C39,25 39.9,25.9 41.1,25.9 C42.2,25.9 43.2,25 43.2,23.8 C43.2,22.6 42.3,21.7 41.1,21.7 C39.9,21.7 39,22.6 39,23.8c " />
<path
android:name="_R_G_L_0_G_D_3_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M16.5 23.8 C16.5,25 17.4,25.9 18.6,25.9 C19.8,25.9 20.7,25 20.7,23.8 C20.7,22.6 19.8,21.7 18.6,21.7 C17.4,21.7 16.5,22.6 16.5,23.8c " />
<path
android:name="_R_G_L_0_G_D_4_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M33.33 34.95 C33.33,34.95 28.13,34.95 28.13,34.95 C28.13,34.95 28.13,32.95 28.13,32.95 C28.13,32.95 31.33,32.95 31.33,32.95 C31.33,32.95 31.33,28.45 31.33,28.45 C31.33,28.45 33.33,28.45 33.33,28.45 C33.33,28.45 33.33,34.95 33.33,34.95c " />
</group>
diff --git a/packages/SystemUI/res/drawable/face_dialog_pulse_light_to_dark.xml b/packages/SystemUI/res/drawable/face_dialog_pulse_light_to_dark.xml
index 31a0cbb2605c..ab26408a2ed6 100644
--- a/packages/SystemUI/res/drawable/face_dialog_pulse_light_to_dark.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_pulse_light_to_dark.xml
@@ -18,7 +18,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="0.5"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -27,26 +27,26 @@
android:pathData=" M33.75 42.75 C32.75,43.77 31.37,44.39 29.83,44.39 C26.8,44.39 24.34,41.93 24.34,38.9 "
android:strokeWidth="2"
android:strokeAlpha="0.5"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
<path
android:name="_R_G_L_0_G_D_2_P_0"
android:fillAlpha="0.5"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M39 23.8 C39,25 39.9,25.9 41.1,25.9 C42.2,25.9 43.2,25 43.2,23.8 C43.2,22.6 42.3,21.7 41.1,21.7 C39.9,21.7 39,22.6 39,23.8c " />
<path
android:name="_R_G_L_0_G_D_3_P_0"
android:fillAlpha="0.5"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M16.5 23.8 C16.5,25 17.4,25.9 18.6,25.9 C19.8,25.9 20.7,25 20.7,23.8 C20.7,22.6 19.8,21.7 18.6,21.7 C17.4,21.7 16.5,22.6 16.5,23.8c " />
<path
android:name="_R_G_L_0_G_D_4_P_0"
android:fillAlpha="0.5"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M33.33 34.95 C33.33,34.95 28.13,34.95 28.13,34.95 C28.13,34.95 28.13,32.95 28.13,32.95 C28.13,32.95 31.33,32.95 31.33,32.95 C31.33,32.95 31.33,28.45 31.33,28.45 C31.33,28.45 33.33,28.45 33.33,28.45 C33.33,28.45 33.33,34.95 33.33,34.95c " />
</group>
diff --git a/packages/SystemUI/res/drawable/face_dialog_wink_from_dark.xml b/packages/SystemUI/res/drawable/face_dialog_wink_from_dark.xml
index adbe446abb9d..0cd542d51b51 100644
--- a/packages/SystemUI/res/drawable/face_dialog_wink_from_dark.xml
+++ b/packages/SystemUI/res/drawable/face_dialog_wink_from_dark.xml
@@ -13,7 +13,7 @@
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeWidth="2.5"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
@@ -30,14 +30,14 @@
android:pathData=" M33.75 42.75 C32.75,43.77 31.37,44.39 29.83,44.39 C26.8,44.39 24.34,41.93 24.34,38.9 "
android:strokeWidth="2"
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:trimPathStart="0"
android:trimPathEnd="1"
android:trimPathOffset="0" />
<path
android:name="_R_G_L_0_G_D_1_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M39 23.8 C39,25 39.9,25.9 41.1,25.9 C42.2,25.9 43.2,25 43.2,23.8 C43.2,22.6 42.3,21.7 41.1,21.7 C39.9,21.7 39,22.6 39,23.8c " />
<group
@@ -49,14 +49,14 @@
<path
android:name="_R_G_L_0_G_D_2_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M-2.1 0 C-2.1,1.2 -1.2,2.1 0,2.1 C1.2,2.1 2.1,1.2 2.1,0 C2.1,-1.2 1.2,-2.1 0,-2.1 C-1.2,-2.1 -2.1,-1.2 -2.1,0c " />
</group>
<path
android:name="_R_G_L_0_G_D_3_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorAccent"
+ android:fillColor="@color/biometric_dialog_accent"
android:fillType="nonZero"
android:pathData=" M33.33 34.95 C33.33,34.95 28.13,34.95 28.13,34.95 C28.13,34.95 28.13,32.95 28.13,32.95 C28.13,32.95 31.33,32.95 31.33,32.95 C31.33,32.95 31.33,28.45 31.33,28.45 C31.33,28.45 33.33,28.45 33.33,28.45 C33.33,28.45 33.33,34.95 33.33,34.95c " />
</group>
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 8f411f4c07fe..33263a9131a0 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -39,7 +39,7 @@
android:name="_R_G_L_1_G_D_0_P_0"
android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -50,7 +50,7 @@
android:name="_R_G_L_1_G_D_1_P_0"
android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -61,7 +61,7 @@
android:name="_R_G_L_1_G_D_2_P_0"
android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -72,7 +72,7 @@
android:name="_R_G_L_1_G_D_3_P_0"
android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -83,7 +83,7 @@
android:name="_R_G_L_1_G_D_4_P_0"
android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -109,7 +109,7 @@
<path
android:name="_R_G_L_0_G_D_0_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorError"
+ android:fillColor="@color/biometric_dialog_error"
android:fillType="nonZero"
android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
</group>
@@ -124,7 +124,7 @@
<path
android:name="_R_G_L_0_G_D_1_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorError"
+ android:fillColor="@color/biometric_dialog_error"
android:fillType="nonZero"
android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
</group>
@@ -132,7 +132,7 @@
android:name="_R_G_L_0_G_D_2_P_0"
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorError"
+ android:strokeColor="@color/biometric_dialog_error"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index 89b822840b14..b899828cd85c 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -39,7 +39,7 @@
android:name="_R_G_L_1_G_D_0_P_0"
android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -50,7 +50,7 @@
android:name="_R_G_L_1_G_D_1_P_0"
android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -61,7 +61,7 @@
android:name="_R_G_L_1_G_D_2_P_0"
android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -72,7 +72,7 @@
android:name="_R_G_L_1_G_D_3_P_0"
android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -83,7 +83,7 @@
android:name="_R_G_L_1_G_D_4_P_0"
android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorAccent"
+ android:strokeColor="@color/biometric_dialog_accent"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="5.5"
@@ -109,7 +109,7 @@
<path
android:name="_R_G_L_0_G_D_0_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorError"
+ android:fillColor="@color/biometric_dialog_error"
android:fillType="nonZero"
android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
</group>
@@ -124,7 +124,7 @@
<path
android:name="_R_G_L_0_G_D_1_P_0"
android:fillAlpha="1"
- android:fillColor="?android:attr/colorError"
+ android:fillColor="@color/biometric_dialog_error"
android:fillType="nonZero"
android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
</group>
@@ -132,7 +132,7 @@
android:name="_R_G_L_0_G_D_2_P_0"
android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
android:strokeAlpha="1"
- android:strokeColor="?android:attr/colorError"
+ android:strokeColor="@color/biometric_dialog_error"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="2.5"
diff --git a/packages/SystemUI/res/layout/biometric_dialog.xml b/packages/SystemUI/res/layout/biometric_dialog.xml
index c452855cc9e9..1abb8735ddab 100644
--- a/packages/SystemUI/res/layout/biometric_dialog.xml
+++ b/packages/SystemUI/res/layout/biometric_dialog.xml
@@ -119,7 +119,7 @@
android:textSize="12sp"
android:gravity="center_horizontal"
android:accessibilityLiveRegion="polite"
- android:textColor="?android:attr/textColorSecondary"/>
+ android:textColor="@color/biometric_dialog_gray"/>
<LinearLayout
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/bubble_dismiss_target.xml b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
new file mode 100644
index 000000000000..245177c8461b
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_dismiss_target.xml
@@ -0,0 +1,66 @@
+<!--
+ ~ 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
+ -->
+<!-- Bubble dismiss target consisting of an X icon and the text 'Dismiss'. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/pip_dismiss_gradient_height"
+ android:layout_gravity="bottom|center_horizontal">
+
+ <LinearLayout
+ android:id="@+id/bubble_dismiss_icon_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:paddingBottom="@dimen/bubble_dismiss_target_padding_y"
+ android:paddingTop="@dimen/bubble_dismiss_target_padding_y"
+ android:paddingLeft="@dimen/bubble_dismiss_target_padding_x"
+ android:paddingRight="@dimen/bubble_dismiss_target_padding_x"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/bubble_dismiss_close_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:src="@drawable/bubble_dismiss_icon" />
+
+ <TextView
+ android:id="@+id/bubble_dismiss_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="9dp"
+ android:layout_marginBottom="9dp"
+ android:layout_marginLeft="8dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body1"
+ android:textColor="@android:color/white"
+ android:shadowColor="@android:color/black"
+ android:shadowDx="-1"
+ android:shadowDy="1"
+ android:shadowRadius="0.01"
+ android:text="@string/bubble_dismiss_text" />
+
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/bubble_dismiss_circle"
+ android:layout_width="@dimen/bubble_dismiss_encircle_size"
+ android:layout_height="@dimen/bubble_dismiss_encircle_size"
+ android:layout_gravity="center"
+ android:alpha="0"
+ android:background="@drawable/bubble_dismiss_circle" />
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 07d81c037e21..a53855825105 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -68,4 +68,10 @@
<!-- The color of the text in the Global Actions menu -->
<color name="global_actions_alert_text">@color/GM2_red_300</color>
+
+ <!-- Biometric dialog colors -->
+ <color name="biometric_dialog_gray">#ff888888</color>
+ <color name="biometric_dialog_accent">#ff80cbc4</color> <!-- light teal -->
+ <color name="biometric_dialog_error">#fff28b82</color> <!-- red 300 -->
+
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2200cf0112ac..14a120b36279 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -157,7 +157,9 @@
<!-- Biometric dialog colors -->
<color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black -->
- <color name="biometric_face_icon_gray">#ff757575</color>
+ <color name="biometric_dialog_gray">#ff757575</color>
+ <color name="biometric_dialog_accent">#ff008577</color> <!-- dark teal -->
+ <color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
<!-- Logout button -->
<color name="logout_button_bg_color">#ccffffff</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0fa542c498bc..e53798d083fc 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1095,6 +1095,8 @@
<dimen name="bubble_padding">8dp</dimen>
<!-- Size of individual bubbles. -->
<dimen name="individual_bubble_size">52dp</dimen>
+ <!-- Size of the circle around the bubbles when they're in the dismiss target. -->
+ <dimen name="bubble_dismiss_encircle_size">56dp</dimen>
<!-- How much to inset the icon in the circle -->
<dimen name="bubble_icon_inset">16dp</dimen>
<!-- Padding around the view displayed when the bubble is expanded -->
@@ -1131,7 +1133,12 @@
<dimen name="bubble_header_icon_size">48dp</dimen>
<!-- Space between the pointer triangle and the bubble expanded view -->
<dimen name="bubble_pointer_margin">8dp</dimen>
-
+ <!-- Height of the permission prompt shown with bubbles -->
+ <dimen name="bubble_permission_height">120dp</dimen>
+ <!-- Padding applied to the bubble dismiss target. Touches in this padding cause the bubbles to
+ snap to the dismiss target. -->
+ <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
+ <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<!-- Size of the RAT type for CellularTile -->
<dimen name="celltile_rat_type_size">10sp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ba72b6b85ad..13a20bf9bb7c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2442,4 +2442,6 @@
<string name="bubble_accessibility_action_move_bottom_left">Move bottom left</string>
<!-- Action in accessibility menu to move the stack of bubbles to the bottom right of the screen. [CHAR LIMIT=30]-->
<string name="bubble_accessibility_action_move_bottom_right">Move bottom right</string>
+ <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=20] -->
+ <string name="bubble_dismiss_text">Dismiss</string>
</resources>
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/biometrics/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index f17fcbab31c4..f25b580b13a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -18,7 +18,6 @@ package com.android.systemui.biometrics;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
@@ -93,6 +92,7 @@ public abstract class BiometricDialogView extends LinearLayout {
protected final int mTextColor;
private Bundle mBundle;
+ private Bundle mRestoredState;
private int mState;
private boolean mAnimatingAway;
@@ -151,12 +151,8 @@ public abstract class BiometricDialogView extends LinearLayout {
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
mAnimationTranslationOffset = getResources()
.getDimension(R.dimen.biometric_dialog_animation_translation_offset);
-
- TypedArray array = getContext().obtainStyledAttributes(
- new int[]{android.R.attr.colorError, android.R.attr.textColorSecondary});
- mErrorColor = array.getColor(0, 0);
- mTextColor = array.getColor(1, 0);
- array.recycle();
+ mErrorColor = getResources().getColor(R.color.biometric_dialog_error);
+ mTextColor = getResources().getColor(R.color.biometric_dialog_gray);
DisplayMetrics metrics = new DisplayMetrics();
mWindowManager.getDefaultDisplay().getMetrics(metrics);
@@ -225,7 +221,6 @@ public abstract class BiometricDialogView extends LinearLayout {
mTryAgainButton.setOnClickListener((View v) -> {
updateState(STATE_AUTHENTICATING);
showTryAgainButton(false /* show */);
- handleClearMessage();
mCallback.onTryAgainPressed();
});
@@ -292,7 +287,7 @@ public abstract class BiometricDialogView extends LinearLayout {
mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
- if (requiresConfirmation()) {
+ if (requiresConfirmation() && mRestoredState == null) {
mPositiveButton.setVisibility(View.VISIBLE);
mPositiveButton.setEnabled(false);
}
@@ -449,6 +444,7 @@ public abstract class BiometricDialogView extends LinearLayout {
if (newState == STATE_PENDING_CONFIRMATION) {
mHandler.removeMessages(MSG_CLEAR_MESSAGE);
mErrorText.setVisibility(View.INVISIBLE);
+ mPositiveButton.setVisibility(View.VISIBLE);
mPositiveButton.setEnabled(true);
} else if (newState == STATE_AUTHENTICATED) {
mPositiveButton.setVisibility(View.GONE);
@@ -471,6 +467,7 @@ public abstract class BiometricDialogView extends LinearLayout {
}
public void restoreState(Bundle bundle) {
+ mRestoredState = bundle;
mTryAgainButton.setVisibility(bundle.getInt(KEY_TRY_AGAIN_VISIBILITY));
mPositiveButton.setVisibility(bundle.getInt(KEY_CONFIRM_VISIBILITY));
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index 9a0b1906dd4a..9679d26d4bfe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -360,8 +360,6 @@ public class FaceDialogView extends BiometricDialogView {
if (show) {
mPositiveButton.setVisibility(View.GONE);
- } else if (!show && requiresConfirmation()) {
- mPositiveButton.setVisibility(View.VISIBLE);
}
}
@@ -402,6 +400,12 @@ public class FaceDialogView extends BiometricDialogView {
} else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATING) {
mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
mIconController.startPulsing();
+ } else if (oldState == STATE_ERROR && newState == STATE_PENDING_CONFIRMATION) {
+ mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
+ mIconController.animateOnce(R.drawable.face_dialog_wink_from_dark);
+ } else if (oldState == STATE_ERROR && newState == STATE_AUTHENTICATED) {
+ mHandler.removeCallbacks(mErrorToIdleAnimationRunnable);
+ mIconController.animateOnce(R.drawable.face_dialog_dark_to_checkmark);
} else if (oldState == STATE_AUTHENTICATING && newState == STATE_ERROR) {
mIconController.animateOnce(R.drawable.face_dialog_dark_to_error);
mHandler.postDelayed(mErrorToIdleAnimationRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
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..48edf67a3ed4 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;
@@ -103,7 +108,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
static final int DISMISS_ACCESSIBILITY_ACTION = 6;
static final int DISMISS_NO_LONGER_BUBBLE = 7;
- static final int MAX_BUBBLES = 5; // TODO: actually enforce this
+ public static final int MAX_BUBBLES = 5; // TODO: actually enforce this
// Enables some subset of notifs to automatically become bubbles
private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
@@ -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/BubbleDismissView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
new file mode 100644
index 000000000000..4db1e276f431
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDismissView.java
@@ -0,0 +1,227 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.bubbles;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.systemui.R;
+
+/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
+public class BubbleDismissView extends FrameLayout {
+ /** Duration for animations involving the dismiss target text/icon/gradient. */
+ private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
+
+ private View mDismissGradient;
+
+ private LinearLayout mDismissTarget;
+ private ImageView mDismissIcon;
+ private TextView mDismissText;
+ private View mDismissCircle;
+
+ private SpringAnimation mDismissTargetAlphaSpring;
+ private SpringAnimation mDismissTargetVerticalSpring;
+
+ public BubbleDismissView(Context context) {
+ super(context);
+ setVisibility(GONE);
+
+ mDismissGradient = new FrameLayout(mContext);
+
+ FrameLayout.LayoutParams gradientParams =
+ new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
+ gradientParams.gravity = Gravity.BOTTOM;
+ mDismissGradient.setLayoutParams(gradientParams);
+
+ Drawable gradient = mContext.getResources().getDrawable(R.drawable.pip_dismiss_scrim);
+ gradient.setAlpha((int) (255 * 0.85f));
+ mDismissGradient.setBackground(gradient);
+
+ mDismissGradient.setVisibility(GONE);
+ addView(mDismissGradient);
+
+ LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
+ mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
+ mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
+ mDismissText = findViewById(R.id.bubble_dismiss_text);
+ mDismissCircle = findViewById(R.id.bubble_dismiss_circle);
+
+ // Set up the basic target area animations. These are very simple animations that don't need
+ // fancy interpolators.
+ final AccelerateDecelerateInterpolator interpolator =
+ new AccelerateDecelerateInterpolator();
+ mDismissGradient.animate()
+ .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
+ .setInterpolator(interpolator);
+ mDismissText.animate()
+ .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
+ .setInterpolator(interpolator);
+ mDismissIcon.animate()
+ .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
+ .setInterpolator(interpolator);
+ mDismissCircle.animate()
+ .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2)
+ .setInterpolator(interpolator);
+
+ mDismissTargetAlphaSpring =
+ new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA)
+ .setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+ mDismissTargetVerticalSpring =
+ new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y)
+ .setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
+
+ mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> {
+ // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being
+ // exactly zero when this listener is triggered. However, if it's less than 50% we can
+ // safely assume it was animating out rather than in.
+ if (alpha < 0.5f) {
+ // If the alpha spring was animating the view out, set it to GONE when it's done.
+ setVisibility(GONE);
+ }
+ });
+ }
+
+ /** Springs in the dismiss target and fades in the gradient. */
+ void springIn() {
+ setVisibility(View.VISIBLE);
+
+ // Fade in the dismiss target (icon + text).
+ mDismissTarget.setAlpha(0f);
+ mDismissTargetAlphaSpring.animateToFinalPosition(1f);
+
+ // Spring up the dismiss target (icon + text).
+ mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
+ mDismissTargetVerticalSpring.animateToFinalPosition(0);
+
+ // Fade in the gradient.
+ mDismissGradient.setVisibility(VISIBLE);
+ mDismissGradient.animate().alpha(1f);
+
+ // Make sure the dismiss elements are in the separated position (in case we hid the target
+ // while they were condensed to cover the bubbles being in the target).
+ mDismissIcon.setAlpha(1f);
+ mDismissIcon.setScaleX(1f);
+ mDismissIcon.setScaleY(1f);
+ mDismissIcon.setTranslationX(0f);
+ mDismissText.setAlpha(1f);
+ mDismissText.setTranslationX(0f);
+ }
+
+ /** Springs out the dismiss target and fades out the gradient. */
+ void springOut() {
+ // Fade out the target.
+ mDismissTargetAlphaSpring.animateToFinalPosition(0f);
+
+ // Spring the target down a bit.
+ mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);
+
+ // Fade out the gradient and then set it to GONE so it's not in the SBV hierarchy.
+ mDismissGradient.animate().alpha(0f).withEndAction(
+ () -> mDismissGradient.setVisibility(GONE));
+
+ // Pop out the dismiss circle.
+ mDismissCircle.animate().alpha(0f).scaleX(1.2f).scaleY(1.2f);
+ }
+
+ /**
+ * Encircles the center of the dismiss target, pulling the X towards the center and hiding the
+ * text.
+ */
+ void animateEncircleCenterWithX(boolean encircle) {
+ // Pull the text towards the center if we're encircling (it'll be faded out, leaving only
+ // the X icon over the bubbles), or back to normal if we're un-encircling.
+ final float textTranslation = encircle
+ ? -mDismissIcon.getWidth() / 4f
+ : 0f;
+
+ // Center the icon if we're encircling, or put it back to normal if not.
+ final float iconTranslation = encircle
+ ? mDismissTarget.getWidth() / 2f
+ - mDismissIcon.getWidth() / 2f
+ - mDismissIcon.getLeft()
+ : 0f;
+
+ // Fade in/out the text and translate it.
+ mDismissText.animate()
+ .alpha(encircle ? 0f : 1f)
+ .translationX(textTranslation);
+
+ mDismissIcon.animate()
+ .setDuration(150)
+ .translationX(iconTranslation);
+
+ // Fade out the gradient if we're encircling (the bubbles will 'absorb' it by darkening
+ // themselves).
+ mDismissGradient.animate()
+ .alpha(encircle ? 0f : 1f);
+
+ // Prepare the circle to be 'dropped in'.
+ if (encircle) {
+ mDismissCircle.setAlpha(0f);
+ mDismissCircle.setScaleX(1.2f);
+ mDismissCircle.setScaleY(1.2f);
+ }
+
+ // Drop in the circle, or pull it back up.
+ mDismissCircle.animate()
+ .alpha(encircle ? 1f : 0f)
+ .scaleX(encircle ? 1f : 0f)
+ .scaleY(encircle ? 1f : 0f);
+ }
+
+ /** Animates the circle and the centered icon out. */
+ void animateEncirclingCircleDisappearance() {
+ // Pop out the dismiss icon and circle.
+ mDismissIcon.animate()
+ .setDuration(50)
+ .scaleX(0.9f)
+ .scaleY(0.9f)
+ .alpha(0f);
+ mDismissCircle.animate()
+ .scaleX(0.9f)
+ .scaleY(0.9f)
+ .alpha(0f);
+ }
+
+ /** Returns the Y value of the center of the dismiss target. */
+ float getDismissTargetCenterY() {
+ return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f;
+ }
+
+ /** Returns the dismiss target, which contains the text/icon and any added padding. */
+ View getDismissTarget() {
+ return mDismissTarget;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 123d73dc6432..2b1742592fba 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -19,12 +19,18 @@ package com.android.systemui.bubbles;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
import android.graphics.Outline;
+import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
@@ -32,6 +38,8 @@ import android.graphics.RectF;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.StatsLog;
@@ -84,6 +92,9 @@ public class BubbleStackView extends FrameLayout {
/** Max width of the flyout, in terms of percent of the screen width. */
private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
+ /** Percent to darken the bubbles when they're in the dismiss target. */
+ private static final float DARKEN_PERCENT = 0.3f;
+
/** How long to wait, in milliseconds, before hiding the flyout. */
@VisibleForTesting
static final int FLYOUT_HIDE_AFTER = 5000;
@@ -131,6 +142,10 @@ public class BubbleStackView extends FrameLayout {
private final SpringAnimation mExpandedViewYAnim;
private final BubbleData mBubbleData;
+ private final Vibrator mVibrator;
+ private final ValueAnimator mDesaturateAndDarkenAnimator;
+ private final Paint mDesaturateAndDarkenPaint = new Paint();
+
private PhysicsAnimationLayout mBubbleContainer;
private StackAnimationController mStackAnimationController;
private ExpandedAnimationController mExpandedAnimationController;
@@ -183,6 +198,20 @@ public class BubbleStackView extends FrameLayout {
private boolean mViewUpdatedRequested = false;
private boolean mIsExpansionAnimating = false;
+ private boolean mShowingDismiss = false;
+
+ /**
+ * Whether the user is currently dragging their finger within the dismiss target. In this state
+ * the stack will be magnetized to the center of the target, so we shouldn't move it until the
+ * touch exits the dismiss target area.
+ */
+ private boolean mDraggingInDismissTarget = false;
+
+ /** Whether the stack is magneting towards the dismiss target. */
+ private boolean mAnimatingMagnet = false;
+
+ /** The view to desaturate/darken when magneted to the dismiss target. */
+ private View mDesaturateAndDarkenTargetView;
private LayoutInflater mInflater;
@@ -222,6 +251,8 @@ public class BubbleStackView extends FrameLayout {
@NonNull private final SurfaceSynchronizer mSurfaceSynchronizer;
+ private BubbleDismissView mDismissContainer;
+ private Runnable mAfterMagnet;
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer) {
@@ -253,6 +284,8 @@ public class BubbleStackView extends FrameLayout {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getSize(mDisplaySize);
+ mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+
int padding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -286,6 +319,13 @@ public class BubbleStackView extends FrameLayout {
addView(mFlyoutContainer);
setupFlyout();
+ mDismissContainer = new BubbleDismissView(mContext);
+ mDismissContainer.setLayoutParams(new FrameLayout.LayoutParams(
+ MATCH_PARENT,
+ getResources().getDimensionPixelSize(R.dimen.pip_dismiss_gradient_height),
+ Gravity.BOTTOM));
+ addView(mDismissContainer);
+
mExpandedViewXAnim =
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
mExpandedViewXAnim.setSpring(
@@ -342,6 +382,29 @@ public class BubbleStackView extends FrameLayout {
// This must be a separate OnDrawListener since it should be called for every draw.
getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
+
+ final ColorMatrix animatedMatrix = new ColorMatrix();
+ final ColorMatrix darkenMatrix = new ColorMatrix();
+
+ mDesaturateAndDarkenAnimator = ValueAnimator.ofFloat(1f, 0f);
+ mDesaturateAndDarkenAnimator.addUpdateListener(animation -> {
+ final float animatedValue = (float) animation.getAnimatedValue();
+ animatedMatrix.setSaturation(animatedValue);
+
+ final float animatedDarkenValue = (1f - animatedValue) * DARKEN_PERCENT;
+ darkenMatrix.setScale(
+ 1f - animatedDarkenValue /* red */,
+ 1f - animatedDarkenValue /* green */,
+ 1f - animatedDarkenValue /* blue */,
+ 1f /* alpha */);
+
+ // Concat the matrices so that the animatedMatrix both desaturates and darkens.
+ animatedMatrix.postConcat(darkenMatrix);
+
+ // Update the paint and apply it to the bubble container.
+ mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
+ mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
+ });
}
/**
@@ -549,6 +612,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 +634,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
@@ -828,23 +901,22 @@ public class BubbleStackView extends FrameLayout {
}
mExpandedAnimationController.dragBubbleOut(bubble, x, y);
+ springInDismissTarget();
}
/** Called when a drag operation on an individual bubble has finished. */
public void onBubbleDragFinish(
- View bubble, float x, float y, float velX, float velY, boolean dismissed) {
+ View bubble, float x, float y, float velX, float velY) {
if (DEBUG) {
- Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble + ", dismissed=" + dismissed);
+ Log.d(TAG, "onBubbleDragFinish: bubble=" + bubble);
}
+
if (!mIsExpanded || mIsExpansionAnimating) {
return;
}
- if (dismissed) {
- mExpandedAnimationController.prepareForDismissalWithVelocity(bubble, velX, velY);
- } else {
- mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
- }
+ mExpandedAnimationController.snapBubbleBack(bubble, velX, velY);
+ springOutDismissTargetAndHideCircle();
}
void onDragStart() {
@@ -860,6 +932,7 @@ public class BubbleStackView extends FrameLayout {
hideFlyoutImmediate();
mIsDragging = true;
+ mDraggingInDismissTarget = false;
}
void onDragged(float x, float y) {
@@ -867,7 +940,8 @@ public class BubbleStackView extends FrameLayout {
return;
}
- mStackAnimationController.moveFirstBubbleWithStackFollowing(x, y);
+ springInDismissTarget();
+ mStackAnimationController.moveStackFromTouch(x, y);
}
void onDragFinish(float x, float y, float velX, float velY) {
@@ -884,10 +958,171 @@ public class BubbleStackView extends FrameLayout {
mStackAnimationController.flingStackThenSpringToEdge(x, velX, velY);
logBubbleEvent(null /* no bubble associated with bubble stack move */,
StatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
+
+ springOutDismissTargetAndHideCircle();
}
- void onDragFinishAsDismiss() {
- mIsDragging = false;
+ /** Prepares and starts the desaturate/darken animation on the bubble stack. */
+ private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
+ mDesaturateAndDarkenTargetView = targetView;
+
+ if (desaturateAndDarken) {
+ // Use the animated paint for the bubbles.
+ mDesaturateAndDarkenTargetView.setLayerType(
+ View.LAYER_TYPE_HARDWARE, mDesaturateAndDarkenPaint);
+ mDesaturateAndDarkenAnimator.removeAllListeners();
+ mDesaturateAndDarkenAnimator.start();
+ } else {
+ mDesaturateAndDarkenAnimator.removeAllListeners();
+ mDesaturateAndDarkenAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ // Stop using the animated paint.
+ resetDesaturationAndDarken();
+ }
+ });
+ mDesaturateAndDarkenAnimator.reverse();
+ }
+ }
+
+ private void resetDesaturationAndDarken() {
+ mDesaturateAndDarkenAnimator.removeAllListeners();
+ mDesaturateAndDarkenAnimator.cancel();
+ mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ /**
+ * Magnets the stack to the target, while also transforming the target to encircle the stack and
+ * desaturating/darkening the bubbles.
+ */
+ void animateMagnetToDismissTarget(
+ View magnetView, boolean toTarget, float x, float y, float velX, float velY) {
+ mDraggingInDismissTarget = toTarget;
+
+ if (toTarget) {
+ // The Y-value for the bubble stack to be positioned in the center of the dismiss target
+ final float destY = mDismissContainer.getDismissTargetCenterY() - mBubbleSize / 2f;
+
+ mAnimatingMagnet = true;
+
+ final Runnable afterMagnet = () -> {
+ mAnimatingMagnet = false;
+ if (mAfterMagnet != null) {
+ mAfterMagnet.run();
+ }
+ };
+
+ if (magnetView == this) {
+ mStackAnimationController.magnetToDismiss(velX, velY, destY, afterMagnet);
+ animateDesaturateAndDarken(mBubbleContainer, true);
+ } else {
+ mExpandedAnimationController.magnetBubbleToDismiss(
+ magnetView, velX, velY, destY, afterMagnet);
+
+ animateDesaturateAndDarken(magnetView, true);
+ }
+
+ mDismissContainer.animateEncircleCenterWithX(true);
+
+ } else {
+ mAnimatingMagnet = false;
+
+ if (magnetView == this) {
+ mStackAnimationController.demagnetizeFromDismissToPoint(x, y, velX, velY);
+ animateDesaturateAndDarken(mBubbleContainer, false);
+ } else {
+ mExpandedAnimationController.demagnetizeBubbleTo(x, y, velX, velY);
+ animateDesaturateAndDarken(magnetView, false);
+ }
+
+ mDismissContainer.animateEncircleCenterWithX(false);
+ }
+
+ mVibrator.vibrate(VibrationEffect.get(toTarget
+ ? VibrationEffect.EFFECT_CLICK
+ : VibrationEffect.EFFECT_TICK));
+ }
+
+ /**
+ * Magnets the stack to the dismiss target if it's not already there. Then, dismiss the stack
+ * using the 'implode' animation and animate out the target.
+ */
+ void magnetToStackIfNeededThenAnimateDismissal(
+ View touchedView, float velX, float velY, Runnable after) {
+ final Runnable animateDismissal = () -> {
+ mAfterMagnet = null;
+
+ mVibrator.vibrate(VibrationEffect.get(VibrationEffect.EFFECT_CLICK));
+ mDismissContainer.animateEncirclingCircleDisappearance();
+
+ // 'Implode' the stack and then hide the dismiss target.
+ if (touchedView == this) {
+ mStackAnimationController.implodeStack(
+ () -> {
+ mAnimatingMagnet = false;
+ mShowingDismiss = false;
+ mDraggingInDismissTarget = false;
+ after.run();
+ resetDesaturationAndDarken();
+ });
+ } else {
+ mExpandedAnimationController.dismissDraggedOutBubble(() -> {
+ mAnimatingMagnet = false;
+ mShowingDismiss = false;
+ mDraggingInDismissTarget = false;
+ resetDesaturationAndDarken();
+ after.run();
+ });
+ }
+ };
+
+ if (mAnimatingMagnet) {
+ // If the magnet animation is currently playing, dismiss the stack after it's done. This
+ // happens if the stack is flung towards the target.
+ mAfterMagnet = animateDismissal;
+ } else if (mDraggingInDismissTarget) {
+ // If we're in the dismiss target, but not animating, we already magneted - dismiss
+ // immediately.
+ animateDismissal.run();
+ } else {
+ // Otherwise, we need to start the magnet animation and then dismiss afterward.
+ animateMagnetToDismissTarget(touchedView, true, -1 /* x */, -1 /* y */, velX, velY);
+ mAfterMagnet = animateDismissal;
+ }
+ }
+
+ /** Animates in the dismiss target, including the gradient behind it. */
+ private void springInDismissTarget() {
+ if (mShowingDismiss) {
+ return;
+ }
+
+ mShowingDismiss = true;
+
+ // Show the dismiss container and bring it to the front so the bubbles will go behind it.
+ mDismissContainer.springIn();
+ mDismissContainer.bringToFront();
+ mDismissContainer.setZ(Short.MAX_VALUE - 1);
+ }
+
+ /**
+ * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
+ * were dragged into the target and encircled.
+ */
+ private void springOutDismissTargetAndHideCircle() {
+ if (!mShowingDismiss) {
+ return;
+ }
+
+ mDismissContainer.springOut();
+ mShowingDismiss = false;
+ }
+
+
+ /** Whether the location of the given MotionEvent is within the dismiss target area. */
+ public boolean isInDismissTarget(MotionEvent ev) {
+ return isIntersecting(mDismissContainer.getDismissTarget(), ev.getRawX(), ev.getRawY());
}
/**
@@ -1056,7 +1291,7 @@ public class BubbleStackView extends FrameLayout {
private void setupFlyout() {
// Retrieve the styled floating background color.
TypedArray ta = mContext.obtainStyledAttributes(
- new int[] {android.R.attr.colorBackgroundFloating});
+ new int[]{android.R.attr.colorBackgroundFloating});
final int floatingBackgroundColor = ta.getColor(0, Color.WHITE);
ta.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 82e6279772f4..f429c2c124b3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -16,8 +16,6 @@
package com.android.systemui.bubbles;
-import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
-
import android.content.Context;
import android.graphics.PointF;
import android.os.Handler;
@@ -27,17 +25,35 @@ import android.view.View;
import android.view.ViewConfiguration;
import com.android.systemui.Dependency;
-import com.android.systemui.pip.phone.PipDismissViewController;
/**
* Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
* dismissing, and flings.
*/
class BubbleTouchHandler implements View.OnTouchListener {
- /** Velocity required to dismiss a bubble without dragging it into the dismiss target. */
- private static final float DISMISS_MIN_VELOCITY = 4000f;
+ /** Velocity required to dismiss the stack without dragging it into the dismiss target. */
+ private static final float STACK_DISMISS_MIN_VELOCITY = 4000f;
+
+ /**
+ * Velocity required to dismiss an individual bubble without dragging it into the dismiss
+ * target.
+ *
+ * This is higher than the stack dismiss velocity since unlike the stack, a downward fling could
+ * also be an attempted gesture to return the bubble to the row of expanded bubbles, which would
+ * usually be below the dragged bubble. By increasing the required velocity, it's less likely
+ * that the user is trying to drop it back into the row vs. fling it away.
+ */
+ private static final float INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY = 6000f;
private static final String TAG = "BubbleTouchHandler";
+ /**
+ * When the stack is flung towards the bottom of the screen, it'll be dismissed if it's flung
+ * towards the center of the screen (where the dismiss target is). This value is the width of
+ * the target area to be considered 'towards the target'. For example 50% means that the stack
+ * needs to be flung towards the middle 50%, and the 25% on the left and right sides won't
+ * count.
+ */
+ private static final float DISMISS_FLING_TARGET_WIDTH_PERCENT = 0.5f;
private final PointF mTouchDown = new PointF();
private final PointF mViewPositionOnTouchDown = new PointF();
@@ -45,7 +61,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
private final BubbleData mBubbleData;
private BubbleController mController = Dependency.get(BubbleController.class);
- private PipDismissViewController mDismissViewController;
private boolean mMovedEnough;
private int mTouchSlopSquared;
@@ -53,12 +68,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
private boolean mInDismissTarget;
private Handler mHandler = new Handler();
- private Runnable mShowDismissAffordance = new Runnable() {
- @Override
- public void run() {
- mDismissViewController.showDismissTarget();
- }
- };
/** View that was initially touched, when we received the first ACTION_DOWN event. */
private View mTouchedView;
@@ -67,7 +76,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
BubbleData bubbleData, Context context) {
final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTouchSlopSquared = touchSlop * touchSlop;
- mDismissViewController = new PipDismissViewController(context);
mBubbleData = bubbleData;
mStack = stackView;
}
@@ -104,11 +112,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
mTouchDown.set(rawX, rawY);
- if (!isFlyout) {
- mDismissViewController.createDismissTarget();
- mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
- }
-
if (isStack) {
mViewPositionOnTouchDown.set(mStack.getStackPosition());
mStack.onDragStart();
@@ -140,9 +143,18 @@ class BubbleTouchHandler implements View.OnTouchListener {
}
}
- // TODO - when we're in the target stick to it / animate in some way?
- mInDismissTarget = mDismissViewController.updateTarget(
- isStack ? mStack.getBubbleAt(0) : mTouchedView);
+ final boolean currentlyInDismissTarget = mStack.isInDismissTarget(event);
+ if (currentlyInDismissTarget != mInDismissTarget) {
+ mInDismissTarget = currentlyInDismissTarget;
+
+ mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
+ final float velX = mVelocityTracker.getXVelocity();
+ final float velY = mVelocityTracker.getYVelocity();
+
+ // If the touch event is within the dismiss target, magnet the stack to it.
+ mStack.animateMagnetToDismissTarget(
+ mTouchedView, mInDismissTarget, viewX, viewY, velX, velY);
+ }
break;
case MotionEvent.ACTION_CANCEL:
@@ -151,28 +163,40 @@ class BubbleTouchHandler implements View.OnTouchListener {
case MotionEvent.ACTION_UP:
trackMovement(event);
- if (mInDismissTarget && isStack) {
- mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
- mStack.onDragFinishAsDismiss();
+ mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
+ final float velX = mVelocityTracker.getXVelocity();
+ final float velY = mVelocityTracker.getYVelocity();
+
+ final boolean shouldDismiss =
+ isStack
+ ? mInDismissTarget
+ || isFastFlingTowardsDismissTarget(rawX, rawY, velX, velY)
+ : mInDismissTarget
+ || velY > INDIVIDUAL_BUBBLE_DISMISS_MIN_VELOCITY;
+
+ if (shouldDismiss) {
+ final String individualBubbleKey =
+ isStack ? null : ((BubbleView) mTouchedView).getKey();
+ mStack.magnetToStackIfNeededThenAnimateDismissal(mTouchedView, velX, velY,
+ () -> {
+ if (isStack) {
+ mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
+ } else {
+ mController.removeBubble(
+ individualBubbleKey,
+ BubbleController.DISMISS_USER_GESTURE);
+ }
+ });
} else if (isFlyout) {
// TODO(b/129768381): Expand if tapped, dismiss if swiped away.
if (!mBubbleData.isExpanded() && !mMovedEnough) {
mBubbleData.setExpanded(true);
}
} else if (mMovedEnough) {
- mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
- final float velX = mVelocityTracker.getXVelocity();
- final float velY = mVelocityTracker.getYVelocity();
if (isStack) {
mStack.onDragFinish(viewX, viewY, velX, velY);
} else {
- final boolean dismissed = mInDismissTarget || velY > DISMISS_MIN_VELOCITY;
- mStack.onBubbleDragFinish(
- mTouchedView, viewX, viewY, velX, velY, /* dismissed */ dismissed);
- if (dismissed) {
- mController.removeBubble(((BubbleView) mTouchedView).getKey(),
- BubbleController.DISMISS_USER_GESTURE);
- }
+ mStack.onBubbleDragFinish(mTouchedView, viewX, viewY, velX, velY);
}
} else if (mTouchedView == mStack.getExpandedBubbleView()) {
mBubbleData.setExpanded(false);
@@ -191,9 +215,38 @@ class BubbleTouchHandler implements View.OnTouchListener {
return true;
}
+ /**
+ * Whether the given touch data represents a powerful fling towards the bottom-center of the
+ * screen (the dismiss target).
+ */
+ private boolean isFastFlingTowardsDismissTarget(
+ float rawX, float rawY, float velX, float velY) {
+ // Not a fling downward towards the target if velocity is zero or negative.
+ if (velY <= 0) {
+ return false;
+ }
+
+ float bottomOfScreenInterceptX = rawX;
+
+ // Only do math if the X velocity is non-zero, otherwise X won't change.
+ if (velX != 0) {
+ // Rise over run...
+ final float slope = velY / velX;
+ // ...y = mx + b, b = y / mx...
+ final float yIntercept = rawY - slope * rawX;
+ // ...calculate the x value when y = bottom of the screen.
+ bottomOfScreenInterceptX = (mStack.getHeight() - yIntercept) / slope;
+ }
+
+ final float dismissTargetWidth =
+ mStack.getWidth() * DISMISS_FLING_TARGET_WIDTH_PERCENT;
+ return velY > STACK_DISMISS_MIN_VELOCITY
+ && bottomOfScreenInterceptX > dismissTargetWidth / 2f
+ && bottomOfScreenInterceptX < mStack.getWidth() - dismissTargetWidth / 2f;
+ }
+
/** Clears all touch-related state. */
private void resetForNextGesture() {
- cleanUpDismissTarget();
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -203,15 +256,6 @@ class BubbleTouchHandler implements View.OnTouchListener {
mInDismissTarget = false;
}
- /**
- * Removes the dismiss target and cancels any pending callbacks to show it.
- */
- private void cleanUpDismissTarget() {
- mHandler.removeCallbacks(mShowDismissAffordance);
- mDismissViewController.destroyDismissTarget();
- }
-
-
private void trackMovement(MotionEvent event) {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 95fbfe33ee71..a9ad464867a2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -64,6 +64,20 @@ public class ExpandedAnimationController
/** Size of dismiss target at bottom of screen. */
private float mPipDismissHeight;
+ /** Whether the dragged-out bubble is in the dismiss target. */
+ private boolean mIndividualBubbleWithinDismissTarget = false;
+
+ /**
+ * Whether the dragged out bubble is springing towards the touch point, rather than using the
+ * default behavior of moving directly to the touch point.
+ *
+ * This happens when the user's finger exits the dismiss area while the bubble is magnetized to
+ * the center. Since the touch point differs from the bubble location, we need to animate the
+ * bubble back to the touch point to avoid a jarring instant location change from the center of
+ * the target to the touch point just outside the target bounds.
+ */
+ private boolean mSpringingBubbleToTouch = false;
+
public ExpandedAnimationController(Point displaySize) {
mDisplaySize = displaySize;
}
@@ -151,8 +165,23 @@ public class ExpandedAnimationController
* bubble is dragged back into the row.
*/
public void dragBubbleOut(View bubbleView, float x, float y) {
- bubbleView.setTranslationX(x);
- bubbleView.setTranslationY(y);
+ if (mSpringingBubbleToTouch) {
+ if (mLayout.arePropertiesAnimatingOnView(
+ bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) {
+ animationForChild(mBubbleDraggingOut)
+ .translationX(x)
+ .translationY(y)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .start();
+ } else {
+ mSpringingBubbleToTouch = false;
+ }
+ }
+
+ if (!mSpringingBubbleToTouch && !mIndividualBubbleWithinDismissTarget) {
+ bubbleView.setTranslationX(x);
+ bubbleView.setTranslationY(y);
+ }
final boolean draggedOutEnough =
y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
@@ -164,6 +193,53 @@ public class ExpandedAnimationController
}
}
+ /** Plays a dismiss animation on the dragged out bubble. */
+ public void dismissDraggedOutBubble(Runnable after) {
+ mIndividualBubbleWithinDismissTarget = false;
+
+ // Fill the space from the soon to be dismissed bubble.
+ animateStackByBubbleWidthsStartingFrom(
+ /* numBubbleWidths */ -1,
+ /* startIndex */ mLayout.indexOfChild(mBubbleDraggingOut) + 1);
+
+ animationForChild(mBubbleDraggingOut)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .scaleX(1.1f)
+ .scaleY(1.1f)
+ .alpha(0f, after)
+ .start();
+ }
+
+ /** Magnets the given bubble to the dismiss target. */
+ public void magnetBubbleToDismiss(
+ View bubbleView, float velX, float velY, float destY, Runnable after) {
+ mIndividualBubbleWithinDismissTarget = true;
+ mSpringingBubbleToTouch = false;
+ animationForChild(bubbleView)
+ .withStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .withPositionStartVelocities(velX, velY)
+ .translationX(mLayout.getWidth() / 2f - mBubbleSizePx / 2f)
+ .translationY(destY, after)
+ .start();
+ }
+
+ /**
+ * Springs the dragged-out bubble towards the given coordinates and sets flags to have touch
+ * events update the spring's final position until it's settled.
+ */
+ public void demagnetizeBubbleTo(float x, float y, float velX, float velY) {
+ mIndividualBubbleWithinDismissTarget = false;
+ mSpringingBubbleToTouch = true;
+
+ animationForChild(mBubbleDraggingOut)
+ .translationX(x)
+ .translationY(y)
+ .withPositionStartVelocities(velX, velY)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .start();
+ }
+
/**
* Snaps a bubble back to its position within the bubble row, and animates the rest of the
* bubbles to accommodate it if it was previously dragged out past the threshold.
@@ -274,28 +350,21 @@ public class ExpandedAnimationController
@Override
void onChildRemoved(View child, int index, Runnable finishRemoval) {
- // Bubble pops out to the top.
- // TODO: Reverse this when bubbles are at the bottom.
-
final PhysicsAnimationLayout.PhysicsPropertyAnimator animator = animationForChild(child);
- animator.alpha(0f, finishRemoval /* endAction */);
// If we're removing the dragged-out bubble, that means it got dismissed.
if (child.equals(mBubbleDraggingOut)) {
- animator.position(
- mLayout.getWidth() / 2f - mBubbleSizePx / 2f,
- mLayout.getHeight() + mBubbleSizePx)
- .withPositionStartVelocities(mBubbleDraggingOutVelX, mBubbleDraggingOutVelY)
- .scaleX(ANIMATE_SCALE_PERCENT)
- .scaleY(ANIMATE_SCALE_PERCENT);
-
mBubbleDraggingOut = null;
+ finishRemoval.run();
} else {
- animator.translationY(getExpandedY() - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR);
+ animator.alpha(0f, finishRemoval /* endAction */)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ .scaleX(1.1f)
+ .scaleY(1.1f)
+ .start();
}
- animator.start();
-
// Animate all the other bubbles to their new positions sans this bubble.
animateBubblesAfterIndexToCorrectX(index);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 460652612593..997d2c4627d8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -290,6 +290,10 @@ public class PhysicsAnimationLayout extends FrameLayout {
final Runnable checkIfAllFinished = () -> {
if (!arePropertiesAnimating(properties)) {
action.run();
+
+ for (DynamicAnimation.ViewProperty property : properties) {
+ removeEndActionForProperty(property);
+ }
}
};
@@ -379,10 +383,21 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Checks whether any animations of the given properties are still running. */
public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
for (int i = 0; i < getChildCount(); i++) {
- for (DynamicAnimation.ViewProperty property : properties) {
- if (getAnimationAtIndex(property, i).isRunning()) {
- return true;
- }
+ if (arePropertiesAnimatingOnView(getChildAt(i), properties)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /** Checks whether any animations of the given properties are running on the given view. */
+ public boolean arePropertiesAnimatingOnView(
+ View view, DynamicAnimation.ViewProperty... properties) {
+ for (DynamicAnimation.ViewProperty property : properties) {
+ final SpringAnimation animation = getAnimationFromView(property, view);
+ if (animation != null && animation.isRunning()) {
+ return true;
}
}
@@ -556,7 +571,11 @@ public class PhysicsAnimationLayout extends FrameLayout {
DynamicAnimation anim, boolean canceled, float value, float velocity) {
if (!arePropertiesAnimating(mProperty)) {
if (mEndActionForProperty.containsKey(mProperty)) {
- mEndActionForProperty.get(mProperty).run();
+ final Runnable callback = mEndActionForProperty.get(mProperty);
+
+ if (callback != null) {
+ callback.run();
+ }
}
}
}
@@ -578,6 +597,12 @@ public class PhysicsAnimationLayout extends FrameLayout {
/** Start delay to use when start is called. */
private long mStartDelay = 0;
+ /** Damping ratio to use for the animations. */
+ private float mDampingRatio = -1;
+
+ /** Stiffness to use for the animations. */
+ private float mStiffness = -1;
+
/** End actions to call when animations for the given property complete. */
private Map<DynamicAnimation.ViewProperty, Runnable[]> mEndActionsForProperty =
new HashMap<>();
@@ -687,6 +712,24 @@ public class PhysicsAnimationLayout extends FrameLayout {
}
/**
+ * Set the damping ratio to use for this animation. If not supplied, will default to the
+ * value from {@link PhysicsAnimationController#getSpringForce}.
+ */
+ public PhysicsPropertyAnimator withDampingRatio(float dampingRatio) {
+ mDampingRatio = dampingRatio;
+ return this;
+ }
+
+ /**
+ * Set the stiffness to use for this animation. If not supplied, will default to the
+ * value from {@link PhysicsAnimationController#getSpringForce}.
+ */
+ public PhysicsPropertyAnimator withStiffness(float stiffness) {
+ mStiffness = stiffness;
+ return this;
+ }
+
+ /**
* Set the start velocities to use for TRANSLATION_X and TRANSLATION_Y animations. This
* overrides any value set via {@link #withStartVelocity(float)} for those properties.
*/
@@ -711,12 +754,14 @@ public class PhysicsAnimationLayout extends FrameLayout {
// If there are end actions, set an end listener on the layout for all the properties
// we're about to animate.
- if (after != null) {
+ if (after != null && after.length > 0) {
final DynamicAnimation.ViewProperty[] propertiesArray =
properties.toArray(new DynamicAnimation.ViewProperty[0]);
- for (Runnable callback : after) {
- setEndActionForMultipleProperties(callback, propertiesArray);
- }
+ setEndActionForMultipleProperties(() -> {
+ for (Runnable callback : after) {
+ callback.run();
+ }
+ }, propertiesArray);
}
// If we used position-specific end actions, we'll need to listen for both TRANSLATION_X
@@ -746,12 +791,15 @@ public class PhysicsAnimationLayout extends FrameLayout {
// Actually start the animations.
for (DynamicAnimation.ViewProperty property : properties) {
+ final SpringForce defaultSpringForce = mController.getSpringForce(property, mView);
animateValueForChild(
property,
mView,
mAnimatedProperties.get(property),
mPositionStartVelocities.getOrDefault(property, mDefaultStartVelocity),
mStartDelay,
+ mStiffness >= 0 ? mStiffness : defaultSpringForce.getStiffness(),
+ mDampingRatio >= 0 ? mDampingRatio : defaultSpringForce.getDampingRatio(),
mEndActionsForProperty.get(property));
}
@@ -760,6 +808,8 @@ public class PhysicsAnimationLayout extends FrameLayout {
mPositionStartVelocities.clear();
mDefaultStartVelocity = 0;
mStartDelay = 0;
+ mStiffness = -1;
+ mDampingRatio = -1;
mEndActionsForProperty.clear();
}
@@ -778,6 +828,8 @@ public class PhysicsAnimationLayout extends FrameLayout {
float value,
float startVel,
long startDelay,
+ float stiffness,
+ float dampingRatio,
Runnable[] afterCallbacks) {
if (view != null) {
final SpringAnimation animation =
@@ -795,6 +847,9 @@ public class PhysicsAnimationLayout extends FrameLayout {
});
}
+ animation.getSpring().setStiffness(stiffness);
+ animation.getSpring().setDampingRatio(dampingRatio);
+
if (startVel > 0) {
animation.setStartVelocity(startVel);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index bc249aedc605..f937525cf417 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -30,7 +30,6 @@ import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
import com.google.android.collect.Sets;
@@ -116,6 +115,25 @@ public class StackAnimationController extends
*/
private boolean mIsMovingFromFlinging = false;
+ /**
+ * Whether the stack is within the dismiss target (either by being dragged, magnet'd, or flung).
+ */
+ private boolean mWithinDismissTarget = false;
+
+ /**
+ * Whether the first bubble is springing towards the touch point, rather than using the default
+ * behavior of moving directly to the touch point with the rest of the stack following it.
+ *
+ * This happens when the user's finger exits the dismiss area while the stack is magnetized to
+ * the center. Since the touch point differs from the stack location, we need to animate the
+ * stack back to the touch point to avoid a jarring instant location change from the center of
+ * the target to the touch point just outside the target bounds.
+ *
+ * This is reset once the spring animations end, since that means the first bubble has
+ * successfully 'caught up' to the touch.
+ */
+ private boolean mFirstBubbleSpringingToTouch = false;
+
/** Horizontal offset of bubbles in the stack. */
private float mStackOffset;
/** Diameter of the bubbles themselves. */
@@ -445,6 +463,120 @@ public class StackAnimationController extends
return allowableRegion;
}
+ /** Moves the stack in response to a touch event. */
+ public void moveStackFromTouch(float x, float y) {
+
+ // If we're springing to the touch point to 'catch up' after dragging out of the dismiss
+ // target, then update the stack position animations instead of moving the bubble directly.
+ if (mFirstBubbleSpringingToTouch) {
+ final SpringAnimation springToTouchX =
+ (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_X);
+ final SpringAnimation springToTouchY =
+ (SpringAnimation) mStackPositionAnimations.get(DynamicAnimation.TRANSLATION_Y);
+
+ // If either animation is still running, we haven't caught up. Update the animations.
+ if (springToTouchX.isRunning() || springToTouchY.isRunning()) {
+ springToTouchX.animateToFinalPosition(x);
+ springToTouchY.animateToFinalPosition(y);
+ } else {
+ // If the animations have finished, the stack is now at the touch point. We can
+ // resume moving the bubble directly.
+ mFirstBubbleSpringingToTouch = false;
+ }
+ }
+
+ if (!mFirstBubbleSpringingToTouch && !mWithinDismissTarget) {
+ moveFirstBubbleWithStackFollowing(x, y);
+ }
+ }
+
+ /**
+ * Demagnetizes the stack, springing it towards the given point. This also sets flags so that
+ * subsequent touch events will update the final position of the demagnetization spring instead
+ * of directly moving the bubbles, until demagnetization is complete.
+ */
+ public void demagnetizeFromDismissToPoint(float x, float y, float velX, float velY) {
+ mWithinDismissTarget = false;
+ mFirstBubbleSpringingToTouch = true;
+
+ springFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_X,
+ new SpringForce()
+ .setDampingRatio(DEFAULT_BOUNCINESS)
+ .setStiffness(DEFAULT_STIFFNESS),
+ velX, x);
+
+ springFirstBubbleWithStackFollowing(
+ DynamicAnimation.TRANSLATION_Y,
+ new SpringForce()
+ .setDampingRatio(DEFAULT_BOUNCINESS)
+ .setStiffness(DEFAULT_STIFFNESS),
+ velY, y);
+ }
+
+ /**
+ * Spring the stack towards the dismiss target, respecting existing velocity. This also sets
+ * flags so that subsequent touch events will not move the stack until it's demagnetized.
+ */
+ public void magnetToDismiss(float velX, float velY, float destY, Runnable after) {
+ mWithinDismissTarget = true;
+ mFirstBubbleSpringingToTouch = false;
+
+ animationForChildAtIndex(0)
+ .translationX(mLayout.getWidth() / 2f - mIndividualBubbleSize / 2f)
+ .translationY(destY, after)
+ .withPositionStartVelocities(velX, velY)
+ .withStiffness(SpringForce.STIFFNESS_MEDIUM)
+ .withDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ .start();
+ }
+
+ /**
+ * 'Implode' the stack by shrinking the bubbles via chained animations and fading them out.
+ */
+ public void implodeStack(Runnable after) {
+ // Pop and fade the bubbles sequentially.
+ animationForChildAtIndex(0)
+ .scaleX(0.5f)
+ .scaleY(0.5f)
+ .alpha(0f)
+ .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ .withStiffness(SpringForce.STIFFNESS_HIGH)
+ .start(() -> {
+ // Run the callback and reset flags. The child translation animations might
+ // still be running, but that's fine. Once the alpha is at 0f they're no longer
+ // visible anyway.
+ after.run();
+ mWithinDismissTarget = false;
+ });
+ }
+
+ /**
+ * Springs the first bubble to the given final position, with the rest of the stack 'following'.
+ */
+ protected void springFirstBubbleWithStackFollowing(
+ DynamicAnimation.ViewProperty property, SpringForce spring,
+ float vel, float finalPosition) {
+
+ if (mLayout.getChildCount() == 0) {
+ return;
+ }
+
+ Log.d(TAG, String.format("Springing %s to final position %f.",
+ PhysicsAnimationLayout.getReadablePropertyName(property),
+ finalPosition));
+
+ StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
+ SpringAnimation springAnimation =
+ new SpringAnimation(this, firstBubbleProperty)
+ .setSpring(spring)
+ .setStartVelocity(vel);
+
+ cancelStackPositionAnimation(property);
+ mStackPositionAnimations.put(property, springAnimation);
+ springAnimation.animateToFinalPosition(finalPosition);
+ }
+
@Override
Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
return Sets.newHashSet(
@@ -459,7 +591,9 @@ public class StackAnimationController extends
int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
if (property.equals(DynamicAnimation.TRANSLATION_X)
|| property.equals(DynamicAnimation.TRANSLATION_Y)) {
- return index + 1; // Just chain them linearly.
+ return index + 1;
+ } else if (mWithinDismissTarget) {
+ return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used).
} else {
return NONE;
}
@@ -469,9 +603,15 @@ public class StackAnimationController extends
@Override
float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
if (property.equals(DynamicAnimation.TRANSLATION_X)) {
- // Offset to the left if we're on the left, or the right otherwise.
- return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
- ? -mStackOffset : mStackOffset;
+ // If we're in the dismiss target, have the bubbles pile on top of each other with no
+ // offset.
+ if (mWithinDismissTarget) {
+ return 0f;
+ } else {
+ // Offset to the left if we're on the left, or the right otherwise.
+ return mLayout.isFirstChildXLeftOfCenter(mStackPosition.x)
+ ? -mStackOffset : mStackOffset;
+ }
} else {
return 0f;
}
@@ -480,11 +620,8 @@ public class StackAnimationController extends
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
return new SpringForce()
- .setDampingRatio(BubbleController.getBubbleBounciness(
- mLayout.getContext(), DEFAULT_BOUNCINESS))
- .setStiffness(BubbleController.getBubbleStiffness(
- mLayout.getContext(),
- mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS));
+ .setDampingRatio(DEFAULT_BOUNCINESS)
+ .setStiffness(mIsMovingFromFlinging ? FLING_FOLLOW_STIFFNESS : DEFAULT_STIFFNESS);
}
@Override
@@ -594,32 +731,6 @@ public class StackAnimationController extends
}
/**
- * Springs the first bubble to the given final position, with the rest of the stack 'following'.
- */
- private void springFirstBubbleWithStackFollowing(
- DynamicAnimation.ViewProperty property, SpringForce spring,
- float vel, float finalPosition) {
-
- if (mLayout.getChildCount() == 0) {
- return;
- }
-
- Log.d(TAG, String.format("Springing %s to final position %f.",
- PhysicsAnimationLayout.getReadablePropertyName(property),
- finalPosition));
-
- StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
- SpringAnimation springAnimation =
- new SpringAnimation(this, firstBubbleProperty)
- .setSpring(spring)
- .setStartVelocity(vel);
-
- cancelStackPositionAnimation(property);
- mStackPositionAnimations.put(property, springAnimation);
- springAnimation.animateToFinalPosition(finalPosition);
- }
-
- /**
* Cancels any outstanding first bubble property animations that are running. This does not
* affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only
* cancels animations started from {@link #springFirstBubbleWithStackFollowing} and
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/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
index cd8480505c04..567d192073b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/ExpandedAnimationControllerTest.java
@@ -17,6 +17,8 @@
package com.android.systemui.bubbles.animation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.verify;
import android.content.res.Resources;
import android.graphics.Point;
@@ -69,14 +71,14 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
testBubblesInCorrectExpandedPositions();
- Mockito.verify(afterExpand).run();
+ verify(afterExpand).run();
Runnable afterCollapse = Mockito.mock(Runnable.class);
mExpandedController.collapseBackToStack(afterCollapse);
waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
testStackedAtPosition(mExpansionPoint.x, mExpansionPoint.y, -1);
- Mockito.verify(afterExpand).run();
+ verify(afterExpand).run();
}
@Test
@@ -140,6 +142,78 @@ public class ExpandedAnimationControllerTest extends PhysicsAnimationLayoutTestC
testBubblesInCorrectExpandedPositions();
}
+ @Test
+ public void testMagnetToDismiss_dismiss() throws InterruptedException {
+ expand();
+
+ final View draggedOutView = mViews.get(0);
+ final Runnable after = Mockito.mock(Runnable.class);
+
+ mExpandedController.prepareForBubbleDrag(draggedOutView);
+ mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
+
+ // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
+ // called.
+ mExpandedController.magnetBubbleToDismiss(
+ mViews.get(0), 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ verify(after).run();
+ assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
+
+ // Dismiss the now-magneted bubble, verify that the callback was called.
+ final Runnable afterDismiss = Mockito.mock(Runnable.class);
+ mExpandedController.dismissDraggedOutBubble(afterDismiss);
+ waitForPropertyAnimations(DynamicAnimation.ALPHA);
+ verify(after).run();
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ assertEquals(mBubblePadding, mViews.get(1).getTranslationX(), 1f);
+ }
+
+ @Test
+ public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
+ expand();
+
+ final View draggedOutView = mViews.get(0);
+ final Runnable after = Mockito.mock(Runnable.class);
+
+ mExpandedController.prepareForBubbleDrag(draggedOutView);
+ mExpandedController.dragBubbleOut(draggedOutView, 25, 25);
+
+ // Magnet to dismiss, verify the bubble is at the dismiss target and the callback was
+ // called.
+ mExpandedController.magnetBubbleToDismiss(
+ draggedOutView, 100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ verify(after).run();
+ assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
+
+ // Demagnetize the bubble towards (25, 25).
+ mExpandedController.demagnetizeBubbleTo(25 /* x */, 25 /* y */, 100, 100);
+
+ // Start dragging towards (20, 20).
+ mExpandedController.dragBubbleOut(draggedOutView, 20, 20);
+
+ // Since we just demagnetized, the bubble shouldn't be at (20, 20), it should be animating
+ // towards it.
+ assertNotEquals(20, draggedOutView.getTranslationX());
+ assertNotEquals(20, draggedOutView.getTranslationY());
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Waiting for the animations should result in the bubble ending at (20, 20) since the
+ // animation end value was updated.
+ assertEquals(20, draggedOutView.getTranslationX(), 1f);
+ assertEquals(20, draggedOutView.getTranslationY(), 1f);
+
+ // Drag to (30, 30).
+ mExpandedController.dragBubbleOut(draggedOutView, 30, 30);
+
+ // It should go there instantly since the animations finished.
+ assertEquals(30, draggedOutView.getTranslationX(), 1f);
+ assertEquals(30, draggedOutView.getTranslationY(), 1f);
+ }
+
/** Expand the stack and wait for animations to finish. */
private void expand() throws InterruptedException {
mExpandedController.expandFromStack(mExpansionPoint, Mockito.mock(Runnable.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
index 9fce092ef7ce..a398fba008bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayoutTestCase.java
@@ -195,9 +195,11 @@ public class PhysicsAnimationLayoutTestCase extends SysuiTestCase {
@Override
protected void animateValueForChild(DynamicAnimation.ViewProperty property, View view,
- float value, float startVel, long startDelay, Runnable[] afterCallbacks) {
+ float value, float startVel, long startDelay, float stiffness,
+ float dampingRatio, Runnable[] afterCallbacks) {
mMainThreadHandler.post(() -> super.animateValueForChild(
- property, view, value, startVel, startDelay, afterCallbacks));
+ property, view, value, startVel, startDelay, stiffness, dampingRatio,
+ afterCallbacks));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
index 910cee3574dd..b83276bc93da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/animation/StackAnimationControllerTest.java
@@ -17,6 +17,8 @@
package com.android.systemui.bubbles.animation;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.verify;
import android.graphics.PointF;
import android.testing.AndroidTestingRunner;
@@ -33,6 +35,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
import org.mockito.Spy;
@SmallTest
@@ -223,6 +226,59 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
assertEquals(prevStackPos, mStackController.getStackPosition());
}
+ @Test
+ public void testMagnetToDismiss_dismiss() throws InterruptedException {
+ final Runnable after = Mockito.mock(Runnable.class);
+
+ // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
+ // called.
+ mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ verify(after).run();
+ assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
+
+ // Dismiss the stack, verify that the callback was called.
+ final Runnable afterImplode = Mockito.mock(Runnable.class);
+ mStackController.implodeStack(afterImplode);
+ waitForPropertyAnimations(
+ DynamicAnimation.ALPHA, DynamicAnimation.SCALE_X, DynamicAnimation.SCALE_Y);
+ verify(after).run();
+ }
+
+ @Test
+ public void testMagnetToDismiss_demagnetizeThenDrag() throws InterruptedException {
+ final Runnable after = Mockito.mock(Runnable.class);
+
+ // Magnet to dismiss, verify the stack is at the dismiss target and the callback was
+ // called.
+ mStackController.magnetToDismiss(100 /* velX */, 100 /* velY */, 1000 /* destY */, after);
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+ verify(after).run();
+
+ assertEquals(1000, mViews.get(0).getTranslationY(), .1f);
+
+ // Demagnetize towards (25, 25) and then send a touch event.
+ mStackController.demagnetizeFromDismissToPoint(25, 25, 0, 0);
+ waitForLayoutMessageQueue();
+ mStackController.moveStackFromTouch(20, 20);
+
+ // Since the stack is demagnetizing, it shouldn't be at the stack position yet.
+ assertNotEquals(20, mStackController.getStackPosition().x, 1f);
+ assertNotEquals(20, mStackController.getStackPosition().y, 1f);
+
+ waitForPropertyAnimations(DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
+
+ // Once the animation is done it should end at the touch position coordinates.
+ assertEquals(20, mStackController.getStackPosition().x, 1f);
+ assertEquals(20, mStackController.getStackPosition().y, 1f);
+
+ mStackController.moveStackFromTouch(30, 30);
+
+ // Touches after the animation are done should change the stack position instantly.
+ assertEquals(30, mStackController.getStackPosition().x, 1f);
+ assertEquals(30, mStackController.getStackPosition().y, 1f);
+ }
+
/**
* Checks every child view to make sure it's stacked at the given coordinates, off to the left
* or right side depending on offset multiplier.
@@ -249,5 +305,13 @@ public class StackAnimationControllerTest extends PhysicsAnimationLayoutTestCase
super.flingThenSpringFirstBubbleWithStackFollowing(
property, vel, friction, spring, finalPosition));
}
+
+ @Override
+ protected void springFirstBubbleWithStackFollowing(DynamicAnimation.ViewProperty property,
+ SpringForce spring, float vel, float finalPosition) {
+ mMainThreadHandler.post(() ->
+ super.springFirstBubbleWithStackFollowing(
+ property, spring, vel, finalPosition));
+ }
}
}
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index fa62f64d59ca..e30e166a46b5 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -4191,6 +4191,8 @@ message MetricsEvent {
// Tag FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
// Tag FIELD_AUTOFILL_NUMBER_REQUESTS: number of requests made to the service (each request
// is logged by a separate AUTOFILL_REQUEST metric)
+ // NOTE: starting on OS Q, it also added the following fields:
+ // TAg FIELD_AUTOFILL_AUGMENTED_ONLY: if the session was used just for augmented autofill
AUTOFILL_SESSION_FINISHED = 919;
// meta-event: a reader has checkpointed the log here.
@@ -7243,8 +7245,46 @@ message MetricsEvent {
// OS: Q
ACTION_DIRECT_SHARE_TARGETS_LOADED_CHOOSER_SERVICE = 1719;
- // ---- Skipping ahead to avoid conflicts between master and release branches.
+ // Field indicating that an autofill session was created just for augmented autofill purposes.
+ // OS: Q
+ // Value: 1 for true, absent when false
+ FIELD_AUTOFILL_AUGMENTED_ONLY = 1720;
+
+ // The augmented autofill service set its whitelisted packages and activities.
+ // OS: Q
+ // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+ // request
+ // Tag FIELD_AUTOFILL_NUMBER_PACKAGES: Number of whitelisted packages.
+ // Tag FIELD_AUTOFILL_NUMBER_ACTIVITIES: Number of whitelisted activities.
+ AUTOFILL_AUGMENTED_WHITELIST_REQUEST = 1721;
+
+ // Generic field used to indicate the number of packages in an Autofill metric (typically a
+ // whitelist request).
+ // OS: Q
+ FIELD_AUTOFILL_NUMBER_PACKAGES = 1722;
+ // Generic field used to indicate the number of activities in an Autofill metric (typically a
+ // whitelist request).
+ // OS: Q
+ FIELD_AUTOFILL_NUMBER_ACTIVITIES = 1723;
+
+ // Reports the result of a request made to the augmented autofill service
+ // OS: Q
+ // Type TYPE_UNKNOWN: if the type of response could not be determined
+ // Type TYPE_SUCCESS: service called onSucess() passing null
+ // Type TYPE_OPEN: service shown the UI
+ // Type TYPE_CLOSE: service hid the UI
+ // Type TYPE_ERROR: service timed out responding
+
+ // Tag FIELD_CLASS_NAME: Class name of the activity that is autofilled.
+ // Tag FIELD_AUTOFILL_SERVICE: Package of the augmented autofill service that processed the
+ // request
+ // Tag FIELD_AUTOFILL_SESSION_ID: id of the autofill session associated with this metric
+ // Tag FIELD_AUTOFILL_DURATION: how long it took (in ms) to the service to respond, or -1 if the
+ // type of response could not be determined
+ AUTOFILL_AUGMENTED_RESPONSE = 1724;
+
+ // ---- Skipping ahead to avoid conflicts between master and release branches.
// OPEN: Settings > System > Gestures > Global Actions Panel
// CATEGORY: SETTINGS
// OS: Q
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 386dec472019..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;
@@ -1177,8 +1196,8 @@ final class AutofillManagerServiceImpl
* @return whether caller UID is the augmented autofill service for the user
*/
@GuardedBy("mLock")
- boolean setAugmentedAutofillWhitelistLocked(List<String> packages,
- List<ComponentName> activities, int callingUid) {
+ boolean setAugmentedAutofillWhitelistLocked(@Nullable List<String> packages,
+ @Nullable List<ComponentName> activities, int callingUid) {
if (!isCalledByAugmentedAutofillServiceLocked("setAugmentedAutofillWhitelistLocked",
callingUid)) {
@@ -1189,8 +1208,25 @@ final class AutofillManagerServiceImpl
+ activities + ")");
}
whitelistForAugmentedAutofillPackages(packages, activities);
+ final String serviceName;
+ if (mRemoteAugmentedAutofillServiceInfo != null) {
+ serviceName = mRemoteAugmentedAutofillServiceInfo.getComponentName()
+ .flattenToShortString();
+ } else {
+ Slog.e(TAG, "setAugmentedAutofillWhitelistLocked(): no service");
+ serviceName = "N/A";
+ }
+
+ final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_AUGMENTED_WHITELIST_REQUEST)
+ .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, serviceName);
+ if (packages != null) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_PACKAGES, packages.size());
+ }
+ if (activities != null) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_ACTIVITIES, activities.size());
+ }
+ mMetricsLogger.write(log);
- // TODO(b/122858578): log metrics
return true;
}
@@ -1233,7 +1269,6 @@ final class AutofillManagerServiceImpl
}
/**
- *
* @throws IllegalArgumentException if packages or components are empty.
*/
private void whitelistForAugmentedAutofillPackages(@Nullable List<String> packages,
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 609904b32230..d2b71e591b22 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -16,6 +16,8 @@
package com.android.server.autofill;
+import static android.service.autofill.augmented.Helper.logResponse;
+
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
@@ -43,6 +45,7 @@ import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.infra.AbstractSinglePendingRequestRemoteService;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.IResultReceiver;
final class RemoteAugmentedAutofillService
@@ -173,6 +176,7 @@ final class RemoteAugmentedAutofillService
private final @Nullable AutofillValue mFocusedValue;
private final @NonNull IAutoFillManagerClient mClient;
private final @NonNull ComponentName mActivityComponent;
+ private final int mSessionId;
private final int mTaskId;
private final long mRequestTime = SystemClock.elapsedRealtime();
private final @NonNull IFillCallback mCallback;
@@ -184,6 +188,7 @@ final class RemoteAugmentedAutofillService
@Nullable AutofillValue focusedValue) {
super(service, sessionId);
mClient = client;
+ mSessionId = sessionId;
mTaskId = taskId;
mActivityComponent = activityComponent;
mFocusedId = focusedId;
@@ -283,6 +288,8 @@ final class RemoteAugmentedAutofillService
remoteService.dispatchOnFillTimeout(cancellation);
}
finish();
+ logResponse(MetricsEvent.TYPE_ERROR, remoteService.getComponentName().getPackageName(),
+ mActivityComponent, mSessionId, remoteService.mRequestTimeoutMs);
}
@Override
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1a0353cde8ce..66b5437f0a7d 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -570,7 +570,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
if (mForAugmentedAutofillOnly) {
- // TODO(b/122858578): log metrics
if (sVerbose) {
Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
+ "(mForAugmentedAutofillOnly=" + mForAugmentedAutofillOnly
@@ -2408,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
@@ -2539,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) {
@@ -2694,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);
+ }
}
}
@@ -3253,6 +3258,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS,
totalAugmentedRequests);
}
+ if (mForAugmentedAutofillOnly) {
+ log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1);
+ }
mMetricsLogger.write(log);
return mRemoteFillService;
@@ -3268,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 28bc34859e6c..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;
@@ -342,7 +340,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
Integer newDefaultSubIdObj = new Integer(intent.getIntExtra(
PhoneConstants.SUBSCRIPTION_KEY,
SubscriptionManager.getDefaultSubscriptionId()));
- int newDefaultPhoneId = intent.getIntExtra(PhoneConstants.SLOT_KEY,
+ int newDefaultPhoneId = intent.getIntExtra(PhoneConstants.PHONE_KEY,
SubscriptionManager.getPhoneId(mDefaultSubId));
if (DBG) {
log("onReceive:current mDefaultSubId=" + mDefaultSubId
@@ -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,26 +1982,23 @@ 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);
+ pw.println("mDefaultPhoneId=" + mDefaultPhoneId);
+ pw.println("mDefaultSubId=" + mDefaultSubId);
pw.decreaseIndent();
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/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index 9fce644d6c4b..171cc5abdb97 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -778,6 +778,7 @@ public abstract class BrightnessMappingStrategy {
pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment);
pw.println(" mUserLux=" + mUserLux);
pw.println(" mUserBrightness=" + mUserBrightness);
+ pw.println(" mDefaultConfig=" + mDefaultConfig);
}
private void computeSpline() {
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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e6313d9dcbe4..44f76774fc8a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -363,8 +363,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
System.currentTimeMillis() - session.getUpdatedMillis();
final boolean valid;
if (session.isStaged()) {
- if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS
- && session.isStagedAndInTerminalState()) {
+ if (timeSinceUpdate >= MAX_TIME_SINCE_UPDATE_MILLIS) {
valid = false;
} else {
valid = true;
@@ -531,16 +530,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
+ "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
}
- // Only system components can circumvent restricted whitelisting when installing.
- if ((params.installFlags
- & PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS) != 0
- && mContext.checkCallingOrSelfPermission(Manifest.permission
- .WHITELIST_RESTRICTED_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
- throw new SecurityException("You need the "
- + "android.permission.WHITELIST_RESTRICTED_PERMISSIONS permission to"
- + " use the PackageManager.INSTALL_WHITELIST_RESTRICTED_PERMISSIONS flag");
- }
-
// Defensively resize giant app icons
if (params.appIcon != null) {
final ActivityManager am = (ActivityManager) mContext.getSystemService(
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5f6e7399f3a7..6f9a918d105c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1150,7 +1150,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
*/
void sealAndValidateIfNecessary() {
synchronized (mLock) {
- if (!mShouldBeSealed) {
+ if (!mShouldBeSealed || isStagedAndInTerminalState()) {
return;
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e935771aea63..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) {
@@ -11726,7 +11725,12 @@ public class PackageManagerService extends IPackageManager.Stub
"Code and resource paths haven't been set correctly");
}
- if (mApexManager.isApexPackage(pkg.packageName)) {
+ // Check that there is an APEX package with the same name only during install/first boot
+ // after OTA.
+ final boolean isUserInstall = (scanFlags & SCAN_BOOTING) == 0;
+ final boolean isFirstBootOrUpgrade = (scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0;
+ if ((isUserInstall || isFirstBootOrUpgrade)
+ && mApexManager.isApexPackage(pkg.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
pkg.packageName + " is an APEX package and can't be installed as an APK.");
}
@@ -21526,6 +21530,7 @@ public class PackageManagerService extends IPackageManager.Stub
storage.registerListener(mStorageListener);
mInstallerService.systemReady();
+ mApexManager.systemReady();
mPackageDexOptimizer.systemReady();
getStorageManagerInternal().addExternalStoragePolicy(
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 33dd48a1ac6a..fbf074e3ba15 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2351,9 +2351,10 @@ class PackageManagerShellCommand extends ShellCommand {
break;
case "-g":
sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
- case "-w":
- sessionParams.installFlags |=
- PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
+ break;
+ case "--restrict-permissions":
+ sessionParams.installFlags &=
+ ~PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS;
break;
case "--dont-kill":
sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
@@ -3004,10 +3005,10 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" -d: allow version code downgrade (debuggable packages only)");
pw.println(" -p: partial application install (new split on top of existing pkg)");
pw.println(" -g: grant all runtime permissions");
- pw.println(" -w: whitelist all restricted permissions");
pw.println(" -S: size in bytes of package, required for stdin");
pw.println(" --user: install under the given user.");
pw.println(" --dont-kill: installing a new feature split, don't kill running app");
+ pw.println(" --restrict-permissions: don't whitelist restricted permissions at install");
pw.println(" --originating-uri: set URI where app was downloaded from");
pw.println(" --referrer: set URI that instigated the install of the app");
pw.println(" --pkg: specify expected package name of app being installed");
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e0256460042a..4fdf1bc58e05 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1134,7 +1134,7 @@ public final class DefaultPermissionGrantPolicy {
private void grantRuntimePermissions(PackageInfo pkg, Set<String> permissionsWithoutSplits,
boolean systemFixed, boolean ignoreSystemPackage,
boolean whitelistRestrictedPermissions, int userId) {
- UserHandle user = UserHandle.of(userId);
+ UserHandle user = UserHandle.of(userId);
if (pkg == null) {
return;
}
@@ -1203,7 +1203,7 @@ public final class DefaultPermissionGrantPolicy {
if (ArrayUtils.isEmpty(disabledPkg.requestedPermissions)) {
return;
}
- if (!requestedPermissions.equals(disabledPkg.requestedPermissions)) {
+ if (!Arrays.equals(requestedPermissions, disabledPkg.requestedPermissions)) {
grantablePermissions = new ArraySet<>(Arrays.asList(requestedPermissions));
requestedPermissions = disabledPkg.requestedPermissions;
}
@@ -1213,7 +1213,7 @@ public final class DefaultPermissionGrantPolicy {
final int numRequestedPermissions = requestedPermissions.length;
// Sort requested permissions so that all permissions that are a foreground permission (i.e.
- // permisions that have background permission) are before their background permissions.
+ // permissions that have a background permission) are before their background permissions.
final String[] sortedRequestedPermissions = new String[numRequestedPermissions];
int numForeground = 0;
int numOther = 0;
@@ -1258,9 +1258,16 @@ public final class DefaultPermissionGrantPolicy {
continue;
}
- int uid = UserHandle.getUid(userId,
- UserHandle.getAppId(pkg.applicationInfo.uid));
- String op = AppOpsManager.permissionToOp(permission);
+ // Preserve whitelisting flags.
+ newFlags |= (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT);
+
+ // If we are whitelisting the permission, update the exempt flag before grant.
+ if (whitelistRestrictedPermissions && isPermissionRestricted(permission)) {
+ mContext.getPackageManager().updatePermissionFlags(permission,
+ pkg.packageName,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT,
+ PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, user);
+ }
if (pm.checkPermission(permission, pkg.packageName)
!= PackageManager.PERMISSION_GRANTED) {
@@ -1268,13 +1275,12 @@ public final class DefaultPermissionGrantPolicy {
.grantRuntimePermission(pkg.packageName, permission, user);
}
- if (whitelistRestrictedPermissions && isPermissionRestricted(permission)) {
- newFlags |= PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
- }
-
mContext.getPackageManager().updatePermissionFlags(permission, pkg.packageName,
newFlags, newFlags, user);
+ int uid = UserHandle.getUid(userId,
+ UserHandle.getAppId(pkg.applicationInfo.uid));
+
List<String> fgPerms = mPermissionManager.getBackgroundPermissions()
.get(permission);
if (fgPerms != null) {
@@ -1285,6 +1291,7 @@ public final class DefaultPermissionGrantPolicy {
if (pm.checkPermission(fgPerm, pkg.packageName)
== PackageManager.PERMISSION_GRANTED) {
// Upgrade the app-op state of the fg permission to allow bg access
+ // TODO: Dont' call app ops from package manager code.
mContext.getSystemService(AppOpsManager.class).setUidMode(
AppOpsManager.permissionToOp(fgPerm), uid,
AppOpsManager.MODE_ALLOWED);
@@ -1295,8 +1302,10 @@ public final class DefaultPermissionGrantPolicy {
}
String bgPerm = getBackgroundPermission(permission);
+ String op = AppOpsManager.permissionToOp(permission);
if (bgPerm == null) {
if (op != null) {
+ // TODO: Dont' call app ops from package manager code.
mContext.getSystemService(AppOpsManager.class).setUidMode(op, uid,
AppOpsManager.MODE_ALLOWED);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index d45a8ef4e0ae..37c1aaa45b8b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -32,7 +32,6 @@ import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_INSTAL
import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM;
import static android.content.pm.PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE;
import static android.content.pm.PackageManager.MASK_PERMISSION_FLAGS_ALL;
-import static android.content.pm.PackageManager.RESTRICTED_PERMISSIONS_ENABLED;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import static com.android.server.pm.PackageManagerService.DEBUG_INSTALL;
@@ -1058,8 +1057,8 @@ public class PermissionManagerService {
boolean wasChanged = false;
- boolean restrictionExempt = !RESTRICTED_PERMISSIONS_ENABLED
- || (origPermissions.getPermissionFlags(bp.name, userId)
+ boolean restrictionExempt =
+ (origPermissions.getPermissionFlags(bp.name, userId)
& FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
boolean restrictionApplied = (origPermissions.getPermissionFlags(
bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
@@ -1177,8 +1176,8 @@ public class PermissionManagerService {
for (int userId : currentUserIds) {
boolean wasChanged = false;
- boolean restrictionExempt = !RESTRICTED_PERMISSIONS_ENABLED
- || (origPermissions.getPermissionFlags(bp.name, userId)
+ boolean restrictionExempt =
+ (origPermissions.getPermissionFlags(bp.name, userId)
& FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) != 0;
boolean restrictionApplied = (origPermissions.getPermissionFlags(
bp.name, userId) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
@@ -2054,7 +2053,7 @@ public class PermissionManagerService {
return;
}
- if (RESTRICTED_PERMISSIONS_ENABLED && bp.isHardOrSoftRestricted()
+ if (bp.isHardOrSoftRestricted()
&& (flags & PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT) == 0) {
Log.e(TAG, "Cannot grant restricted non-exempt permission "
+ permName + " for package " + packageName);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index a280d83fac27..250f3313e3c4 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -326,8 +326,8 @@ public final class PermissionPolicyService extends SystemService {
return;
}
- final boolean applyRestriction = PackageManager.RESTRICTED_PERMISSIONS_ENABLED
- && (mPackageManager.getPermissionFlags(permission, pkg.packageName,
+ final boolean applyRestriction =
+ (mPackageManager.getPermissionFlags(permission, pkg.packageName,
mContext.getUser()) & FLAG_PERMISSION_APPLY_RESTRICTION) != 0;
if (permissionInfo.isHardRestricted()) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 7a26f7cbf640..d847af0e9b56 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -78,6 +78,8 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_W
import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.CHANGING_APPS;
+import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
import static com.android.server.wm.DisplayContentProto.DISPLAY_INFO;
import static com.android.server.wm.DisplayContentProto.DOCKED_STACK_DIVIDER_CONTROLLER;
@@ -85,14 +87,12 @@ import static com.android.server.wm.DisplayContentProto.DPI;
import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
import static com.android.server.wm.DisplayContentProto.ID;
import static com.android.server.wm.DisplayContentProto.IME_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
import static com.android.server.wm.DisplayContentProto.PINNED_STACK_CONTROLLER;
import static com.android.server.wm.DisplayContentProto.ROTATION;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.STACKS;
import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER;
-import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
-import static com.android.server.wm.DisplayContentProto.CHANGING_APPS;
-import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -2945,9 +2945,16 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
forAllWindows(mScheduleToastTimeout, false /* traverseTopToBottom */);
}
- WindowState findFocusedWindowIfNeeded() {
- return (mWmService.mPerDisplayFocusEnabled
- || mWmService.mRoot.mTopFocusedAppByProcess.isEmpty()) ? findFocusedWindow() : null;
+ /**
+ * Looking for the focused window on this display if the top focused display hasn't been
+ * found yet (topFocusedDisplayId is INVALID_DISPLAY) or per-display focused was allowed.
+ *
+ * @param topFocusedDisplayId Id of the top focused display.
+ * @return The focused window or null if there isn't any or no need to seek.
+ */
+ WindowState findFocusedWindowIfNeeded(int topFocusedDisplayId) {
+ return (mWmService.mPerDisplayFocusEnabled || topFocusedDisplayId == INVALID_DISPLAY)
+ ? findFocusedWindow() : null;
}
WindowState findFocusedWindow() {
@@ -2971,10 +2978,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
* {@link WindowManagerService#UPDATE_FOCUS_WILL_PLACE_SURFACES},
* {@link WindowManagerService#UPDATE_FOCUS_REMOVING_FOCUS}
* @param updateInputWindows Whether to sync the window information to the input module.
+ * @param topFocusedDisplayId Display id of current top focused display.
* @return {@code true} if the focused window has changed.
*/
- boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
- WindowState newFocus = findFocusedWindowIfNeeded();
+ boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
+ int topFocusedDisplayId) {
+ WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
if (mCurrentFocus == newFocus) {
return false;
}
@@ -2994,7 +3003,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
if (imWindowChanged) {
mWmService.mWindowsChanged = true;
setLayoutNeeded();
- newFocus = findFocusedWindowIfNeeded();
+ newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
}
if (mCurrentFocus != newFocus) {
mWmService.mH.obtainMessage(REPORT_FOCUS_CHANGE, this).sendToTarget();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9f4232400221..8a5f52f8c453 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -170,7 +170,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
int topFocusedDisplayId = INVALID_DISPLAY;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent dc = mChildren.get(i);
- changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows);
+ changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
final WindowState newFocus = dc.mCurrentFocus;
if (newFocus != null) {
final int pidOfNewFocus = newFocus.mSession.mPid;
@@ -180,6 +180,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (topFocusedDisplayId == INVALID_DISPLAY) {
topFocusedDisplayId = dc.getDisplayId();
}
+ } else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
+ // The top-most display that has a focused app should still be the top focused
+ // display even when the app window is not ready yet (process not attached or
+ // window not added yet).
+ topFocusedDisplayId = dc.getDisplayId();
}
}
if (topFocusedDisplayId == INVALID_DISPLAY) {
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/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
new file mode 100644
index 000000000000..f8d7e845da2c
--- /dev/null
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -0,0 +1,273 @@
+/*
+ * 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.
+ */
+
+package android.net.ip;
+
+import android.annotation.NonNull;
+import android.net.NattKeepalivePacketData;
+import android.net.ProxyInfo;
+import android.net.TcpKeepalivePacketData;
+import android.net.shared.ProvisioningConfiguration;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A convenience wrapper for IpClient.
+ *
+ * Wraps IIpClient calls, making them a bit more friendly to use. Currently handles:
+ * - Clearing calling identity
+ * - Ignoring RemoteExceptions
+ * - Converting to stable parcelables
+ *
+ * By design, all methods on IIpClient are asynchronous oneway IPCs and are thus void. All the
+ * wrapper methods in this class return a boolean that callers can use to determine whether
+ * RemoteException was thrown.
+ */
+public class IpClientManager {
+ @NonNull private final IIpClient mIpClient;
+ @NonNull private final String mTag;
+
+ public IpClientManager(@NonNull IIpClient ipClient, @NonNull String tag) {
+ mIpClient = ipClient;
+ mTag = tag;
+ }
+
+ public IpClientManager(@NonNull IIpClient ipClient) {
+ this(ipClient, IpClientManager.class.getSimpleName());
+ }
+
+ private void log(String s, Throwable e) {
+ Log.e(mTag, s, e);
+ }
+
+ /**
+ * For clients using {@link ProvisioningConfiguration.Builder#withPreDhcpAction()}, must be
+ * called after {@link IIpClientCallbacks#onPreDhcpAction} to indicate that DHCP is clear to
+ * proceed.
+ */
+ public boolean completedPreDhcpAction() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.completedPreDhcpAction();
+ return true;
+ } catch (RemoteException e) {
+ log("Error completing PreDhcpAction", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Confirm the provisioning configuration.
+ */
+ public boolean confirmConfiguration() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.confirmConfiguration();
+ return true;
+ } catch (RemoteException e) {
+ log("Error confirming IpClient configuration", e);
+ return false;
+ }
+ }
+
+ /**
+ * Indicate that packet filter read is complete.
+ */
+ public boolean readPacketFilterComplete(byte[] data) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.readPacketFilterComplete(data);
+ return true;
+ } catch (RemoteException e) {
+ log("Error notifying IpClient of packet filter read", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Shut down this IpClient instance altogether.
+ */
+ public boolean shutdown() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.shutdown();
+ return true;
+ } catch (RemoteException e) {
+ log("Error shutting down IpClient", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Start provisioning with the provided parameters.
+ */
+ public boolean startProvisioning(ProvisioningConfiguration prov) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.startProvisioning(prov.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error starting IpClient provisioning", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Stop this IpClient.
+ *
+ * <p>This does not shut down the StateMachine itself, which is handled by {@link #shutdown()}.
+ */
+ public boolean stop() {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.stop();
+ return true;
+ } catch (RemoteException e) {
+ log("Error stopping IpClient", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the TCP buffer sizes to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public boolean setTcpBufferSizes(String tcpBufferSizes) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setTcpBufferSizes(tcpBufferSizes);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting IpClient TCP buffer sizes", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the HTTP Proxy configuration to use.
+ *
+ * This may be called, repeatedly, at any time before or after a call to
+ * #startProvisioning(). The setting is cleared upon calling #stop().
+ */
+ public boolean setHttpProxy(ProxyInfo proxyInfo) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setHttpProxy(proxyInfo);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting IpClient proxy", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
+ * if not, Callback.setFallbackMulticastFilter() is called.
+ */
+ public boolean setMulticastFilter(boolean enabled) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setMulticastFilter(enabled);
+ return true;
+ } catch (RemoteException e) {
+ log("Error setting multicast filter", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Add a TCP keepalive packet filter before setting up keepalive offload.
+ */
+ public boolean addKeepalivePacketFilter(int slot, TcpKeepalivePacketData pkt) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.addKeepalivePacketFilter(slot, pkt.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error adding Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Add a NAT-T keepalive packet filter before setting up keepalive offload.
+ */
+ public boolean addKeepalivePacketFilter(int slot, NattKeepalivePacketData pkt) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.addNattKeepalivePacketFilter(slot, pkt.toStableParcelable());
+ return true;
+ } catch (RemoteException e) {
+ log("Error adding Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Remove a keepalive packet filter after stopping keepalive offload.
+ */
+ public boolean removeKeepalivePacketFilter(int slot) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.removeKeepalivePacketFilter(slot);
+ return true;
+ } catch (RemoteException e) {
+ log("Error removing Keepalive Packet Filter ", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Set the L2 key and group hint for storing info into the memory store.
+ */
+ public boolean setL2KeyAndGroupHint(String l2Key, String groupHint) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ mIpClient.setL2KeyAndGroupHint(l2Key, groupHint);
+ return true;
+ } catch (RemoteException e) {
+ log("Failed setL2KeyAndGroupHint", e);
+ return false;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index e60e54c9e44f..04f897ed1bdb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -380,6 +380,14 @@ public class DisplayContentTests extends WindowTestsBase {
assertTrue(window1.isFocused());
assertEquals(perDisplayFocusEnabled && targetSdk >= Q, window2.isFocused());
assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
+
+ // Make sure top focused display not changed if there is a focused app.
+ window1.mAppToken.hiddenRequested = true;
+ window1.getDisplayContent().setFocusedApp(window1.mAppToken);
+ updateFocusedWindow();
+ assertTrue(!window1.isFocused());
+ assertEquals(window1.getDisplayId(),
+ mWm.mRoot.getTopFocusedDisplayContent().getDisplayId());
}
/**
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) {
+ }
+ }
}