Merge "Fix GlobalActionsImeTest flakes" into rvc-dev
diff --git a/Android.bp b/Android.bp
index ee381a4..01a43b6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -930,6 +930,8 @@
srcs: [
"core/java/android/os/incremental/IIncrementalService.aidl",
"core/java/android/os/incremental/IncrementalNewFileParams.aidl",
+ "core/java/android/os/incremental/IStorageHealthListener.aidl",
+ "core/java/android/os/incremental/StorageHealthCheckParams.aidl",
],
path: "core/java",
}
diff --git a/ApiDocs.bp b/ApiDocs.bp
index 90df19a..a81342a 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -66,7 +66,7 @@
":opt-telephony-srcs",
":opt-net-voip-srcs",
":art-module-public-api-stubs-source",
- ":conscrypt.module.public.api.stubs.source",
+ ":conscrypt.module.public.api{.public.stubs.source}",
":android_icu4j_public_api_files",
"test-mock/src/**/*.java",
"test-runner/src/**/*.java",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 6e6efe5..c0197c4 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -69,7 +69,7 @@
name: "metalava-full-api-stubs-default",
defaults: ["metalava-base-api-stubs-default"],
srcs: [
- ":conscrypt.module.public.api.stubs.source",
+ ":conscrypt.module.public.api{.public.stubs.source}",
":framework-updatable-sources",
],
sdk_version: "core_platform",
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index b4a7cd4..9a711d3 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -18,7 +18,6 @@
import static android.provider.DeviceConfig.NAMESPACE_BLOBSTORE;
import static android.text.format.Formatter.FLAG_IEC_UNITS;
import static android.text.format.Formatter.formatFileSize;
-import static android.util.TimeUtils.formatDuration;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,18 +57,24 @@
* Job Id for idle maintenance job ({@link BlobStoreIdleJobService}).
*/
public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L
- /**
- * Max time period (in millis) between each idle maintenance job run.
- */
- public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1);
-
- /**
- * Timeout in millis after which sessions with no updates will be deleted.
- */
- public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7);
public static class DeviceConfigProperties {
/**
+ * Denotes the max time period (in millis) between each idle maintenance job run.
+ */
+ public static final String KEY_IDLE_JOB_PERIOD_MS = "idle_job_period_ms";
+ public static final long DEFAULT_IDLE_JOB_PERIOD_MS = TimeUnit.DAYS.toMillis(1);
+ public static long IDLE_JOB_PERIOD_MS = DEFAULT_IDLE_JOB_PERIOD_MS;
+
+ /**
+ * Denotes the timeout in millis after which sessions with no updates will be deleted.
+ */
+ public static final String KEY_SESSION_EXPIRY_TIMEOUT_MS =
+ "session_expiry_timeout_ms";
+ public static final long DEFAULT_SESSION_EXPIRY_TIMEOUT_MS = TimeUnit.DAYS.toMillis(7);
+ public static long SESSION_EXPIRY_TIMEOUT_MS = DEFAULT_SESSION_EXPIRY_TIMEOUT_MS;
+
+ /**
* Denotes how low the limit for the amount of data, that an app will be allowed to acquire
* a lease on, can be.
*/
@@ -119,6 +124,13 @@
}
properties.getKeyset().forEach(key -> {
switch (key) {
+ case KEY_IDLE_JOB_PERIOD_MS:
+ IDLE_JOB_PERIOD_MS = properties.getLong(key, DEFAULT_IDLE_JOB_PERIOD_MS);
+ break;
+ case KEY_SESSION_EXPIRY_TIMEOUT_MS:
+ SESSION_EXPIRY_TIMEOUT_MS = properties.getLong(key,
+ DEFAULT_SESSION_EXPIRY_TIMEOUT_MS);
+ break;
case KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR:
TOTAL_BYTES_PER_APP_LIMIT_FLOOR = properties.getLong(key,
DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR);
@@ -143,6 +155,12 @@
static void dump(IndentingPrintWriter fout, Context context) {
final String dumpFormat = "%s: [cur: %s, def: %s]";
+ fout.println(String.format(dumpFormat, KEY_IDLE_JOB_PERIOD_MS,
+ TimeUtils.formatDuration(IDLE_JOB_PERIOD_MS),
+ TimeUtils.formatDuration(DEFAULT_IDLE_JOB_PERIOD_MS)));
+ fout.println(String.format(dumpFormat, KEY_SESSION_EXPIRY_TIMEOUT_MS,
+ TimeUtils.formatDuration(SESSION_EXPIRY_TIMEOUT_MS),
+ TimeUtils.formatDuration(DEFAULT_SESSION_EXPIRY_TIMEOUT_MS)));
fout.println(String.format(dumpFormat, KEY_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
formatFileSize(context, TOTAL_BYTES_PER_APP_LIMIT_FLOOR, FLAG_IEC_UNITS),
formatFileSize(context, DEFAULT_TOTAL_BYTES_PER_APP_LIMIT_FLOOR,
@@ -167,6 +185,22 @@
}
/**
+ * Returns the max time period (in millis) between each idle maintenance job run.
+ */
+ public static long getIdleJobPeriodMs() {
+ return DeviceConfigProperties.IDLE_JOB_PERIOD_MS;
+ }
+
+ /**
+ * Returns whether a session is expired or not. A session is considered expired if the session
+ * has not been modified in a while (i.e. SESSION_EXPIRY_TIMEOUT_MS).
+ */
+ public static boolean hasSessionExpired(long sessionLastModifiedMs) {
+ return sessionLastModifiedMs
+ < System.currentTimeMillis() - DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS;
+ }
+
+ /**
* Returns the maximum amount of data that an app can acquire a lease on.
*/
public static long getAppDataBytesLimit() {
@@ -277,9 +311,6 @@
fout.println("XML current version: " + XML_VERSION_CURRENT);
fout.println("Idle job ID: " + IDLE_JOB_ID);
- fout.println("Idle job period: " + formatDuration(IDLE_JOB_PERIOD_MILLIS));
-
- fout.println("Session expiry timeout: " + formatDuration(SESSION_EXPIRY_TIMEOUT_MILLIS));
fout.println("Total bytes per app limit: " + formatFileSize(context,
getAppDataBytesLimit(), FLAG_IEC_UNITS));
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
index 460e776..4b0f719 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java
@@ -16,7 +16,6 @@
package com.android.server.blob;
import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID;
-import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_PERIOD_MILLIS;
import static com.android.server.blob.BlobStoreConfig.LOGV;
import static com.android.server.blob.BlobStoreConfig.TAG;
@@ -60,7 +59,7 @@
new ComponentName(context, BlobStoreIdleJobService.class))
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
- .setPeriodic(IDLE_JOB_PERIOD_MILLIS)
+ .setPeriodic(BlobStoreConfig.getIdleJobPeriodMs())
.build();
jobScheduler.schedule(job);
if (LOGV) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 9376198..7a27b2c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -29,10 +29,10 @@
import static android.os.UserHandle.USER_NULL;
import static com.android.server.blob.BlobStoreConfig.LOGV;
-import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
import static com.android.server.blob.BlobStoreConfig.TAG;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
+import static com.android.server.blob.BlobStoreConfig.hasSessionExpired;
import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
@@ -986,9 +986,9 @@
userSessions.removeIf((sessionId, blobStoreSession) -> {
boolean shouldRemove = false;
+ // TODO: handle the case where no content has been written to session yet.
// Cleanup sessions which haven't been modified in a while.
- if (blobStoreSession.getSessionFile().lastModified()
- < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) {
+ if (hasSessionExpired(blobStoreSession.getSessionFile().lastModified())) {
shouldRemove = true;
}
diff --git a/apex/sdkextensions/Android.bp b/apex/sdkextensions/Android.bp
deleted file mode 100644
index fdb078e..0000000
--- a/apex/sdkextensions/Android.bp
+++ /dev/null
@@ -1,84 +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.
-
-package {
- default_visibility: [":__subpackages__"],
-}
-
-apex {
- name: "com.android.sdkext",
- defaults: [ "com.android.sdkext-defaults" ],
- binaries: [ "derive_sdk" ],
- prebuilts: [ "cur_sdkinfo" ],
- manifest: "manifest.json",
- min_sdk_version: "current",
-}
-
-apex_defaults {
- name: "com.android.sdkext-defaults",
- updatable: true,
- min_sdk_version: "R",
- java_libs: [ "framework-sdkextensions" ],
- prebuilts: [
- "derive_sdk.rc",
- ],
- key: "com.android.sdkext.key",
- certificate: ":com.android.sdkext.certificate",
-}
-
-sdk {
- name: "sdkextensions-sdk",
- java_sdk_libs: [ "framework-sdkextensions" ],
-}
-
-apex_key {
- name: "com.android.sdkext.key",
- public_key: "com.android.sdkext.avbpubkey",
- private_key: "com.android.sdkext.pem",
-}
-
-android_app_certificate {
- name: "com.android.sdkext.certificate",
- certificate: "com.android.sdkext",
-}
-
-python_binary_host {
- name: "gen_sdkinfo",
- srcs: [
- "sdk.proto",
- "gen_sdkinfo.py",
- ],
- proto: {
- canonical_path_from_root: false,
- },
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
-}
-
-gensrcs {
- name: "cur_sdkinfo_src",
- srcs: [""],
- tools: [ "gen_sdkinfo" ],
- cmd: "$(location) -v 0 -o $(out)",
-}
-
-prebuilt_etc {
- name: "cur_sdkinfo",
- src: ":cur_sdkinfo_src",
- filename: "sdkinfo.binarypb",
- installable: false,
-}
diff --git a/apex/sdkextensions/OWNERS b/apex/sdkextensions/OWNERS
deleted file mode 100644
index a6e5522..0000000
--- a/apex/sdkextensions/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-dariofreni@google.com
-hansson@google.com
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
deleted file mode 100644
index 3dc1b9f..0000000
--- a/apex/sdkextensions/TEST_MAPPING
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsSdkExtensionsTestCases"
- },
- {
- "name": "sdkextensions_e2e_tests"
- }
- ]
-}
diff --git a/apex/sdkextensions/com.android.sdkext.avbpubkey b/apex/sdkextensions/com.android.sdkext.avbpubkey
deleted file mode 100644
index 8f47741..0000000
--- a/apex/sdkextensions/com.android.sdkext.avbpubkey
+++ /dev/null
Binary files differ
diff --git a/apex/sdkextensions/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem
deleted file mode 100644
index 8164601..0000000
--- a/apex/sdkextensions/com.android.sdkext.pem
+++ /dev/null
@@ -1,51 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIIJKQIBAAKCAgEAr72pTSavrziDP54AtQZlRclDxJf9HXRZwFRbYx9hWZ4z7ZtO
-pNBDPvPJCiAOVUsILgCQhBUolz2dyLob25Fd0PVp0n9ibIPEQYjTfHjOK40qb57N
-LhEp2ceGiAfsywPSi0TH1PQ6JgbCe/RM4TefI/sj3gYJPka3ksMvylhMIgUVLgME
-kYizhzwHqlLMspB858SioREZdGwcdZrMMIajGFU69Q9ZRDBzhPvxyKhYoObcOtk1
-uVaiE/fNoi3wKGJz2l2vhUuNrQW7MWlVMag+Qes4YACUTk9LZrOVFEJFjWc8xGUi
-ABtfKGs5JgNr/sWnhvifLn8lJuf0/BJmjD+L5QwXYs2cS7gcZJtTM12J94r0Twgw
-wF2lNmIxAE9sYqj5Rh3dIlTPE5vMUECmQEGjIBB/hzT65VxVqSjU/IlS506JTg3p
-IOQtZ15cUzTBpda4jrvqcq6RNVvgBCu2bV5D8Z4z9FUlPyvD+Zq/6lcoJfLtznAs
-G2463hyPAHTGBIcZ5p5bTuGxoAb6ivyqo4b9Qi4yYA6je9HJmuy8T3Mn5JROoeu9
-BH1K54r/mpT4TQPwuKUvRRtBAV2OPHjo+zp0Gd4Y6rxDYxEIdfEae7pQr/QExSPB
-q/QCr9RhixR1mO373LHuja+MxdAxIxugb2HTS61PQo+PbYrhJMcVuxTwJOECAwEA
-AQKCAgAH7ToRrMkH0ji5SdsmTx+KQkW4PFLCXVke/68PjX7KmAQnl3W4oVwnHr/W
-oROEbVn1GTlre7jU+YaAY0SWZrwgjLE1OWGrG1ZizlUbrCdAd6GOX09J4KROml1L
-DXB0x7tbZMLOrCVjSbLD/ITrM6MN8Gnxvbv0/yOQjxU8vzbP4gLOjHxMRCo001RV
-Ll7lPvcjTQ84zJilU6sE8vJ6zdfVZSK/ou2X0cekG+kP7+fvefo8/UcbEPlGhUrV
-IdVPPQGUu90K2hmN0FBdLi8Vik0klAN68Qu/bHwuKbNzsnmIoztucFFUR+fG3u84
-87aPS0L/J3+mjT2Tv6qhJANUGBmrK/h7MkelpKXlRTCITJLX9xP7hfSbJ4f6aLVq
-ZYPPciGxSBbUDgAwvPtOlMDzccg7YsYyiBBO28wh8MN97rePmc0z6nGmjeXhcbCC
-QktG50VYFCyqp5muKgqQmRfRjHFHLWs8GEqgxMeEL3U3HjYfCYr+6E8Sr5OnOBeH
-3buCi1+zgnNYCvbamgY/OJmW7f9h5O31hxmTplc2E1ZuxUGQZthabt1rN3bmNkyf
-KUmPwnIYkDkWBSV5lzyQExfS3/EVvj0EnHhx8faamimNrGo8xCcfnLT3c0WEFVmo
-yIyVRX3EpXJFM2JkeJ21/IEZXTzHSoNxk12CBG8i8lLSflWSMQKCAQEA2ZqVnOuV
-SZfLCUYUUh8Hvhc5zONstfq7ma1Zsttsdaj9t68nLRiBDvLOGnMjDkYZzoSd4fyl
-oy+YqWGBqcqa5kg1NOCH0I46p9d8RcWAfDnB4sqbLgWh70qsvni6igRijmsMDvkA
-U9HeEdPaLCjQ4UXw7GQvN5rRxuRt+OSqV3tV/Pk9JYyYjz7faC8dmbKDrWHHuOvm
-/9y6Xy+L5IgftykNlUeddSCIoMOAadM7BiRjsrHnOYBQ8xBcn0OYafpIswItrgVi
-IrsPJaBFidx8QYK4MVibyka6U0cm28OocDSPtSk/4jrnCEEhLjFUnWwuMXuBGlrd
-W7wP/muoJqb1VwKCAQEAzsAT90kkOCvAcrfGRE3KkUjwWAsQyP8u2+27JIQPqrpW
-GfWAzJXFt80TSp0Zf/Lrq3/SQ9n4AaL4K9dcMoreedoQN9C9JI7zPtZAWNrJVUcV
-dq2gZjBQ78+oK7uQgvFNWxga5D+Nh+Y+9Tp537fc5HIh0Y13PgsxxPk2OnZJTvLX
-HM5H7Aua9ssmqChsrKihuUsDSPozfBz+H7FNHEdKMqVLqJJSK6m0uMxuLovdVfka
-5S7iBMjEGZc46Iz3ckE0pdOiQLooNqfEQqFe5Uou/KZxxKI1NW25rEEBTVyQWt+2
-BNUCfKP7noZ45u5sUY3eJrgI7BrPEDbNS81WYaLchwKCAQA8Q4mHyd6wYO+EA/qA
-u8NDK9+AFMP4qhXme5HJ7Obetwx9IG7zGEQ1xZy6yoQ84cEn5qZq/bNJvFbFIhHs
-2gWIHRtPJ5e1dI5eCVmLYSUyQjSmAIJ1fm3YfY/VuE3BB3HcC11tkBw9GnQr78YO
-UMd4fAw7C4vgFGpgcMbcFUfvrmKkCsqaaZOeqETq75F9DWlWTSwo1HxHA/RBhENz
-6RcPfLkcTJcY5wevrjUUGcHQ86cAyDBHRngkuLVODkRZpU0Y9lN8TFVfVPre6sIX
-ag6nffJRCD8tB+V2RtBGMKunV4ctHt1oY/Oz34W260aJynoIjjG1ANEpJK4xQdNx
-0O9FAoIBAQCz2AGGKemHswdEwveEkuaSWpA3Bekj7lYkmTchHH9EU7JyAkx3qhDD
-QXB2hxGXawf1tsqAmypQwiJ+gGeCz6mW9UkGRF1DX9XX4yc2I5rew2a4RXAxc/Xz
-pP70i8O5I43Wn7FEusOyY2aAis1Y/eb4EQ+56QTAw5wXa3DwidRbCIJ2XDnT6oRy
-CWUnAYMG7ek/9TB2Wq5OWCn2B5S79IdmZsLZb+5qbMT3u1xcwO1Xy8jJc27IGpv6
-ZsDqCTV1/aJ+XQnWpBg28tiV3Sle6pjUzTRJh5AhWcEZRbKMSOiJI/CBY4k2Qq6t
-xuuEdgFjL7T+mTepqehUglcyiPuLEtAhAoIBAQDDQ5pTFOlunfYzm+CIvvImAgy7
-vrEabJYABATytAbXRMMbrKoEdU2ApEDyEW7PgysDnYLAZ+icObnJlBTYvNdlWFly
-XiviGVfpjFWDT9U/gUwFQu2lfEjoiivoGS92USHUy4UMVHiiznnm20VLLkgd3xna
-HUNSDdHIEgzOTmDsKJwMfA9zGckx23JJimPR5ekv6vr6QllYtECs4lTC1gVQAp2f
-5daxHRbkmO6gw1RgQADLkAnYz3aI1jNuHm5VyAZGt/d3JCtZ3Wwwejll8uJ4J09G
-oEtqyY9RVeHK50bLO4lyAXFiE+J6qqXjsGC20cpxeZYW5llMY/dhA6WV4YXV
------END RSA PRIVATE KEY-----
diff --git a/apex/sdkextensions/com.android.sdkext.pk8 b/apex/sdkextensions/com.android.sdkext.pk8
deleted file mode 100644
index ccc0bf4..0000000
--- a/apex/sdkextensions/com.android.sdkext.pk8
+++ /dev/null
Binary files differ
diff --git a/apex/sdkextensions/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem
deleted file mode 100644
index 45d2ade..0000000
--- a/apex/sdkextensions/com.android.sdkext.x509.pem
+++ /dev/null
@@ -1,35 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIGIzCCBAugAwIBAgIUXuDL7QvzQh7S6rihWz2KRvCFVT0wDQYJKoZIhvcNAQEL
-BQAwgZ8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
-DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
-b2lkMRswGQYDVQQDDBJjb20uYW5kcm9pZC5zZGtleHQxIjAgBgkqhkiG9w0BCQEW
-E2FuZHJvaWRAYW5kcm9pZC5jb20wIBcNMTkxMjAyMTQyNDM0WhgPNDc1NzEwMjgx
-NDI0MzRaMIGfMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQG
-A1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwH
-QW5kcm9pZDEbMBkGA1UEAwwSY29tLmFuZHJvaWQuc2RrZXh0MSIwIAYJKoZIhvcN
-AQkBFhNhbmRyb2lkQGFuZHJvaWQuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
-MIICCgKCAgEAxFvZZ6ES1oqAu1K74/ZxnC3SOhHnLISLBgJEe7DqtdpuNFAwvdVO
-RL/HULhDbjYlOhpU2x3SavDIZZ2lRfiS9Q+M25WftxTRHVjBcpgwbV77TVxPKlAa
-tVN2lUVOY+s4QAVMNIXjC4kCKK/pCQtacH715EtdV47fWdg/Nx4iP/Aord8k3KGI
-9iI2ZOUjaugTRxu5lKRNDrv0bw5rEzyYmDyMud+kR/iS3/5oog57wPE0ffAkZXWE
-p3L2Cejre3ekCizsvVh6EmH6ForKLtL6f0z5Zir1f4R9+YcENspTlJR3pDhg7y3I
-uTQT/iDCtV0l+g2PjGZPEeAQHND3+kDQR7Sno/WC1Nhws6vcu1MdrC+kIh1ewx4y
-8moy/yqb5M98PJDzTSi/AOTB/OiqLXo/T8rjLBmirs9y3fTT6gJ6qXxOWgt8dle9
-7TBfa84Xi8uVY61c+A+YI0nLal7QDPsP3RPv5sJSQ9x9YnweVfD9Q0EOi52sSNu+
-QuN/kcUrMgPgha20VhfH/CkkPDyIp6aZyHHM69MIl+cYEm8vPa5uy3dosuRomT0f
-I4HOBjULDIuj+rIi+Rg3qHvmpuejwZXI/FBNWIhLEUG3ytgksjMaBoYAYflGdrcj
-BQexuF3EO+j4uo7JGjNcaT7wRoCH9gt29VHckDg2qz6VWXrlpmME4UkCAwEAAaNT
-MFEwHQYDVR0OBBYEFISN2nmUHllgPZMZ62U7mU3ZxzlXMB8GA1UdIwQYMBaAFISN
-2nmUHllgPZMZ62U7mU3ZxzlXMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
-BQADggIBAFHIwyBNIVyHXUsDUdcjxfojXQsF/BCL9ehE3pgdkvDfQanaIREWn0nc
-oCFDFkYMRqaXOGC5TKq4OCjXOLsdfODt8HQ3F9J1B0ghQ5tfOdw7xDugNAszqP/Q
-h7kpvqLTycjrqOeZ5KjxEEYtP/KlUmALgOKcTcSH+XhWyxhjF4j24T9F2yJRr3/A
-r1NGU/djH953bHKC8OpJ2teUpDLA4TxVp/EhslH2eVigF80c/w74QPLEWkD9zv/4
-YeRg/R5N83zHs99NtlWMIeHfK6fUbzMyaSZtvm+jK20tkByQb/OQRed+drk25MtL
-68IRvxqri367qRScdpTZbu0ByLO4X8gFdubRUWr+tcO4pZX+DJRVriExbOkU2xhS
-Vtslq23V/hwTuUNm1CXjR70mPS13BTmHrIQDqLoIw/WTQlGh+vxnlAFRIHM3KB2c
-OdzUBu+NcB4aZEd0KKtct600A0DKPr1MQPb5jDq9wEtPSQYwMF0nRFNnXltbrXMd
-4hhwnhKr74fVMUmb+7eQP56XE/Nk4D+npMO54vv1pav+DI2/nxCND9BOLBweY38p
-Tvd2RjesMok0zXuVXiCIu4GEpwo7WkSnv25xrb0Ey2M8QWnGNnCcX7Kv6ip3RdWy
-HiN0G8RJrs/yNEVSDRx8ZhtwTpXVPQxbARbmhNF4/fnolElkmrMP
------END CERTIFICATE-----
diff --git a/apex/sdkextensions/derive_sdk/Android.bp b/apex/sdkextensions/derive_sdk/Android.bp
deleted file mode 100644
index 41eae09..0000000
--- a/apex/sdkextensions/derive_sdk/Android.bp
+++ /dev/null
@@ -1,55 +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.
-
-cc_defaults {
- name: "derive_sdk-defaults",
- srcs: [
- "derive_sdk.cpp",
- "sdk.proto",
- ],
- proto: {
- type: "lite",
- static: true,
- },
- min_sdk_version: "current",
- shared_libs: ["liblog"],
- // static c++/libbase for smaller size
- stl: "c++_static",
- static_libs: ["libbase"],
-}
-
-cc_binary {
- name: "derive_sdk",
- defaults: [ "derive_sdk-defaults" ],
- apex_available: [ "com.android.sdkext" ],
- visibility: [ "//frameworks/base/apex/sdkextensions" ]
-}
-
-// Work around testing using a 64-bit test suite on 32-bit test device by
-// using a prefer32 version of derive_sdk in testing.
-cc_binary {
- name: "derive_sdk_prefer32",
- defaults: [ "derive_sdk-defaults" ],
- compile_multilib: "prefer32",
- stem: "derive_sdk",
- apex_available: [ "test_com.android.sdkext" ],
- visibility: [ "//frameworks/base/apex/sdkextensions/testing" ],
- installable: false,
-}
-
-prebuilt_etc {
- name: "derive_sdk.rc",
- src: "derive_sdk.rc",
- installable: false,
-}
diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
deleted file mode 100644
index 900193a..0000000
--- a/apex/sdkextensions/derive_sdk/derive_sdk.cpp
+++ /dev/null
@@ -1,79 +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 LOG_TAG "derive_sdk"
-
-#include <algorithm>
-#include <dirent.h>
-#include <iostream>
-#include <sys/stat.h>
-#include <vector>
-
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/properties.h>
-
-#include "frameworks/base/apex/sdkextensions/derive_sdk/sdk.pb.h"
-
-using com::android::sdkext::proto::SdkVersion;
-
-int main(int, char**) {
- std::unique_ptr<DIR, decltype(&closedir)> apex(opendir("/apex"), closedir);
- if (!apex) {
- LOG(ERROR) << "Could not read /apex";
- return EXIT_FAILURE;
- }
- struct dirent* de;
- std::vector<std::string> paths;
- while ((de = readdir(apex.get()))) {
- std::string name = de->d_name;
- if (name[0] == '.' || name.find('@') != std::string::npos) {
- // Skip <name>@<ver> dirs, as they are bind-mounted to <name>
- continue;
- }
- std::string path = "/apex/" + name + "/etc/sdkinfo.binarypb";
- struct stat statbuf;
- if (stat(path.c_str(), &statbuf) == 0) {
- paths.push_back(path);
- }
- }
-
- std::vector<int> versions;
- for (const auto& path : paths) {
- std::string contents;
- if (!android::base::ReadFileToString(path, &contents, true)) {
- LOG(ERROR) << "failed to read " << path;
- continue;
- }
- SdkVersion sdk_version;
- if (!sdk_version.ParseFromString(contents)) {
- LOG(ERROR) << "failed to parse " << path;
- continue;
- }
- LOG(INFO) << "Read version " << sdk_version.version() << " from " << path;
- versions.push_back(sdk_version.version());
- }
- auto itr = std::min_element(versions.begin(), versions.end());
- std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr);
-
- if (!android::base::SetProperty("build.version.extensions.r", prop_value)) {
- LOG(ERROR) << "failed to set sdk_info prop";
- return EXIT_FAILURE;
- }
-
- LOG(INFO) << "R extension version is " << prop_value;
- return EXIT_SUCCESS;
-}
diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.rc b/apex/sdkextensions/derive_sdk/derive_sdk.rc
deleted file mode 100644
index 18f021c..0000000
--- a/apex/sdkextensions/derive_sdk/derive_sdk.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service derive_sdk /apex/com.android.sdkext/bin/derive_sdk
- user nobody
- group nobody
- oneshot
- disabled
diff --git a/apex/sdkextensions/derive_sdk/sdk.proto b/apex/sdkextensions/derive_sdk/sdk.proto
deleted file mode 100644
index d15b935..0000000
--- a/apex/sdkextensions/derive_sdk/sdk.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 = "proto3";
-package com.android.sdkext.proto;
-
-option java_outer_classname = "SdkProto";
-option optimize_for = LITE_RUNTIME;
-
-message SdkVersion {
- int32 version = 1;
-}
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
deleted file mode 100644
index b8aad7d..0000000
--- a/apex/sdkextensions/framework/Android.bp
+++ /dev/null
@@ -1,49 +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.
-
-package {
- default_visibility: [ ":__pkg__" ]
-}
-
-filegroup {
- name: "framework-sdkextensions-sources",
- srcs: [
- "java/**/*.java",
- ],
- path: "java",
- visibility: [ "//frameworks/base" ] // For the "global" stubs.
-}
-
-java_sdk_library {
- name: "framework-sdkextensions",
- srcs: [ ":framework-sdkextensions-sources" ],
- defaults: ["framework-module-defaults"],
-
- // TODO(b/155480189) - Remove naming_scheme once references have been resolved.
- // Temporary java_sdk_library component naming scheme to use to ease the transition from separate
- // modules to java_sdk_library.
- naming_scheme: "framework-modules",
-
- permitted_packages: [ "android.os.ext" ],
- installable: true,
- visibility: [
- "//frameworks/base/apex/sdkextensions",
- "//frameworks/base/apex/sdkextensions/testing",
- ],
- hostdex: true, // for hiddenapi check
- apex_available: [
- "com.android.sdkext",
- "test_com.android.sdkext",
- ],
-}
diff --git a/apex/sdkextensions/framework/api/current.txt b/apex/sdkextensions/framework/api/current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/sdkextensions/framework/api/current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/sdkextensions/framework/api/module-lib-current.txt b/apex/sdkextensions/framework/api/module-lib-current.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/sdkextensions/framework/api/module-lib-current.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/sdkextensions/framework/api/module-lib-removed.txt b/apex/sdkextensions/framework/api/module-lib-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/sdkextensions/framework/api/module-lib-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/sdkextensions/framework/api/removed.txt b/apex/sdkextensions/framework/api/removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/sdkextensions/framework/api/removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/sdkextensions/framework/api/system-current.txt b/apex/sdkextensions/framework/api/system-current.txt
deleted file mode 100644
index bbff4c5..0000000
--- a/apex/sdkextensions/framework/api/system-current.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 2.0
-package android.os.ext {
-
- public class SdkExtensions {
- method public static int getExtensionVersion(int);
- }
-
-}
-
diff --git a/apex/sdkextensions/framework/api/system-removed.txt b/apex/sdkextensions/framework/api/system-removed.txt
deleted file mode 100644
index d802177..0000000
--- a/apex/sdkextensions/framework/api/system-removed.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 2.0
diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
deleted file mode 100644
index 6c25f28..0000000
--- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
+++ /dev/null
@@ -1,75 +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.
- */
-
-package android.os.ext;
-
-import android.annotation.IntDef;
-import android.annotation.SystemApi;
-import android.os.Build.VERSION_CODES;
-import android.os.SystemProperties;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Methods for interacting with the extension SDK.
- *
- * This class provides information about the extension SDK version present
- * on this device. Use the {@link #getExtensionVersion(int) getExtension} to
- * query for the extension version for the given SDK version.
-
- * @hide
- */
-@SystemApi
-public class SdkExtensions {
-
- private static final int R_EXTENSION_INT;
- static {
- // Note: when adding more extension versions, the logic that records current
- // extension versions when saving a rollback must also be updated.
- // At the time of writing this is in RollbackManagerServiceImpl#getExtensionVersions()
- R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0);
- }
-
- /**
- * Values suitable as parameters for {@link #getExtensionVersion(int)}.
- * @hide
- */
- @IntDef(value = { VERSION_CODES.R })
- @Retention(RetentionPolicy.SOURCE)
- public @interface SdkVersion {}
-
- private SdkExtensions() { }
-
- /**
- * Return the version of the extension to the given SDK.
- *
- * @param sdk the SDK version to get the extension version of.
- * @see SdkVersion
- * @throws IllegalArgumentException if sdk is not an sdk version with extensions
- */
- public static int getExtensionVersion(@SdkVersion int sdk) {
- if (sdk < VERSION_CODES.R) {
- throw new IllegalArgumentException(String.valueOf(sdk) + " does not have extensions");
- }
-
- if (sdk == VERSION_CODES.R) {
- return R_EXTENSION_INT;
- }
- return 0;
- }
-
-}
diff --git a/apex/sdkextensions/framework/java/android/os/ext/package.html b/apex/sdkextensions/framework/java/android/os/ext/package.html
deleted file mode 100644
index 34c1697..0000000
--- a/apex/sdkextensions/framework/java/android/os/ext/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<HTML>
-<BODY>
-Provides APIs to interface with the SDK extensions.
-</BODY>
-</HTML>
diff --git a/apex/sdkextensions/gen_sdkinfo.py b/apex/sdkextensions/gen_sdkinfo.py
deleted file mode 100644
index 5af478b..0000000
--- a/apex/sdkextensions/gen_sdkinfo.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import sdk_pb2
-import sys
-
-if __name__ == '__main__':
- argv = sys.argv[1:]
- if not len(argv) == 4 or sorted([argv[0], argv[2]]) != ['-o', '-v']:
- print('usage: gen_sdkinfo -v <version> -o <output-file>')
- sys.exit(1)
-
- for i in range(len(argv)):
- if sys.argv[i] == '-o':
- filename = sys.argv[i+1]
- if sys.argv[i] == '-v':
- version = int(sys.argv[i+1])
-
- proto = sdk_pb2.SdkVersion()
- proto.version = version
- with open(filename, 'wb') as f:
- f.write(proto.SerializeToString())
diff --git a/apex/sdkextensions/manifest.json b/apex/sdkextensions/manifest.json
deleted file mode 100644
index deeb29e..0000000
--- a/apex/sdkextensions/manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "com.android.sdkext",
- "version": 300000000
-}
diff --git a/apex/sdkextensions/sdk.proto b/apex/sdkextensions/sdk.proto
deleted file mode 100644
index d15b935..0000000
--- a/apex/sdkextensions/sdk.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 = "proto3";
-package com.android.sdkext.proto;
-
-option java_outer_classname = "SdkProto";
-option optimize_for = LITE_RUNTIME;
-
-message SdkVersion {
- int32 version = 1;
-}
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
deleted file mode 100644
index f2f5b32..0000000
--- a/apex/sdkextensions/testing/Android.bp
+++ /dev/null
@@ -1,46 +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.
-
-apex_test {
- name: "test_com.android.sdkext",
- visibility: [ "//system/apex/tests" ],
- defaults: ["com.android.sdkext-defaults"],
- manifest: "test_manifest.json",
- prebuilts: [ "sdkinfo_45" ],
- file_contexts: ":com.android.sdkext-file_contexts",
- installable: false, // Should never be installed on the systemimage
- multilib: {
- prefer32: {
- binaries: ["derive_sdk_prefer32"],
- },
- },
- // The automated test infra ends up building this apex for 64+32-bit and
- // then installs it on a 32-bit-only device. Work around this weirdness
- // by preferring 32-bit.
- compile_multilib: "prefer32",
-}
-
-genrule {
- name: "sdkinfo_45_src",
- out: [ "sdkinfo.binarypb" ],
- tools: [ "gen_sdkinfo" ],
- cmd: "$(location) -v 45 -o $(out)",
-}
-
-prebuilt_etc {
- name: "sdkinfo_45",
- src: ":sdkinfo_45_src",
- filename: "sdkinfo.binarypb",
- installable: false,
-}
diff --git a/apex/sdkextensions/testing/test_manifest.json b/apex/sdkextensions/testing/test_manifest.json
deleted file mode 100644
index 1b4a2b0..0000000
--- a/apex/sdkextensions/testing/test_manifest.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "name": "com.android.sdkext",
- "version": 2147483647
-}
diff --git a/api/test-current.txt b/api/test-current.txt
index 5dc7bdb..a163dea 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1728,6 +1728,15 @@
}
+package android.media.tv {
+
+ public final class TvInputManager {
+ method public void addHardwareDevice(int);
+ method public void removeHardwareDevice(int);
+ }
+
+}
+
package android.metrics {
public class LogMaker {
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index f30ed17..3dbe413 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -292,6 +292,7 @@
name: "statsd_test",
defaults: ["statsd_defaults"],
test_suites: ["device-tests", "mts"],
+ test_config: "statsd_test.xml",
//TODO(b/153588990): Remove when the build system properly separates
//32bit and 64bit architectures.
@@ -299,7 +300,10 @@
multilib: {
lib64: {
suffix: "64",
- }
+ },
+ lib32: {
+ suffix: "32",
+ },
},
cflags: [
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index c6e9e01..62e1567 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -6713,11 +6713,11 @@
* Logs when a data stall event occurs.
*
* Log from:
- * frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+ * packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
*/
message DataStallEvent {
// Data stall evaluation type.
- // See frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+ // See packages/modules/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
// Refer to the definition of DATA_STALL_EVALUATION_TYPE_*.
optional int32 evaluation_type = 1;
// See definition in data_stall_event.proto.
@@ -6730,6 +6730,10 @@
optional com.android.server.connectivity.CellularData cell_info = 5 [(log_mode) = MODE_BYTES];
// See definition in data_stall_event.proto.
optional com.android.server.connectivity.DnsEvent dns_event = 6 [(log_mode) = MODE_BYTES];
+ // The tcp packets fail rate from the latest tcp polling.
+ optional int32 tcp_fail_rate = 7;
+ // Number of packets sent since the last received packet.
+ optional int32 tcp_sent_since_last_recv = 8;
}
/*
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 3d0eeb8..8587e145 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -160,7 +160,7 @@
static const long kPullerCacheClearIntervalSec = 1;
// Max time to do a pull.
- static const int64_t kPullMaxDelayNs = 10 * NS_PER_SEC;
+ static const int64_t kPullMaxDelayNs = 30 * NS_PER_SEC;
// Maximum number of pushed atoms statsd stats will track above kMaxPushedAtomId.
static const int kMaxNonPlatformPushedAtoms = 100;
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index e86fdf0..673f668 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -82,7 +82,9 @@
DIMENSION_GUARDRAIL_REACHED = 6,
MULTIPLE_BUCKETS_SKIPPED = 7,
// Not an invalid bucket case, but the bucket is dropped.
- BUCKET_TOO_SMALL = 8
+ BUCKET_TOO_SMALL = 8,
+ // Not an invalid bucket case, but the bucket is skipped.
+ NO_DATA = 9
};
struct Activation {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index f03ce45..dbec24b 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -108,7 +108,7 @@
mSkipZeroDiffOutput(metric.skip_zero_diff_output()),
mUseZeroDefaultBase(metric.use_zero_default_base()),
mHasGlobalBase(false),
- mCurrentBucketIsInvalid(false),
+ mCurrentBucketIsSkipped(false),
mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC
: StatsdStats::kPullMaxDelayNs),
mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()),
@@ -383,15 +383,12 @@
void ValueMetricProducer::invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
const BucketDropReason reason) {
- if (!mCurrentBucketIsInvalid) {
+ if (!mCurrentBucketIsSkipped) {
// Only report to StatsdStats once per invalid bucket.
StatsdStats::getInstance().noteInvalidatedBucket(mMetricId);
}
- if (!maxDropEventsReached()) {
- mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason));
- }
- mCurrentBucketIsInvalid = true;
+ skipCurrentBucket(dropTimeNs, reason);
}
void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs,
@@ -400,6 +397,14 @@
resetBase();
}
+void ValueMetricProducer::skipCurrentBucket(const int64_t dropTimeNs,
+ const BucketDropReason reason) {
+ if (!maxDropEventsReached()) {
+ mCurrentSkippedBucket.dropEvents.emplace_back(buildDropEvent(dropTimeNs, reason));
+ }
+ mCurrentBucketIsSkipped = true;
+}
+
void ValueMetricProducer::resetBase() {
for (auto& slice : mCurrentBaseInfo) {
for (auto& baseInfo : slice.second) {
@@ -961,12 +966,10 @@
int64_t conditionTrueDuration = mConditionTimer.newBucketStart(bucketEndTime);
bool isBucketLargeEnough = bucketEndTime - mCurrentBucketStartTimeNs >= mMinBucketSizeNs;
if (!isBucketLargeEnough) {
- if (!maxDropEventsReached()) {
- mCurrentSkippedBucket.dropEvents.emplace_back(
- buildDropEvent(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL));
- }
+ skipCurrentBucket(eventTimeNs, BucketDropReason::BUCKET_TOO_SMALL);
}
- if (isBucketLargeEnough && !mCurrentBucketIsInvalid) {
+ bool bucketHasData = false;
+ if (!mCurrentBucketIsSkipped) {
// The current bucket is large enough to keep.
for (const auto& slice : mCurrentSlicedBucket) {
ValueBucket bucket = buildPartialBucket(bucketEndTime, slice.second);
@@ -975,14 +978,33 @@
if (bucket.valueIndex.size() > 0) {
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(bucket);
+ bucketHasData = true;
}
}
- } else {
+ }
+
+ if (!bucketHasData && !mCurrentBucketIsSkipped) {
+ skipCurrentBucket(eventTimeNs, BucketDropReason::NO_DATA);
+ }
+
+ if (mCurrentBucketIsSkipped) {
mCurrentSkippedBucket.bucketStartTimeNs = mCurrentBucketStartTimeNs;
- mCurrentSkippedBucket.bucketEndTimeNs = bucketEndTime;
+ // Fill in the gap if we skipped multiple buckets.
+ mCurrentSkippedBucket.bucketEndTimeNs =
+ numBucketsForward > 1 ? nextBucketStartTimeNs : bucketEndTime;
mSkippedBuckets.emplace_back(mCurrentSkippedBucket);
}
+ // This means that the current bucket was not flushed before a forced bucket split.
+ if (bucketEndTime < nextBucketStartTimeNs && numBucketsForward <= 1) {
+ SkippedBucket bucketInGap;
+ bucketInGap.bucketStartTimeNs = bucketEndTime;
+ bucketInGap.bucketEndTimeNs = nextBucketStartTimeNs;
+ bucketInGap.dropEvents.emplace_back(
+ buildDropEvent(eventTimeNs, BucketDropReason::NO_DATA));
+ mSkippedBuckets.emplace_back(bucketInGap);
+ }
+
appendToFullBucket(eventTimeNs, fullBucketEndTimeNs);
initCurrentSlicedBucket(nextBucketStartTimeNs);
// Update the condition timer again, in case we skipped buckets.
@@ -1036,13 +1058,13 @@
// TODO: remove mCurrentBaseInfo entries when obsolete
}
- mCurrentBucketIsInvalid = false;
+ mCurrentBucketIsSkipped = false;
mCurrentSkippedBucket.reset();
// If we do not have a global base when the condition is true,
// we will have incomplete bucket for the next bucket.
if (mUseDiff && !mHasGlobalBase && mCondition) {
- mCurrentBucketIsInvalid = false;
+ mCurrentBucketIsSkipped = false;
}
mCurrentBucketStartTimeNs = nextBucketStartTimeNs;
VLOG("metric %lld: new bucket start time: %lld", (long long)mMetricId,
@@ -1051,7 +1073,7 @@
void ValueMetricProducer::appendToFullBucket(int64_t eventTimeNs, int64_t fullBucketEndTimeNs) {
bool isFullBucketReached = eventTimeNs > fullBucketEndTimeNs;
- if (mCurrentBucketIsInvalid) {
+ if (mCurrentBucketIsSkipped) {
if (isFullBucketReached) {
// If the bucket is invalid, we ignore the full bucket since it contains invalid data.
mCurrentFullBucket.clear();
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 751fef2..bb4a661 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -144,6 +144,10 @@
void invalidateCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
void invalidateCurrentBucketWithoutResetBase(const int64_t dropTimeNs,
const BucketDropReason reason);
+ // Skips the current bucket without notifying StatsdStats of the skipped bucket.
+ // This should only be called from #flushCurrentBucketLocked. Otherwise, a future event that
+ // causes the bucket to be invalidated will not notify StatsdStats.
+ void skipCurrentBucket(const int64_t dropTimeNs, const BucketDropReason reason);
const int mWhatMatcherIndex;
@@ -250,11 +254,9 @@
// diff against.
bool mHasGlobalBase;
- // Invalid bucket. There was a problem in collecting data in the current bucket so we cannot
- // trust any of the data in this bucket.
- //
- // For instance, one pull failed.
- bool mCurrentBucketIsInvalid;
+ // This is to track whether or not the bucket is skipped for any of the reasons listed in
+ // BucketDropReason, many of which make the bucket potentially invalid.
+ bool mCurrentBucketIsSkipped;
const int64_t mMaxPullDelayNs;
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 1121392..6bfa267 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -212,6 +212,8 @@
MULTIPLE_BUCKETS_SKIPPED = 7;
// Not an invalid bucket case, but the bucket is dropped.
BUCKET_TOO_SMALL = 8;
+ // Not an invalid bucket case, but the bucket is skipped.
+ NO_DATA = 9;
};
message DropEvent {
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 2e6043d..72decf2 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -278,7 +278,7 @@
optional int64 max_num_gauge_atoms_per_bucket = 11 [default = 10];
- optional int32 max_pull_delay_sec = 13 [default = 10];
+ optional int32 max_pull_delay_sec = 13 [default = 30];
optional bool split_bucket_for_app_upgrade = 14 [default = true];
}
@@ -328,7 +328,7 @@
optional bool skip_zero_diff_output = 14 [default = true];
- optional int32 max_pull_delay_sec = 16 [default = 10];
+ optional int32 max_pull_delay_sec = 16 [default = 30];
optional bool split_bucket_for_app_upgrade = 17 [default = true];
diff --git a/cmds/statsd/statsd_test.xml b/cmds/statsd/statsd_test.xml
new file mode 100644
index 0000000..8f9bb1c
--- /dev/null
+++ b/cmds/statsd/statsd_test.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Runs statsd_test.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-native" />
+ <option name="test-suite-tag" value="mts" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="statsd_test->/data/local/tmp/statsd_test" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="statsd_test" />
+ </test>
+
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="mainline-module-package-name" value="com.google.android.os.statsd" />
+ </object>
+</configuration>
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 474aa22..14246cab 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -1115,13 +1115,21 @@
EXPECT_EQ(false, curInterval.hasValue);
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
{bucket2StartTimeNs}, {bucket3StartTimeNs});
+ // The 1st bucket is dropped because of no data
// The 3rd bucket is dropped due to multiple buckets being skipped.
- ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
- EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
- EXPECT_EQ(bucket4StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
+ ASSERT_EQ(2, valueProducer->mSkippedBuckets.size());
+
+ EXPECT_EQ(bucketStartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+ EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size());
- EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
- EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs);
+ EXPECT_EQ(NO_DATA, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+ EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs);
+
+ EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[1].bucketStartTimeNs);
+ EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].bucketEndTimeNs);
+ ASSERT_EQ(1, valueProducer->mSkippedBuckets[1].dropEvents.size());
+ EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[1].dropEvents[0].reason);
+ EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[1].dropEvents[0].dropTimeNs);
}
/*
@@ -2214,7 +2222,7 @@
valueProducer->mCondition = ConditionState::kFalse;
valueProducer->onConditionChanged(true, bucketStartTimeNs + 2);
- EXPECT_EQ(true, valueProducer->mCurrentBucketIsInvalid);
+ EXPECT_EQ(true, valueProducer->mCurrentBucketIsSkipped);
ASSERT_EQ(0UL, valueProducer->mCurrentSlicedBucket.size());
ASSERT_EQ(0UL, valueProducer->mSkippedBuckets.size());
@@ -2629,13 +2637,17 @@
vector<shared_ptr<LogEvent>> allData;
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4));
+ // Pull fails and arrives late.
valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1);
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
{partialBucketSplitTimeNs - bucketStartTimeNs},
{bucketStartTimeNs}, {partialBucketSplitTimeNs});
ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+ ASSERT_EQ(2, valueProducer->mSkippedBuckets[0].dropEvents.size());
+ EXPECT_EQ(PULL_FAILED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+ EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[1].reason);
EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
- EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
+ EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
}
@@ -3464,26 +3476,41 @@
// Condition change event that skips forward by three buckets.
valueProducer->onConditionChanged(false, bucket4StartTimeNs + 10);
+ int64_t dumpTimeNs = bucket4StartTimeNs + 1000;
+
// Check dump report.
ProtoOutputStream output;
std::set<string> strSet;
- valueProducer->onDumpReport(bucket4StartTimeNs + 1000, true /* include recent buckets */, true,
+ valueProducer->onDumpReport(dumpTimeNs, true /* include current buckets */, true,
NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
StatsLogReport report = outputStreamToProto(&output);
EXPECT_TRUE(report.has_value_metrics());
ASSERT_EQ(0, report.value_metrics().data_size());
- ASSERT_EQ(1, report.value_metrics().skipped_size());
+ ASSERT_EQ(2, report.value_metrics().skipped_size());
EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
report.value_metrics().skipped(0).start_bucket_elapsed_millis());
- EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ EXPECT_EQ(NanoToMillis(bucket4StartTimeNs),
report.value_metrics().skipped(0).end_bucket_elapsed_millis());
ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
EXPECT_EQ(BucketDropReason::MULTIPLE_BUCKETS_SKIPPED, dropEvent.drop_reason());
EXPECT_EQ(NanoToMillis(bucket4StartTimeNs + 10), dropEvent.drop_time_millis());
+
+ // This bucket is skipped because a dumpReport with include current buckets is called.
+ // This creates a new bucket from bucket4StartTimeNs to dumpTimeNs in which we have no data
+ // since the condition is false for the entire bucket interval.
+ EXPECT_EQ(NanoToMillis(bucket4StartTimeNs),
+ report.value_metrics().skipped(1).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(dumpTimeNs),
+ report.value_metrics().skipped(1).end_bucket_elapsed_millis());
+ ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size());
+
+ dropEvent = report.value_metrics().skipped(1).drop_event(0);
+ EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(dumpTimeNs), dropEvent.drop_time_millis());
}
/*
@@ -3544,6 +3571,89 @@
}
/*
+ * Test that NO_DATA dump reason is logged when a flushed bucket contains no data.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenDataUnavailable) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds
+ valueProducer->onDumpReport(dumpReportTimeNs, true /* include current bucket */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(0, report.value_metrics().data_size());
+ ASSERT_EQ(1, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(dumpReportTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(dumpReportTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
+ * Test that a skipped bucket is logged when a forced bucket split occurs when the previous bucket
+ * was not flushed in time.
+ */
+TEST(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenForceBucketSplitBeforeBucketFlush) {
+ ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
+
+ sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+ sp<ValueMetricProducer> valueProducer =
+ ValueMetricProducerTestHelper::createValueProducerWithCondition(pullerManager, metric);
+
+ // App update event.
+ int64_t appUpdateTimeNs = bucket2StartTimeNs + 1000;
+ valueProducer->notifyAppUpgrade(appUpdateTimeNs);
+
+ // Check dump report.
+ ProtoOutputStream output;
+ std::set<string> strSet;
+ int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000; // 10 seconds
+ valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true,
+ NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+ StatsLogReport report = outputStreamToProto(&output);
+ EXPECT_TRUE(report.has_value_metrics());
+ ASSERT_EQ(0, report.value_metrics().data_size());
+ ASSERT_EQ(2, report.value_metrics().skipped_size());
+
+ EXPECT_EQ(NanoToMillis(bucketStartTimeNs),
+ report.value_metrics().skipped(0).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(0).end_bucket_elapsed_millis());
+ ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size());
+
+ auto dropEvent = report.value_metrics().skipped(0).drop_event(0);
+ EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
+
+ EXPECT_EQ(NanoToMillis(bucket2StartTimeNs),
+ report.value_metrics().skipped(1).start_bucket_elapsed_millis());
+ EXPECT_EQ(NanoToMillis(appUpdateTimeNs),
+ report.value_metrics().skipped(1).end_bucket_elapsed_millis());
+ ASSERT_EQ(1, report.value_metrics().skipped(1).drop_event_size());
+
+ dropEvent = report.value_metrics().skipped(1).drop_event(0);
+ EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason());
+ EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis());
+}
+
+/*
* Test multiple bucket drop events in the same bucket.
*/
TEST(ValueMetricProducerTest_BucketDrop, TestMultipleBucketDropEvents) {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8e43ca3..cffa59c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -604,11 +604,8 @@
throw new IllegalStateException(
"Received config update for non-existing activity");
}
- // Given alwaysReportChange=false because the configuration is from view root, the
- // activity may not be able to handle the changes. In that case the activity will be
- // relaunched immediately, then Activity#onConfigurationChanged shouldn't be called.
activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
- newDisplayId, false /* alwaysReportChange */);
+ newDisplayId);
};
}
@@ -4520,8 +4517,7 @@
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig,
- false /* alwaysReportChange */);
+ performConfigurationChangedForActivity(r, r.newConfig);
if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
+ r.activity.mCurrentConfig);
@@ -4841,8 +4837,7 @@
}
}
if (r.newConfig != null) {
- performConfigurationChangedForActivity(r, r.newConfig,
- false /* alwaysReportChange */);
+ performConfigurationChangedForActivity(r, r.newConfig);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+ r.activityInfo.name + " with new config "
+ r.activity.mCurrentConfig);
@@ -5510,12 +5505,11 @@
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
- * @param alwaysReportChange If the configuration is changed, always report to activity.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig, boolean alwaysReportChange) {
+ Configuration newBaseConfig) {
performConfigurationChangedForActivity(r, newBaseConfig, r.activity.getDisplayId(),
- false /* movedToDifferentDisplay */, alwaysReportChange);
+ false /* movedToDifferentDisplay */);
}
/**
@@ -5528,19 +5522,16 @@
* {@link ActivityClientRecord#overrideConfig}.
* @param displayId The id of the display where the Activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
- * @param alwaysReportChange If the configuration is changed, always report to activity.
* @return {@link Configuration} instance sent to client, null if not sent.
*/
private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
- Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay,
- boolean alwaysReportChange) {
+ Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
- r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay,
- alwaysReportChange);
+ r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
return reportedConfig;
}
@@ -5596,12 +5587,11 @@
* ActivityManager.
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
- * @param alwaysReportChange If the configuration is changed, always report to activity.
* @return Configuration sent to client, null if no changes and not moved to different display.
*/
private Configuration performActivityConfigurationChanged(Activity activity,
Configuration newConfig, Configuration amOverrideConfig, int displayId,
- boolean movedToDifferentDisplay, boolean alwaysReportChange) {
+ boolean movedToDifferentDisplay) {
if (activity == null) {
throw new IllegalArgumentException("No activity provided.");
}
@@ -5614,31 +5604,27 @@
// callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
handleWindowingModeChangeIfNeeded(activity, newConfig);
- boolean shouldChangeConfig = false;
+ boolean shouldReportChange = false;
if (activity.mCurrentConfig == null) {
- shouldChangeConfig = true;
+ shouldReportChange = true;
} else {
// If the new config is the same as the config this Activity is already running with and
// the override config also didn't change, then don't bother calling
// onConfigurationChanged.
final int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
-
- if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
+ if (diff == 0 && !movedToDifferentDisplay
+ && mResourcesManager.isSameResourcesOverrideConfig(activityToken,
amOverrideConfig)) {
- // Always send the task-level config changes. For system-level configuration, if
- // this activity doesn't handle any of the config changes, then don't bother
- // calling onConfigurationChanged as we're going to destroy it.
- if (alwaysReportChange
- || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
- || !REPORT_TO_ACTIVITY) {
- shouldChangeConfig = true;
- }
+ // Nothing significant, don't proceed with updating and reporting.
+ return null;
+ } else if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
+ || !REPORT_TO_ACTIVITY) {
+ // If this activity doesn't handle any of the config changes, then don't bother
+ // calling onConfigurationChanged. Otherwise, report to the activity for the
+ // changes.
+ shouldReportChange = true;
}
}
- if (!shouldChangeConfig && !movedToDifferentDisplay) {
- // Nothing significant, don't proceed with updating and reporting.
- return null;
- }
// Propagate the configuration change to ResourcesManager and Activity.
@@ -5675,7 +5661,7 @@
activity.dispatchMovedToDisplay(displayId, configToReport);
}
- if (shouldChangeConfig) {
+ if (shouldReportChange) {
activity.mCalled = false;
activity.onConfigurationChanged(configToReport);
if (!activity.mCalled) {
@@ -5792,7 +5778,7 @@
// config and avoid onConfigurationChanged if it hasn't changed.
Activity a = (Activity) cb;
performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
- config, false /* alwaysReportChange */);
+ config);
} else if (!equivalent) {
performConfigurationChanged(cb, config);
} else {
@@ -5943,18 +5929,6 @@
}
}
- @Override
- public void handleActivityConfigurationChanged(IBinder activityToken,
- @NonNull Configuration overrideConfig, int displayId) {
- handleActivityConfigurationChanged(activityToken, overrideConfig, displayId,
- // This is the only place that uses alwaysReportChange=true. The entry point should
- // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
- // has confirmed the activity should handle the configuration instead of relaunch.
- // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
- // something wrong from server side.
- true /* alwaysReportChange */);
- }
-
/**
* Handle new activity configuration and/or move to a different display. This method is a noop
* if {@link #updatePendingActivityConfiguration(IBinder, Configuration)} has been called with
@@ -5964,10 +5938,10 @@
* @param overrideConfig Activity override config.
* @param displayId Id of the display where activity was moved to, -1 if there was no move and
* value didn't change.
- * @param alwaysReportChange If the configuration is changed, always report to activity.
*/
- void handleActivityConfigurationChanged(IBinder activityToken,
- @NonNull Configuration overrideConfig, int displayId, boolean alwaysReportChange) {
+ @Override
+ public void handleActivityConfigurationChanged(IBinder activityToken,
+ @NonNull Configuration overrideConfig, int displayId) {
ActivityClientRecord r = mActivities.get(activityToken);
// Check input params.
if (r == null || r.activity == null) {
@@ -6010,15 +5984,14 @@
+ ", config=" + overrideConfig);
final Configuration reportedConfig = performConfigurationChangedForActivity(r,
- mCompatConfiguration, displayId, true /* movedToDifferentDisplay */,
- alwaysReportChange);
+ mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
if (viewRoot != null) {
viewRoot.onMovedToDisplay(displayId, reportedConfig);
}
} else {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name + ", config=" + overrideConfig);
- performConfigurationChangedForActivity(r, mCompatConfiguration, alwaysReportChange);
+ performConfigurationChangedForActivity(r, mCompatConfiguration);
}
// Notify the ViewRootImpl instance about configuration changes. It may have initiated this
// update to make sure that resources are updated before updating itself.
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 2f488cd..c577d0e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2032,8 +2032,16 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device's main front and back cameras can stream
- * concurrently as described in {@link
- * android.hardware.camera2.CameraManager#getConcurrentCameraIds()}
+ * concurrently as described in {@link
+ * android.hardware.camera2.CameraManager#getConcurrentCameraIds()}.
+ * </p>
+ * <p>While {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds()} and
+ * associated APIs are only available on API level 30 or newer, this feature flag may be
+ * advertised by devices on API levels below 30. If present on such a device, the same
+ * guarantees hold: The main front and main back camera can be used at the same time, with
+ * guaranteed stream configurations as defined in the table for concurrent streaming at
+ * {@link android.hardware.camera2.CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}.
+ * </p>
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent";
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index ebf4cc5..bf105ce 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2216,6 +2216,20 @@
}
};
+ // Uses IS_USER_UNLOCKED_PROPERTY for invalidation as the APIs have the same dependencies.
+ private final PropertyInvalidatedCache<Integer, Boolean> mIsUserUnlockingOrUnlockedCache =
+ new PropertyInvalidatedCache<Integer, Boolean>(
+ 32, CACHE_KEY_IS_USER_UNLOCKED_PROPERTY) {
+ @Override
+ protected Boolean recompute(Integer query) {
+ try {
+ return mService.isUserUnlockingOrUnlocked(query);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ };
+
/** {@hide} */
@UnsupportedAppUsage
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
@@ -2227,6 +2241,7 @@
/** {@hide} */
public void disableIsUserUnlockedCache() {
mIsUserUnlockedCache.disableLocal();
+ mIsUserUnlockingOrUnlockedCache.disableLocal();
}
/** {@hide} */
@@ -2263,11 +2278,7 @@
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
public boolean isUserUnlockingOrUnlocked(@UserIdInt int userId) {
- try {
- return mService.isUserUnlockingOrUnlocked(userId);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
+ return mIsUserUnlockingOrUnlockedCache.query(userId);
}
/**
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 25cb040..4c57570 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -19,6 +19,8 @@
import android.content.pm.DataLoaderParamsParcel;
import android.content.pm.IDataLoaderStatusListener;
import android.os.incremental.IncrementalNewFileParams;
+import android.os.incremental.IStorageHealthListener;
+import android.os.incremental.StorageHealthCheckParams;
/** @hide */
interface IIncrementalService {
@@ -34,7 +36,10 @@
* Opens or creates a storage given a target path and data loader params. Returns the storage ID.
*/
int openStorage(in @utf8InCpp String path);
- int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, in IDataLoaderStatusListener listener, int createMode);
+ int createStorage(in @utf8InCpp String path, in DataLoaderParamsParcel params, int createMode,
+ in IDataLoaderStatusListener statusListener,
+ in StorageHealthCheckParams healthCheckParams,
+ in IStorageHealthListener healthListener);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
diff --git a/core/java/android/os/incremental/IStorageHealthListener.aidl b/core/java/android/os/incremental/IStorageHealthListener.aidl
new file mode 100644
index 0000000..9f93ede
--- /dev/null
+++ b/core/java/android/os/incremental/IStorageHealthListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.os.incremental;
+
+/** @hide */
+oneway interface IStorageHealthListener {
+ /** OK status, no pending reads. */
+ const int HEALTH_STATUS_OK = 0;
+ /* Statuses depend on timeouts defined in StorageHealthCheckParams. */
+ /** Pending reads detected, waiting for params.blockedTimeoutMs to confirm blocked state. */
+ const int HEALTH_STATUS_READS_PENDING = 1;
+ /** There are reads pending for params.blockedTimeoutMs, waiting till
+ * params.unhealthyTimeoutMs to confirm unhealthy state. */
+ const int HEALTH_STATUS_BLOCKED = 2;
+ /** There are reads pending for params.unhealthyTimeoutMs>,
+ * marking storage as unhealthy. */
+ const int HEALTH_STATUS_UNHEALTHY = 3;
+
+ /** Health status callback. */
+ void onHealthStatus(in int storageId, in int status);
+}
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 958c7fb..863d86ef 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -65,7 +65,9 @@
public static IncrementalFileStorages initialize(Context context,
@NonNull File stageDir,
@NonNull DataLoaderParams dataLoaderParams,
- @Nullable IDataLoaderStatusListener dataLoaderStatusListener,
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener,
List<InstallationFileParcel> addedFiles) throws IOException {
// TODO(b/136132412): sanity check if session should not be incremental
IncrementalManager incrementalManager = (IncrementalManager) context.getSystemService(
@@ -75,9 +77,9 @@
throw new IOException("Failed to obtain incrementalManager.");
}
- final IncrementalFileStorages result =
- new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams,
- dataLoaderStatusListener);
+ final IncrementalFileStorages result = new IncrementalFileStorages(stageDir,
+ incrementalManager, dataLoaderParams, statusListener, healthCheckParams,
+ healthListener);
for (InstallationFileParcel file : addedFiles) {
if (file.location == LOCATION_DATA_APP) {
try {
@@ -100,7 +102,9 @@
private IncrementalFileStorages(@NonNull File stageDir,
@NonNull IncrementalManager incrementalManager,
@NonNull DataLoaderParams dataLoaderParams,
- @Nullable IDataLoaderStatusListener dataLoaderStatusListener) throws IOException {
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener) throws IOException {
try {
mStageDir = stageDir;
mIncrementalManager = incrementalManager;
@@ -117,10 +121,9 @@
mDefaultStorage.bind(stageDir.getAbsolutePath());
} else {
mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
- dataLoaderParams,
- dataLoaderStatusListener,
- IncrementalManager.CREATE_MODE_CREATE
- | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
+ dataLoaderParams, IncrementalManager.CREATE_MODE_CREATE
+ | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false,
+ statusListener, healthCheckParams, healthListener);
if (mDefaultStorage == null) {
throw new IOException(
"Couldn't create incremental storage at " + stageDir);
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 916edfa..c7f50c9 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -110,11 +110,15 @@
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
- @NonNull DataLoaderParams params, @Nullable IDataLoaderStatusListener listener,
+ @NonNull DataLoaderParams params,
@CreateMode int createMode,
- boolean autoStartDataLoader) {
+ boolean autoStartDataLoader,
+ @Nullable IDataLoaderStatusListener statusListener,
+ @Nullable StorageHealthCheckParams healthCheckParams,
+ @Nullable IStorageHealthListener healthListener) {
try {
- final int id = mService.createStorage(path, params.getData(), listener, createMode);
+ final int id = mService.createStorage(path, params.getData(), createMode,
+ statusListener, healthCheckParams, healthListener);
if (id < 0) {
return null;
}
diff --git a/core/java/android/os/incremental/StorageHealthCheckParams.aidl b/core/java/android/os/incremental/StorageHealthCheckParams.aidl
new file mode 100644
index 0000000..6839317
--- /dev/null
+++ b/core/java/android/os/incremental/StorageHealthCheckParams.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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.os.incremental;
+
+/**
+ * @hide
+ */
+parcelable StorageHealthCheckParams {
+ /** Timeouts of the oldest pending read.
+ * Valid values 0ms < blockedTimeoutMs < unhealthyTimeoutMs < storage page read timeout.
+ * Invalid values will disable health checking. */
+
+ /** To consider storage "blocked". */
+ int blockedTimeoutMs;
+ /** To consider storage "unhealthy". */
+ int unhealthyTimeoutMs;
+
+ /** After storage is marked "unhealthy", how often to check if it recovered.
+ * Valid value 1000ms < unhealthyMonitoringMs. */
+ int unhealthyMonitoringMs;
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e0bc764..1b19e12 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14363,6 +14363,21 @@
* @hide
*/
public static final String ADVANCED_BATTERY_USAGE_AMOUNT = "advanced_battery_usage_amount";
+
+ /**
+ * For 5G NSA capable devices, determines whether NR tracking indications are on
+ * when the screen is off.
+ *
+ * Values are:
+ * 0: off - All 5G NSA tracking indications are off when the screen is off.
+ * 1: extended - All 5G NSA tracking indications are on when the screen is off as long as
+ * the device is camped on 5G NSA (5G icon is showing in status bar).
+ * If the device is not camped on 5G NSA, tracking indications are off.
+ * 2: always on - All 5G NSA tracking indications are on whether the screen is on or off.
+ * @hide
+ */
+ public static final String NR_NSA_TRACKING_SCREEN_OFF_MODE =
+ "nr_nsa_tracking_screen_off_mode";
}
/**
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d20ffb3..9b5b882 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -136,6 +136,7 @@
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setParent(mRootSurface)
.setFormat(attrs.format)
+ .setBufferSize(getSurfaceWidth(attrs), getSurfaceHeight(attrs))
.setName(attrs.getTitle().toString());
final SurfaceControl sc = b.build();
@@ -242,13 +243,8 @@
WindowManager.LayoutParams attrs = state.mParams;
if (viewFlags == View.VISIBLE) {
- final Rect surfaceInsets = attrs.surfaceInsets;
- int width = surfaceInsets != null
- ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
- int height = surfaceInsets != null
- ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
-
- t.setBufferSize(sc, width, height).setOpaque(sc, isOpaque(attrs)).show(sc).apply();
+ t.setBufferSize(sc, getSurfaceWidth(attrs), getSurfaceHeight(attrs))
+ .setOpaque(sc, isOpaque(attrs)).show(sc).apply();
outSurfaceControl.copyFrom(sc);
} else {
t.hide(sc).apply();
@@ -444,4 +440,15 @@
public android.os.IBinder asBinder() {
return null;
}
+
+ private int getSurfaceWidth(WindowManager.LayoutParams attrs) {
+ final Rect surfaceInsets = attrs.surfaceInsets;
+ return surfaceInsets != null
+ ? attrs.width + surfaceInsets.left + surfaceInsets.right : attrs.width;
+ }
+ private int getSurfaceHeight(WindowManager.LayoutParams attrs) {
+ final Rect surfaceInsets = attrs.surfaceInsets;
+ return surfaceInsets != null
+ ? attrs.height + surfaceInsets.top + surfaceInsets.bottom : attrs.height;
+ }
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index f041ad9..8b0dd8a 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -101,6 +101,7 @@
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
+import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
@@ -2777,6 +2778,7 @@
@Override
public void onListRebuilt(ResolverListAdapter listAdapter) {
setupScrollListener();
+ maybeSetupGlobalLayoutListener();
ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
if (chooserListAdapter.getUserHandle()
@@ -2858,6 +2860,28 @@
});
}
+ private void maybeSetupGlobalLayoutListener() {
+ if (shouldShowTabs()) {
+ return;
+ }
+ final View recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
+ recyclerView.getViewTreeObserver()
+ .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Fixes an issue were the accessibility border disappears on list creation.
+ recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ final TextView titleView = findViewById(R.id.title);
+ if (titleView != null) {
+ titleView.setFocusable(true);
+ titleView.setFocusableInTouchMode(true);
+ titleView.requestFocus();
+ titleView.requestAccessibilityFocus();
+ }
+ }
+ });
+ }
+
@Override // ChooserListCommunicator
public boolean isSendAction(Intent targetIntent) {
if (targetIntent == null) {
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index 762895b..023197b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -695,6 +695,8 @@
}
optional Notification notification = 82;
+ optional SettingProto nr_nsa_tracking_screen_off_mode = 153 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
optional SettingProto nsd_on = 83 [ (android.privacy).dest = DEST_AUTOMATIC ];
message Ntp {
@@ -1060,5 +1062,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 153;
+ // Next tag = 154;
}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a205fcf..2dc3996 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4451,4 +4451,11 @@
<bool name="config_pdp_reject_enable_retry">false</bool>
<!-- pdp data reject retry delay in ms -->
<integer name="config_pdp_reject_retry_delay_ms">-1</integer>
+
+ <!-- Package name that is recognized as an actor for the packages listed in
+ @array/config_overlayableConfiguratorTargets. If an overlay targeting one of the listed
+ targets is signed with the same signature as the configurator, the overlay will be granted
+ the "actor" policy. -->
+ <string name="config_overlayableConfigurator" translatable="false" />
+ <string-array name="config_overlayableConfiguratorTargets" translatable="false" />
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 758a4f7..6ce25d4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4025,4 +4025,6 @@
<java-symbol type="string" name="config_pdp_reject_service_not_subscribed" />
<java-symbol type="string" name="config_pdp_reject_multi_conn_to_same_pdn_not_allowed" />
+ <java-symbol type="string" name="config_overlayableConfigurator" />
+ <java-symbol type="array" name="config_overlayableConfiguratorTargets" />
</resources>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index bdb6bcc..8d73f8a 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -409,6 +409,8 @@
<permission name="android.permission.ACCESS_TV_DESCRAMBLER" />
<permission name="android.permission.ACCESS_TV_TUNER" />
<permission name="android.permission.TUNER_RESOURCE_ACCESS" />
+ <!-- Permissions required for CTS test - TVInputManagerTest -->
+ <permission name="android.permission.TV_INPUT_HARDWARE" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 4aff3e5..b6c6cd0 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -113,7 +113,7 @@
get_canvas(canvasHandle)->restoreUnclippedLayer(saveCount, *paint);
}
-static bool restore(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
+static jboolean restore(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle) {
Canvas* canvas = get_canvas(canvasHandle);
if (canvas->getSaveCount() <= 1) {
return false; // cannot restore anymore
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 8a26296..9cffceb 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -102,7 +102,7 @@
/**
* Draw
*/
-static int draw(JNIEnv* env, jobject, jlong treePtr, jlong canvasPtr,
+static jint draw(JNIEnv* env, jobject, jlong treePtr, jlong canvasPtr,
jlong colorFilterPtr, jobject jrect, jboolean needsMirroring, jboolean canReuseCache) {
VectorDrawable::Tree* tree = reinterpret_cast<VectorDrawable::Tree*>(treePtr);
Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
diff --git a/libs/hwui/jni/android_util_PathParser.cpp b/libs/hwui/jni/android_util_PathParser.cpp
index df5e9cd..72995ef 100644
--- a/libs/hwui/jni/android_util_PathParser.cpp
+++ b/libs/hwui/jni/android_util_PathParser.cpp
@@ -39,18 +39,18 @@
}
}
-static long createEmptyPathData(JNIEnv*, jobject) {
+static jlong createEmptyPathData(JNIEnv*, jobject) {
PathData* pathData = new PathData();
return reinterpret_cast<jlong>(pathData);
}
-static long createPathData(JNIEnv*, jobject, jlong pathDataPtr) {
+static jlong createPathData(JNIEnv*, jobject, jlong pathDataPtr) {
PathData* pathData = reinterpret_cast<PathData*>(pathDataPtr);
PathData* newPathData = new PathData(*pathData);
return reinterpret_cast<jlong>(newPathData);
}
-static long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) {
+static jlong createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, jint strLength) {
const char* pathString = env->GetStringUTFChars(inputStr, NULL);
PathData* pathData = new PathData();
PathParser::ParseResult result;
@@ -65,7 +65,7 @@
}
}
-static bool interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr,
+static jboolean interpolatePathData(JNIEnv*, jobject, jlong outPathDataPtr, jlong fromPathDataPtr,
jlong toPathDataPtr, jfloat fraction) {
PathData* outPathData = reinterpret_cast<PathData*>(outPathDataPtr);
PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
@@ -79,7 +79,7 @@
delete pathData;
}
-static bool canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) {
+static jboolean canMorphPathData(JNIEnv*, jobject, jlong fromPathDataPtr, jlong toPathDataPtr) {
PathData* fromPathData = reinterpret_cast<PathData*>(fromPathDataPtr);
PathData* toPathData = reinterpret_cast<PathData*>(toPathDataPtr);
return VectorDrawableUtils::canMorph(*fromPathData, *toPathData);
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 508a46f4..1fbb672 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -111,4 +111,8 @@
// For preview channels and programs
void sendTvInputNotifyIntent(in Intent intent, int userId);
void requestChannelBrowsable(in Uri channelUri, int userId);
+
+ // For CTS purpose only. Add/remove a TvInputHardware device
+ void addHardwareDevice(in int deviceId);
+ void removeHardwareDevice(in int deviceId);
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index e701055..98a01a4 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -23,6 +23,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
@@ -1801,6 +1802,40 @@
executor, callback);
}
+ /**
+ * API to add a hardware device in the TvInputHardwareManager for CTS testing
+ * purpose.
+ *
+ * @param deviceId Id of the adding hardware device.
+ *
+ * @hide
+ */
+ @TestApi
+ public void addHardwareDevice(int deviceId) {
+ try {
+ mService.addHardwareDevice(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * API to remove a hardware device in the TvInputHardwareManager for CTS testing
+ * purpose.
+ *
+ * @param deviceId Id of the removing hardware device.
+ *
+ * @hide
+ */
+ @TestApi
+ public void removeHardwareDevice(int deviceId) {
+ try {
+ mService.removeHardwareDevice(deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private Hardware acquireTvInputHardwareInternal(int deviceId, TvInputInfo info,
String tvInputSessionId, int priorityHint,
Executor executor, final HardwareCallback callback) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index a458b16..8bf688d 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -447,7 +447,7 @@
private native DvrRecorder nativeOpenDvrRecorder(long bufferSize);
private native DvrPlayback nativeOpenDvrPlayback(long bufferSize);
- private static native DemuxCapabilities nativeGetDemuxCapabilities();
+ private native DemuxCapabilities nativeGetDemuxCapabilities();
private native int nativeCloseDemux(int handle);
private native int nativeCloseFrontend(int handle);
@@ -939,8 +939,7 @@
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
- filter.setMainType(mainType);
- filter.setSubtype(subType);
+ filter.setType(mainType, subType);
filter.setCallback(cb, executor);
if (mHandler == null) {
mHandler = createEventHandler();
@@ -1147,8 +1146,11 @@
}
/* package */ void releaseLnb() {
- mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
- mLnbHandle = null;
+ if (mLnbHandle != null) {
+ // LNB handle can be null if it's opened by name.
+ mTunerResourceManager.releaseLnb(mLnbHandle, mClientId);
+ mLnbHandle = null;
+ }
mLnb = null;
}
}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index cc932da..f0015b7 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -221,12 +221,9 @@
}
/** @hide */
- public void setMainType(@Type int mainType) {
+ public void setType(@Type int mainType, @Subtype int subtype) {
mMainType = mainType;
- }
- /** @hide */
- public void setSubtype(@Subtype int subtype) {
- mSubtype = subtype;
+ mSubtype = TunerUtils.getFilterSubtype(mainType, subtype);
}
/** @hide */
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index fb8c276..7e72140 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -2975,7 +2975,7 @@
jbyte *dst = env->GetByteArrayElements(buffer, &isCopy);
ALOGD("copyData, isCopy=%d", isCopy);
if (dst == nullptr) {
- ALOGD("Failed to GetByteArrayElements");
+ jniThrowRuntimeException(env, "Failed to GetByteArrayElements");
return 0;
}
@@ -2983,7 +2983,7 @@
env->ReleaseByteArrayElements(buffer, dst, 0);
flag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_CONSUMED));
} else {
- ALOGD("Failed to read FMQ");
+ jniThrowRuntimeException(env, "Failed to read FMQ");
env->ReleaseByteArrayElements(buffer, dst, 0);
return 0;
}
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
index 1dd0291..c0482e1 100644
--- a/packages/CarSystemUI/AndroidManifest.xml
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -25,6 +25,11 @@
<uses-permission android:name="android.car.permission.CAR_ENROLL_TRUST"/>
<!-- This permission is required to get bluetooth broadcast. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
+ <!-- These permissions are required to implement icons based on role holders. -->
+ <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"/>
+ <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS"/>
+ <!-- This permission is required to access app information from other users. -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<!-- This permission is required to check the foreground user id. -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
</manifest>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 1418bf8..2a715d0 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -125,6 +125,7 @@
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
+ systemui:useDefaultAppIconForRole="true"
/>
</LinearLayout>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index 837252b..ca4e76e 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -29,12 +29,14 @@
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_icon_image"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/car_navigation_button_icon_height"
android:layout_width="match_parent"
android:layout_gravity="center"
android:animateLayoutChanges="true"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
+ android:tintMode="src_in"
+ android:tint="@color/car_nav_icon_fill_color"
android:clickable="false"
/>
@@ -48,6 +50,7 @@
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:clickable="false"
+ android:visibility="gone"
/>
<ImageView
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
index a586763..7883764 100644
--- a/packages/CarSystemUI/res/values/attrs.xml
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -65,6 +65,10 @@
<attr name="showMoreWhenSelected" format="boolean" />
<!-- whether to highlight the button when selected. Defaults false -->
<attr name="highlightWhenSelected" format="boolean" />
+ <!-- whether to show the icon of the app currently associated this button's role. Only
+ relevant for buttons associated to specific roles (e.g.: AssistantButton).
+ Defaults false -->
+ <attr name="useDefaultAppIconForRole" format="boolean"/>
</declare-styleable>
<!-- Custom attributes to configure hvac values -->
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 0e84d51..d20ab49 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -21,7 +21,7 @@
<color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color>
<color name="car_user_switcher_add_user_background_color">#131313</color>
<color name="car_user_switcher_add_user_add_sign_color">@*android:color/car_body1_light</color>
- <color name="car_nav_icon_fill_color">#8Fffffff</color>
+ <color name="car_nav_icon_fill_color">#8F8F8F</color>
<color name="car_nav_icon_fill_color_selected">#ffffff</color>
<!-- colors for seekbar -->
<color name="car_seekbar_track_background">#131315</color>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index ed0b485..cb321cd 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -175,6 +175,7 @@
<dimen name="car_user_switcher_margin_top">@*android:dimen/car_padding_4</dimen>
<dimen name="car_navigation_button_width">64dp</dimen>
+ <dimen name="car_navigation_button_icon_height">44dp</dimen>
<dimen name="car_navigation_bar_width">760dp</dimen>
<dimen name="car_left_navigation_bar_width">96dp</dimen>
<dimen name="car_right_navigation_bar_width">96dp</dimen>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
index 69ec78e..ede4696 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/AssitantButton.java
@@ -18,6 +18,7 @@
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
@@ -31,7 +32,6 @@
* AssitantButton is a ui component that will trigger the Voice Interaction Service.
*/
public class AssitantButton extends CarNavigationButton {
-
private static final String TAG = "AssistantButton";
private final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
@@ -50,9 +50,7 @@
public AssitantButton(Context context, AttributeSet attrs) {
super(context, attrs);
mAssistUtils = new AssistUtils(context);
- setOnClickListener(v -> {
- showAssistant();
- });
+ setOnClickListener(v -> showAssistant());
}
private void showAssistant() {
@@ -65,4 +63,9 @@
protected void setUpIntents(TypedArray typedArray) {
// left blank because for the assistant button Intent will not be passed from the layout.
}
+
+ @Override
+ protected String getRoleName() {
+ return RoleManager.ROLE_ASSISTANT;
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
new file mode 100644
index 0000000..5c83c02
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/ButtonRoleHolderController.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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.car.navigationbar;
+
+import android.annotation.Nullable;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.car.CarDeviceProvisionedController;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Some CarNavigationButtons can be associated to a {@link RoleManager} role. When they are, it is
+ * possible to have them display the icon of the default application (role holder) for the given
+ * role.
+ *
+ * This class monitors the current role holders for each role type and updates the button icon for
+ * this buttons with have this feature enabled.
+ */
+@Singleton
+public class ButtonRoleHolderController {
+ private static final String TAG = "ButtonRoleHolderController";
+
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final RoleManager mRoleManager;
+ private final CarDeviceProvisionedController mDeviceController;
+ private final Map<String, CarNavigationButton> mButtonMap = new HashMap<>();
+ private final OnRoleHoldersChangedListener mListener = this::onRoleChanged;
+ private boolean mRegistered;
+
+ @Inject
+ public ButtonRoleHolderController(Context context, PackageManager packageManager,
+ RoleManager roleManager, CarDeviceProvisionedController deviceController) {
+ mContext = context;
+ mPackageManager = packageManager;
+ mRoleManager = roleManager;
+ mDeviceController = deviceController;
+ }
+
+ /**
+ * Iterate through a view looking for CarNavigationButton and add it to this controller if it
+ * opted to be associated with a {@link RoleManager} role type.
+ *
+ * @param v the View that may contain CarFacetButtons
+ */
+ void addAllButtonsWithRoleName(View v) {
+ if (v instanceof CarNavigationButton) {
+ CarNavigationButton button = (CarNavigationButton) v;
+ String roleName = button.getRoleName();
+ if (roleName != null && button.isDefaultAppIconForRoleEnabled()) {
+ addButtonWithRoleName(button, roleName);
+ }
+ } else if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ addAllButtonsWithRoleName(viewGroup.getChildAt(i));
+ }
+ }
+ }
+
+ private void addButtonWithRoleName(CarNavigationButton button, String roleName) {
+ mButtonMap.put(roleName, button);
+ updateIcon(roleName);
+ if (!mRegistered) {
+ mRoleManager.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
+ mListener, UserHandle.ALL);
+ mRegistered = true;
+ }
+ }
+
+ void removeAll() {
+ mButtonMap.clear();
+ if (mRegistered) {
+ mRoleManager.removeOnRoleHoldersChangedListenerAsUser(mListener, UserHandle.ALL);
+ mRegistered = false;
+ }
+ }
+
+ @VisibleForTesting
+ void onRoleChanged(String roleName, UserHandle user) {
+ if (RoleManager.ROLE_ASSISTANT.equals(roleName)
+ && user.getIdentifier() == mDeviceController.getCurrentUser()) {
+ updateIcon(roleName);
+ }
+ }
+
+ private void updateIcon(String roleName) {
+ CarNavigationButton button = mButtonMap.get(roleName);
+ if (button == null) {
+ return;
+ }
+ List<String> holders = mRoleManager.getRoleHoldersAsUser(button.getRoleName(),
+ UserHandle.of(mDeviceController.getCurrentUser()));
+ if (holders == null || holders.isEmpty()) {
+ button.setAppIcon(null);
+ } else {
+ button.setAppIcon(loadIcon(holders.get(0)));
+ }
+ }
+
+ @Nullable
+ private Drawable loadIcon(String packageName) {
+ try {
+ ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_ANY_USER);
+ return appInfo.loadIcon(mPackageManager);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(ButtonRoleHolderController.TAG, "Package not found: " + packageName, e);
+ return null;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
index 5c6472e..4c720ab 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBar.java
@@ -87,7 +87,6 @@
private final Executor mUiBgExecutor;
private final IStatusBarService mBarService;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
- private final ButtonSelectionStateController mButtonSelectionStateController;
private final Lazy<PhoneStatusBarPolicy> mIconPolicyLazy;
private final Lazy<StatusBarIconController> mIconControllerLazy;
@@ -139,7 +138,6 @@
@UiBackground Executor uiBgExecutor,
IStatusBarService barService,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
- ButtonSelectionStateController buttonSelectionStateController,
Lazy<PhoneStatusBarPolicy> iconPolicyLazy,
Lazy<StatusBarIconController> iconControllerLazy
) {
@@ -156,7 +154,6 @@
mUiBgExecutor = uiBgExecutor;
mBarService = barService;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
- mButtonSelectionStateController = buttonSelectionStateController;
mIconPolicyLazy = iconPolicyLazy;
mIconControllerLazy = iconControllerLazy;
@@ -280,10 +277,9 @@
* before and after the device is provisioned. . Also for change of density and font size.
*/
private void restartNavBars() {
- // remove and reattach all hvac components such that we don't keep a reference to unused
- // ui elements
- mCarNavigationBarController.removeAllFromHvac();
- mButtonSelectionStateController.removeAll();
+ // remove and reattach all components such that we don't keep a reference to unused ui
+ // elements
+ mCarNavigationBarController.removeAll();
if (mTopNavigationBarWindow != null) {
mTopNavigationBarWindow.removeAllViews();
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
index 288e5cf..ca780ae 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationBarController.java
@@ -37,6 +37,7 @@
private final Context mContext;
private final NavigationBarViewFactory mNavigationBarViewFactory;
private final ButtonSelectionStateController mButtonSelectionStateController;
+ private final ButtonRoleHolderController mButtonRoleHolderController;
private final Lazy<HvacController> mHvacControllerLazy;
private boolean mShowTop;
@@ -59,11 +60,13 @@
public CarNavigationBarController(Context context,
NavigationBarViewFactory navigationBarViewFactory,
ButtonSelectionStateController buttonSelectionStateController,
- Lazy<HvacController> hvacControllerLazy) {
+ Lazy<HvacController> hvacControllerLazy,
+ ButtonRoleHolderController buttonRoleHolderController) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
+ mButtonRoleHolderController = buttonRoleHolderController;
// Read configuration.
mShowTop = mContext.getResources().getBoolean(R.bool.config_enableTopNavigationBar);
@@ -101,9 +104,11 @@
mHvacControllerLazy.get().connectToCarService();
}
- /** Clean up hvac. */
- public void removeAllFromHvac() {
+ /** Clean up */
+ public void removeAll() {
mHvacControllerLazy.get().removeAllComponents();
+ mButtonSelectionStateController.removeAll();
+ mButtonRoleHolderController.removeAll();
}
/** Gets the top window if configured to do so. */
@@ -211,6 +216,7 @@
view.setStatusBarWindowTouchListener(statusBarTouchListener);
view.setNotificationsPanelController(notifShadeController);
mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
+ mButtonRoleHolderController.addAllButtonsWithRoleName(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
index 5f4ac2d..5e113d6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/navigationbar/CarNavigationButton.java
@@ -17,9 +17,11 @@
package com.android.systemui.car.navigationbar;
import android.app.ActivityOptions;
+import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
@@ -29,6 +31,7 @@
import android.widget.ImageView;
import android.widget.LinearLayout;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
@@ -62,6 +65,8 @@
private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
+ private Drawable mAppIcon;
+ private boolean mIsDefaultAppIconForRoleEnabled;
private String[] mComponentNames;
/** App categories that are to be used with this widget */
private String[] mButtonCategories;
@@ -92,7 +97,9 @@
super.setSelected(selected);
mSelected = selected;
if (mHighlightWhenSelected) {
- setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ // Always apply selected alpha if the button does not toggle alpha based on selection
+ // state.
+ setAlpha(!mHighlightWhenSelected || mSelected ? mSelectedAlpha : mUnselectedAlpha);
}
if (mShowMoreWhenSelected && mMoreIcon != null) {
mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
@@ -108,6 +115,20 @@
updateImage();
}
+ /**
+ * Sets the current icon of the default application associated with this button.
+ */
+ public void setAppIcon(Drawable appIcon) {
+ mAppIcon = appIcon;
+ updateImage();
+ }
+
+ /** Gets the icon of the app currently associated to the role of this button. */
+ @VisibleForTesting
+ protected Drawable getAppIcon() {
+ return mAppIcon;
+ }
+
/** Gets whether the icon is in an unseen state. */
public boolean getUnseen() {
return mHasUnseen;
@@ -144,6 +165,22 @@
}
/**
+ * Subclasses should override this method to return the {@link RoleManager} role associated
+ * with this button.
+ */
+ protected String getRoleName() {
+ return null;
+ }
+
+ /**
+ * @return true if this button should show the icon of the default application for the
+ * role returned by {@link #getRoleName()}.
+ */
+ protected boolean isDefaultAppIconForRoleEnabled() {
+ return mIsDefaultAppIconForRoleEnabled;
+ }
+
+ /**
* @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
* a display.
*/
@@ -240,7 +277,6 @@
};
}
-
/**
* Initializes view-related aspects of the button.
*/
@@ -256,28 +292,27 @@
R.styleable.CarNavigationButton_showMoreWhenSelected,
mShowMoreWhenSelected);
- mSelectedIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
mIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_icon, 0);
-
+ mSelectedIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+ mIsDefaultAppIconForRoleEnabled = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_useDefaultAppIconForRole, false);
mIcon = findViewById(R.id.car_nav_button_icon_image);
- mIcon.setScaleType(ImageView.ScaleType.CENTER);
// Always apply selected alpha if the button does not toggle alpha based on selection state.
mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
- mIcon.setImageResource(mIconResourceId);
-
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setAlpha(mSelectedAlpha);
- mMoreIcon.setVisibility(GONE);
-
mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
-
- mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ updateImage();
}
private void updateImage() {
- mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ if (mIsDefaultAppIconForRoleEnabled && mAppIcon != null) {
+ mIcon.setImageDrawable(mAppIcon);
+ } else {
+ mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ }
mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
}
diff --git a/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml b/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml
new file mode 100644
index 0000000..25ec2c1
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/button_role_holder_controller_test.xml
@@ -0,0 +1,41 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/assistant_role_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:useDefaultAppIconForRole="true"
+ />
+
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/assistant_role_disabled_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
index f0e0216..a8e83d6 100644
--- a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
@@ -25,7 +25,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_component_name"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -34,7 +34,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_category"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
@@ -43,7 +43,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/detectable_by_package"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
index b0ca8dc..94edc4b 100644
--- a/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_bar_view_test.xml
@@ -14,7 +14,7 @@
~ limitations under the License.
-->
-<com.android.systemui.navigationbar.car.CarNavigationBarView
+<com.android.systemui.car.navigationbar.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -32,7 +32,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -55,4 +55,4 @@
android:visibility="gone"
/>
-</com.android.systemui.navigationbar.car.CarNavigationBarView>
+</com.android.systemui.car.navigationbar.CarNavigationBarView>
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
index 576928c..44f8340 100644
--- a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
@@ -25,7 +25,7 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/default_no_selection_state"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -34,7 +34,7 @@
systemui:selectedIcon="@drawable/car_ic_overview_selected"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
@@ -44,7 +44,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/long_click_app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
@@ -54,7 +54,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/broadcast"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -63,7 +63,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/selected_icon_undefined"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -71,7 +71,7 @@
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/highlightable_no_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -81,7 +81,7 @@
systemui:highlightWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/not_highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -91,7 +91,7 @@
systemui:showMoreWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -102,7 +102,7 @@
systemui:showMoreWhenSelected="true"
/>
- <com.android.systemui.navigationbar.car.CarNavigationButton
+ <com.android.systemui.car.navigationbar.CarNavigationButton
android:id="@+id/broadcast"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
@@ -112,4 +112,13 @@
systemui:broadcast="true"
/>
+ <com.android.systemui.car.navigationbar.AssitantButton
+ android:id="@+id/role_based_button"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:useDefaultAppIconForRole="true"
+ systemui:highlightWhenSelected="true"
+ />
+
</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
new file mode 100644
index 0000000..a57736b
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/ButtonRoleHolderControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 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.car.navigationbar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+
+import android.app.role.RoleManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.car.CarDeviceProvisionedController;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ButtonRoleHolderControllerTest extends SysuiTestCase {
+ private static final String TEST_VALID_PACKAGE_NAME = "foo";
+ private static final String TEST_INVALID_PACKAGE_NAME = "bar";
+ private static final UserHandle TEST_CURRENT_USER = UserHandle.of(100);
+ private static final UserHandle TEST_NON_CURRENT_USER = UserHandle.of(101);
+
+ private LinearLayout mTestView;
+ private CarNavigationButton mNavButtonDefaultAppIconForRoleWithEnabled;
+ private CarNavigationButton mNavButtonDefaultAppIconForRoleWithDisabled;
+ private ButtonRoleHolderController mControllerUnderTest;
+ private Drawable mAppIcon;
+
+ @Mock
+ private RoleManager mRoleManager;
+ @Mock
+ private CarDeviceProvisionedController mDeviceProvisionedController;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ApplicationInfo mApplicationInfo;
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ MockitoAnnotations.initMocks(this);
+
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.button_role_holder_controller_test, /* root= */ null);
+ mNavButtonDefaultAppIconForRoleWithEnabled = mTestView
+ .findViewById(R.id.assistant_role_button);
+ mNavButtonDefaultAppIconForRoleWithDisabled = mTestView
+ .findViewById(R.id.assistant_role_disabled_button);
+ mAppIcon = mContext.getDrawable(R.drawable.car_ic_apps);
+ when(mApplicationInfo.loadIcon(any())).thenReturn(mAppIcon);
+ doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager)
+ .getApplicationInfo(any(), anyInt());
+ doReturn(mApplicationInfo).when(mPackageManager)
+ .getApplicationInfo(eq(TEST_VALID_PACKAGE_NAME), anyInt());
+ when(mDeviceProvisionedController
+ .getCurrentUser())
+ .thenReturn(TEST_CURRENT_USER.getIdentifier());
+ mControllerUnderTest = new ButtonRoleHolderController(mContext,
+ mPackageManager, mRoleManager, mDeviceProvisionedController);
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_roleAssigned_appIconEnabled_useAssignedAppIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isEqualTo(mAppIcon);
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_roleUnassigned_appIconEnabled_useDefaultIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void onRoleChanged_currentUser_appIconEnabled_useAssignedAppIcon() {
+ when(mRoleManager.getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_CURRENT_USER);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isEqualTo(mAppIcon);
+ }
+
+ @Test
+ public void onRoleChanged_nonCurrentUser_appIconEnabled_iconIsNotUpdated() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ Drawable beforeIcon = mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon();
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_NON_CURRENT_USER);
+
+ Drawable afterIcon = mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon();
+ assertThat(afterIcon).isEqualTo(beforeIcon);
+ }
+
+ @Test
+ public void onRoleChanged_invalidPackage_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_INVALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithEnabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void addAllButtonsWithRoleName_appIconDisabled_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ }
+
+ @Test
+ public void onRoleChanged_roleAssigned_appIconDisabled_useDefaultIcon() {
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(null);
+ mControllerUnderTest.addAllButtonsWithRoleName(mTestView);
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ when(mRoleManager
+ .getRoleHoldersAsUser(eq(RoleManager.ROLE_ASSISTANT), any()))
+ .thenReturn(List.of(TEST_VALID_PACKAGE_NAME));
+
+ mControllerUnderTest.onRoleChanged(RoleManager.ROLE_ASSISTANT, TEST_CURRENT_USER);
+
+ assertThat(mNavButtonDefaultAppIconForRoleWithDisabled.getAppIcon()).isNull();
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
index 911f624..e84e42c 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarControllerTest.java
@@ -53,6 +53,8 @@
@Mock
private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
+ private ButtonRoleHolderController mButtonRoleHolderController;
+ @Mock
private HvacController mHvacController;
@Before
@@ -66,10 +68,15 @@
mDependency.injectMockDependency(StatusBarIconController.class);
}
+ private CarNavigationBarController createNavigationBarController() {
+ return new CarNavigationBarController(mContext, mNavigationBarViewFactory,
+ mButtonSelectionStateController, () -> mHvacController,
+ mButtonRoleHolderController);
+ }
+
@Test
public void testConnectToHvac_callsConnect() {
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.connectToHvac();
@@ -77,20 +84,37 @@
}
@Test
- public void testRemoveAllFromHvac_callsRemoveAll() {
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ public void testRemoveAll_callsHvacControllerRemoveAllComponents() {
+ mCarNavigationBar = createNavigationBarController();
- mCarNavigationBar.removeAllFromHvac();
+ mCarNavigationBar.removeAll();
verify(mHvacController).removeAllComponents();
}
+
+ @Test
+ public void testRemoveAll_callsButtonRoleHolderControllerRemoveAll() {
+ mCarNavigationBar = createNavigationBarController();
+
+ mCarNavigationBar.removeAll();
+
+ verify(mButtonRoleHolderController).removeAll();
+ }
+
+ @Test
+ public void testRemoveAll_callsButtonSelectionStateControllerRemoveAll() {
+ mCarNavigationBar = createNavigationBarController();
+
+ mCarNavigationBar.removeAll();
+
+ verify(mButtonSelectionStateController).removeAll();
+ }
+
@Test
public void testGetTopWindow_topDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getTopWindow();
@@ -100,8 +124,7 @@
@Test
public void testGetTopWindow_topEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getTopWindow();
@@ -111,8 +134,7 @@
@Test
public void testGetTopWindow_topEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableTopNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getTopWindow();
ViewGroup window2 = mCarNavigationBar.getTopWindow();
@@ -123,8 +145,7 @@
@Test
public void testGetBottomWindow_bottomDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -134,8 +155,7 @@
@Test
public void testGetBottomWindow_bottomEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -145,8 +165,7 @@
@Test
public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getBottomWindow();
ViewGroup window2 = mCarNavigationBar.getBottomWindow();
@@ -157,8 +176,7 @@
@Test
public void testGetLeftWindow_leftDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
assertThat(window).isNull();
}
@@ -166,8 +184,7 @@
@Test
public void testGetLeftWindow_leftEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
@@ -177,8 +194,7 @@
@Test
public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getLeftWindow();
ViewGroup window2 = mCarNavigationBar.getLeftWindow();
@@ -189,8 +205,7 @@
@Test
public void testGetRightWindow_rightDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -200,8 +215,7 @@
@Test
public void testGetRightWindow_rightEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -211,8 +225,7 @@
@Test
public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window1 = mCarNavigationBar.getRightWindow();
ViewGroup window2 = mCarNavigationBar.getRightWindow();
@@ -223,8 +236,7 @@
@Test
public void testSetBottomWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
@@ -235,8 +247,7 @@
@Test
public void testSetBottomWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.GONE);
@@ -247,8 +258,7 @@
@Test
public void testSetLeftWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
@@ -259,8 +269,7 @@
@Test
public void testSetLeftWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.GONE);
@@ -271,8 +280,7 @@
@Test
public void testSetRightWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
@@ -283,8 +291,7 @@
@Test
public void testSetRightWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.GONE);
@@ -295,8 +302,7 @@
@Test
public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
@@ -310,8 +316,7 @@
@Test
public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
@@ -323,8 +328,7 @@
@Test
public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationBarController.NotificationsShadeController controller =
@@ -340,8 +344,7 @@
@Test
public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
mCarNavigationBar.registerNotificationController(
mock(CarNavigationBarController.NotificationsShadeController.class));
@@ -355,8 +358,7 @@
@Test
public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -368,8 +370,7 @@
@Test
public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -381,8 +382,7 @@
@Test
public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -396,8 +396,7 @@
@Test
public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -411,8 +410,7 @@
@Test
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
@@ -426,8 +424,7 @@
@Test
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
- mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- mButtonSelectionStateController, () -> mHvacController);
+ mCarNavigationBar = createNavigationBarController();
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
index 04e0a73..0caa86f 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationBarTest.java
@@ -89,6 +89,8 @@
@Mock
private ButtonSelectionStateListener mButtonSelectionStateListener;
@Mock
+ private ButtonRoleHolderController mButtonRoleHolderController;
+ @Mock
private IStatusBarService mBarService;
@Mock
private KeyguardStateController mKeyguardStateController;
@@ -137,8 +139,8 @@
mCarNavigationBarController, mLightBarController, mStatusBarIconController,
mWindowManager, mDeviceProvisionedController, new CommandQueue(mContext),
mAutoHideController, mButtonSelectionStateListener, mHandler, mUiBgExecutor,
- mBarService, () -> mKeyguardStateController, mButtonSelectionStateController,
- () -> mIconPolicy, () -> mIconController);
+ mBarService, () -> mKeyguardStateController, () -> mIconPolicy,
+ () -> mIconController);
}
@Test
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
index 11f2fa4..54282d3 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/navigationbar/CarNavigationButtonTest.java
@@ -180,6 +180,56 @@
}
@Test
+ public void onUnselected_withAppIcon_showsAppIcon() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+ Drawable appIcon = getContext().getDrawable(R.drawable.ic_android);
+
+ roleBasedButton.setSelected(false);
+ roleBasedButton.setAppIcon(appIcon);
+
+ Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(currentDrawable).isEqualTo(appIcon);
+ }
+
+ @Test
+ public void onUnselected_withAppIcon_applyUnselectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+
+ roleBasedButton.setSelected(false);
+ roleBasedButton.setAppIcon(getContext().getDrawable(R.drawable.ic_android));
+
+ assertThat(roleBasedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_withAppIcon_showsAppIconWithSelectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+ Drawable appIcon = getContext().getDrawable(R.drawable.ic_android);
+
+ roleBasedButton.setSelected(true);
+ roleBasedButton.setAppIcon(appIcon);
+
+ Drawable currentDrawable = ((AlphaOptimizedImageButton) roleBasedButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(currentDrawable).isEqualTo(appIcon);
+ }
+
+ @Test
+ public void onSelected_withAppIcon_applySelectedAlpha() {
+ CarNavigationButton roleBasedButton = mTestView.findViewById(R.id.role_based_button);
+
+ roleBasedButton.setSelected(true);
+ roleBasedButton.setAppIcon(getContext().getDrawable(R.drawable.ic_android));
+
+ assertThat(roleBasedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
public void onClick_launchesIntentActivity() {
mDefaultButton.performClick();
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 66787c2..35e2295 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Geaktiveer"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Jou toestel moet herselflaai om hierdie verandering toe te pas. Herselflaai nou of kanselleer."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Werk-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Bedraade oorfoon"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index ead0ebf..25cea4d 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ነቅቷል"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"የእርስዎን መሣሪያ ይህ ለው ለማመልከት እንደገና መነሣት አለበት። አሁን እንደገና ያስነሡ ወይም ይተዉት።"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"የስራ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ባለገመድ ጆሮ ማዳመጫ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index c1f4fd9..bd94c40 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -558,6 +558,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"مفعّل"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"يجب إعادة تشغيل جهازك ليتم تطبيق هذا التغيير. يمكنك إعادة التشغيل الآن أو إلغاء التغيير."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> المخصّص للعمل"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"سمّاعة رأس سلكية"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index b5d642a..5572994 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"সক্ষম কৰা আছে"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই সলনিটো কার্যকৰী হ’বলৈ আপোনাৰ ডিভাইচটো ৰিবুট কৰিবই লাগিব। এতিয়াই ৰিবুট কৰক অথবা বাতিল কৰক।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"কৰ্মস্থান <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"তাঁৰযুক্ত হেডফ\'ন"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index aecaf70..39ab078 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu dəyişikliyin tətbiq edilməsi üçün cihaz yenidən başladılmalıdır. İndi yenidən başladın və ya ləğv edin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"İş <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Simli qulaqlıq"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 092bd42..78fd0bf 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate da restartujete uređaj da bi se ova promena primenila. Restartujte ga odmah ili otkažite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index d610973..30a9e0e 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Уключана"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Перазагрузіце прыладу, каб прымяніць гэта змяненне. Перазагрузіце ці скасуйце."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (праца)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Правадныя навушнікі"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index 5c3fe75..c8dd82f 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Активирано"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да бъде приложена тази промяна, устройството ви трябва да бъде рестартирано. Рестартирайте сега или анулирайте."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за работа"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Слушалки с кабел"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 27d3707..02ca679 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"চালু করা আছে"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"এই পরিবর্তনটি প্রয়োগ করার জন্য আপনার ডিভাইসটি অবশ্যই রিবুট করতে হবে। এখন রিবুট করুন বা বাতিল করুন।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"অফিস <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"তার যুক্ত হেডফোন"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index a9d9e70..785460f 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Morate ponovo pokrenuti uređaj da se ova promjena primijeni. Ponovo pokrenite odmah ili otkažite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Poslovna aplikacija <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 5ed0f1b..50c0374 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Has de reiniciar el teu dispositiu perquè s\'apliquin els canvis. Reinicia\'l ara o cancel·la."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de la feina"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculars amb cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index d4d3735..9fb63ea 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuto"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aby se tato změna projevila, je třeba zařízení restartovat. Restartujte zařízení nebo zrušte akci."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovní <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kabelová sluchátka"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 89dbf25..dc4f873 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiveret"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Din enhed skal genstartes for at denne enhed bliver anvendt. Genstart nu, eller annuller."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> – arbejde"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Høretelefoner med ledning"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index c2030b1..bee2d79 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiviert"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Damit diese Änderung übernommen wird, musst du dein Gerät neu starten. Du kannst es jetzt neu starten oder den Vorgang abbrechen."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (geschäftlich)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kabelgebundene Kopfhörer"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index bfc183e..b1d07ac 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ενεργή"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Για να εφαρμοστεί αυτή η αλλαγή, θα πρέπει να επανεκκινήσετε τη συσκευή σας. Επανεκκίνηση τώρα ή ακύρωση."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Εργασία <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Ενσύρματα ακουστικά"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 9d869ba..e51456a 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 7765824..34ff568 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Enabled"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Your device must be rebooted for this change to apply. Reboot now or cancel."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Work <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index 6a76beb..fb5e9d7 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -153,7 +153,7 @@
<string name="unknown" msgid="3544487229740637809">"Desconocido"</string>
<string name="running_process_item_user_label" msgid="3988506293099805796">"Usuario: <xliff:g id="USER_NAME">%1$s</xliff:g>"</string>
<string name="launch_defaults_some" msgid="3631650616557252926">"Configuraciones predeterminadas establecidas"</string>
- <string name="launch_defaults_none" msgid="8049374306261262709">"No se establecieron configuraciones predeterminadas"</string>
+ <string name="launch_defaults_none" msgid="8049374306261262709">"Sin configuraciones predeterminadas"</string>
<string name="tts_settings" msgid="8130616705989351312">"Configuración de texto a voz"</string>
<string name="tts_settings_title" msgid="7602210956640483039">"Salida de texto a voz"</string>
<string name="tts_default_rate_title" msgid="3964187817364304022">"Velocidad de voz"</string>
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Debes reiniciar el dispositivo para que se aplique el cambio. Reinícialo ahora o cancela la acción."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index 010b85b..a6a89dd 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Habilitado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Es necesario reiniciar tu dispositivo para que se apliquen los cambios. Reiniciar ahora o cancelar."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabajo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index ad2a561..65f1043 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Lubatud"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Selle muudatuse rakendamiseks tuleb seade taaskäivitada. Taaskäivitage kohe või tühistage."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Töö: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Juhtmega kõrvaklapid"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 1ceb738..6864db1 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Gaituta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Aldaketa aplikatzeko, berrabiarazi egin behar da gailua. Berrabiaraz ezazu orain, edo utzi bertan behera."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Laneko <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Entzungailu kableduna"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index d3f1c56..5e13c1b 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"برای اعمال این تغییر، دستگاهتان باید راهاندازی مجدد شود. اکنون راهاندازی مجدد کنید یا لغو کنید."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> محل کار"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"هدفون سیمی"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 5ab01c4b..1ec71c7 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Käytössä"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Laitteesi on käynnistettävä uudelleen, jotta muutos tulee voimaan. Käynnistä uudelleen nyt tai peruuta."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (työ)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Langalliset kuulokkeet"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index 26e5042..0a32ca5 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Votre appareil doit être redémarré pour que ce changement prenne effet. Redémarrez-le maintenant ou annulez la modification."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Écouteurs filaires"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index b8fc50d..bb069b5 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activé"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Vous devez redémarrer l\'appareil pour que cette modification soit appliquée. Redémarrez maintenant ou annulez l\'opération."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (travail)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Casque filaire"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index 8dc58ef..a27f2c4 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necesario reiniciar o teu dispositivo para aplicar este cambio. Reiníciao agora ou cancela o cambio."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Aplicación <xliff:g id="APP_NAME">%s</xliff:g> do traballo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auriculares con cable"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index 9f69a2c..cf1e5ee 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ચાલુ છે"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"આ ફેરફારને લાગુ કરવા માટે તમારા ડિવાઇસને રીબૂટ કરવાની જરૂર છે. હમણાં જ રીબૂટ કરો કે રદ કરો."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ઑફિસ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"વાયરવાળો હૅડફોન"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index dc8e28b..89b7f67 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"चालू है"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"बदली गई सेटिंग को लागू करने के लिए, अपने डिवाइस को फिर से चालू करें. डिवाइस को फिर से चालू करें या रद्द करें."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ऑफ़िस वाला <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"वायर वाला हेडफ़ोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index f887839..f57a302 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogućeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Uređaj se mora ponovno pokrenuti da bi se ta promjena primijenila. Ponovo pokrenite uređaj odmah ili odustanite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za posao"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žičane slušalice"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index d7d2694..e8699dc 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Engedélyezve"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Az eszközt újra kell indítani, hogy a módosítás megtörténjen. Indítsa újra most, vagy vesse el a módosítást."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Munkahelyi <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vezetékes fejhallgató"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index a5ef6b5..3f2b414 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Միացված է"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Սարքն անհրաժեշտ է վերագործարկել, որպեսզի փոփոխությունը կիրառվի։ Վերագործարկեք հիմա կամ չեղարկեք փոփոխությունը։"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Աշխատանքային <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Լարով ականջակալ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 04ca2f6..dd1f999 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktif"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Perangkat Anda harus di-reboot agar perubahan ini diterapkan. Reboot sekarang atau batalkan."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> kerja"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Headphone berkabel"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index ce60eb5..5dad63d 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Virkt"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Endurræsa þarf tækið til að þessi breyting taki gildi. Endurræstu núna eða hættu við."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> í vinnu"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Heyrnartól með snúru"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index bef644b..02b8533 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Attivo"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Devi riavviare il dispositivo per applicare questa modifica. Riavvia ora o annulla."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> di lavoro"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Cuffie con cavo"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index ed79632..af6a870 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"מופעל"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"צריך להפעיל מחדש את המכשיר כדי להחיל את השינוי. יש להפעיל מחדש עכשיו או לבטל."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> של עבודה"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"אוזניות עם חוט"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index 3f08a6a..080c1a9 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"有効"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"この変更を適用するには、デバイスの再起動が必要です。今すぐ再起動してください。キャンセルすることもできます。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"仕事の<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線ヘッドフォン"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index 2ebc3ee..23de5b2 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ჩართული"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ამ ცვლილების ასამოქმედებლად თქვენი მოწყობილობა უნდა გადაიტვირთოს. გადატვირთეთ ახლავე ან გააუქმეთ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"სამსახურის <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"სადენიანი ყურსასმენი"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index 91e5488..821c566 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Қосулы"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бұл өзгеріс күшіне енуі үшін, құрылғыны қайта жүктеу керек. Қазір қайта жүктеңіз не бас тартыңыз."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (жұмыс)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Сымды құлақаспап"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index 143f5f9..f93ab25 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"បានបើក"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ត្រូវតែចាប់ផ្ដើមឧបករណ៍របស់អ្នកឡើងវិញ ទើបការផ្លាស់ប្ដូរនេះត្រូវបានអនុវត្ត។ ចាប់ផ្ដើមឡើងវិញឥឡូវនេះ ឬបោះបង់។"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> សម្រាប់ការងារ"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"កាសមានខ្សែ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index 1df141c..e418779 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ಈ ಬದಲಾವಣೆ ಅನ್ವಯವಾಗಲು ನಿಮ್ಮ ಸಾಧನವನ್ನು ರೀಬೂಟ್ ಮಾಡಬೇಕು. ಇದೀಗ ರೀಬೂಟ್ ಮಾಡಿ ಅಥವಾ ರದ್ದುಗೊಳಿಸಿ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ಉದ್ಯೋಗ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ವೈಯರ್ ಹೊಂದಿರುವ ಹೆಡ್ಫೋನ್"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 86e1650..6b273a3 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"사용 설정됨"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"변경사항을 적용하려면 기기를 재부팅해야 합니다. 지금 재부팅하거나 취소하세요."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"직장용 <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"유선 헤드폰"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 925f4a7..d035362 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Күйүк"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Бул өзгөртүүнү колдонуу үчүн түзмөктү өчүрүп күйгүзүңүз. Азыр өчүрүп күйгүзүңүз же жокко чыгарыңыз."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Жумуш <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Зымдуу гарнитура"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 3bf1996..e188f3d 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ເປີດການນຳໃຊ້ແລ້ວ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ທ່ານຕ້ອງປິດເປີດອຸປະກອນຄືນໃໝ່ເພື່ອນຳໃຊ້ການປ່ຽນແປງນີ້. ປິດເປີດໃໝ່ດຽວນີ້ ຫຼື ຍົກເລີກ."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ບ່ອນເຮັດວຽກ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ຫູຟັງແບບມີສາຍ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 153c995..075032c 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Įgalinta"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kad pakeitimas būtų pritaikytas, įrenginį reikia paleisti iš naujo. Dabar paleiskite iš naujo arba atšaukite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbo „<xliff:g id="APP_NAME">%s</xliff:g>“"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Laidinės ausinės"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index f6e7f35..c37811f 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Iespējots"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Lai šīs izmaiņas tiktu piemērotas, nepieciešama ierīces atkārtota palaišana. Atkārtoti palaidiet to tūlīt vai atceliet izmaiņas."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Darbā: <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vadu austiņas"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index d55f1af..1f5908c 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Овозможено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"За да се примени променава, уредот мора да се рестартира. Рестартирајте сега или откажете."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Работна <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Жичени слушалки"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index c5267d6..6ee9777 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"പ്രവർത്തനക്ഷമമാക്കി"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ഈ മാറ്റം ബാധകമാകുന്നതിന് നിങ്ങളുടെ ഉപകരണം റീബൂട്ട് ചെയ്യേണ്ടതുണ്ട്. ഇപ്പോൾ റീബൂട്ട് ചെയ്യുകയോ റദ്ദാക്കുകയോ ചെയ്യുക."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ഔദ്യോഗികം <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"വയർ മുഖേന ബന്ധിപ്പിച്ച ഹെഡ്ഫോൺ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index 0a01f84..29647df 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Идэвхжүүлсэн"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Энэ өөрчлөлтийг хэрэгжүүлэхийн тулд таны төхөөрөмжийг дахин асаах ёстой. Одоо дахин асаах эсвэл болино уу."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ажлын <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Утастай чихэвч"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 075c748..99bf1e8 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सुरू केले आहे"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"हा बदल लागू करण्यासाठी तुमचे डिव्हाइस रीबूट करणे आवश्यक आहे. आता रीबूट करा किंवा रद्द करा."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्य <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"वायर असलेला हेडफोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index cdf32a7..bdecf64 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Didayakan"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Peranti anda mesti dibut semula supaya perubahan ini berlaku. But semula sekarang atau batalkan."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Kerja <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fon kepala berwayar"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index 2bd3b45..1519380 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ဖွင့်ထားသည်"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ဤအပြောင်းအလဲ ထည့်သွင်းရန် သင့်စက်ကို ပြန်လည်စတင်ရမည်။ ယခု ပြန်လည်စတင်ပါ သို့မဟုတ် ပယ်ဖျက်ပါ။"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"အလုပ် <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ကြိုးတပ်နားကြပ်"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index de5bed6..a2f862e 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Slått på"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten din må startes på nytt for at denne endringen skal tre i kraft. Start på nytt nå eller avbryt."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Jobb-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Hodetelefoner med kabel"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index bfe295a..159050e 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"सक्षम पारिएको छ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"यो परिवर्तन लागू गर्न तपाईंको यन्त्र अनिवार्य रूपमा रिबुट गर्नु पर्छ। अहिले रिबुट गर्नुहोस् वा रद्द गर्नुहोस्।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"कार्यालयको प्रोफाइल <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"तारसहितको हेडफोन"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index a822719..7e3005b 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ingeschakeld"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Je apparaat moet opnieuw worden opgestart om deze wijziging toe te passen. Start nu opnieuw op of annuleer de wijziging."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> voor werk"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Bedrade hoofdtelefoon"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index ab9fbc3..19e250b 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ସକ୍ଷମ କରାଯାଇଛି"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ଏହି ପରିବର୍ତ୍ତନ ଲାଗୁ କରିବା ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିଶ୍ଚିତ ରୂପେ ରିବୁଟ୍ କରାଯିବା ଆବଶ୍ୟକ। ବର୍ତ୍ତମାନ ରିବୁଟ୍ କରନ୍ତୁ କିମ୍ବା ବାତିଲ୍ କରନ୍ତୁ।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ୱାର୍କ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ତାରଯୁକ୍ତ ହେଡଫୋନ୍"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 35d8cba..184f4cd 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ਇਸ ਤਬਦੀਲੀ ਨੂੰ ਲਾਗੂ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਰੀਬੂਟ ਕਰਨਾ ਲਾਜ਼ਮੀ ਹੈ। ਹੁਣੇ ਰੀਬੂਟ ਕਰੋ ਜਾਂ ਰੱਦ ਕਰੋ।"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ਕੰਮ <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"ਤਾਰ ਵਾਲੇ ਹੈੱਡਫ਼ੋਨ"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index 54ed131..eebea5f 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Włączono"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Wprowadzenie zmiany wymaga ponownego uruchomienia urządzenia. Uruchom ponownie teraz lub anuluj."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (do pracy)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Słuchawki przewodowe"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index c6dc1d3..142ef47 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fones de ouvido com fio"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 999e684..0c6f488 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativada"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reiniciar o dispositivo para aplicar esta alteração. Reinicie agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Auscultadores com fios"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index c6dc1d3..142ef47 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Ativado"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"É necessário reinicializar o dispositivo para que a mudança seja aplicada. Faça isso agora ou cancele."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"App <xliff:g id="APP_NAME">%s</xliff:g> de trabalho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Fones de ouvido com fio"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index ba2f36f..036cf83 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Activat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pentru ca modificarea să se aplice, trebuie să reporniți dispozitivul. Reporniți-l acum sau anulați."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> de serviciu"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Căști cu fir"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 8ec3875..6b39754 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Включено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Чтобы изменение вступило в силу, необходимо перезапустить устройство. Вы можете сделать это сейчас или позже."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Рабочее приложение \"<xliff:g id="APP_NAME">%s</xliff:g>\""</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Проводные наушники"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 01c3634..9074c63 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"සබලයි"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"මෙම වෙනස යෙදීමට ඔබේ උපාංගය නැවත පණ ගැන්විය යුතුය. දැන් නැවත පණ ගන්වන්න හෝ අවලංගු කරන්න."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"කාර්යාල <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"රැහැන්ගත කළ හෙඩ්ෆෝන්"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index b5cbf43..cd3109f 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Zapnuté"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Táto zmena sa uplatní až po reštartovaní zariadenia. Zariadenie reštartujte alebo zmenu zrušte."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Pracovná aplikácia <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Slúchadlá s káblom"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index 86cccb8..0e492c7 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Omogočeno"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Napravo je treba znova zagnati, da bo ta sprememba uveljavljena. Znova zaženite zdaj ali prekličite."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> za delo"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Žične slušalke"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index bd3353c..4a80bcd 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiv"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Pajisja jote duhet të riniset që ky ndryshim të zbatohet. Rinise tani ose anuloje."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> për punën"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kufje me tela"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index 29f23b4..321645e 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -555,6 +555,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Омогућено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Морате да рестартујете уређај да би се ова промена применила. Рестартујте га одмах или откажите."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> за посао"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Жичане слушалице"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index a40100e..cd28c86b 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Aktiverat"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Enheten måste startas om för att ändringen ska börja gälla. Starta om nu eller avbryt."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> för arbetet"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Hörlurar med sladd"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index fedf1c8..2bc038e 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Imewashwa"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Ni lazima uwashe tena kifaa chako ili mabadiliko haya yatekelezwe. Washa tena sasa au ughairi."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ya kazini <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Vipokea sauti vyenye waya vinavyobanwa kichwani"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 345f0a7..a28fdce 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"இயக்கப்பட்டது"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"இந்த மாற்றங்கள் செயல்படுத்தப்பட உங்கள் சாதனத்தை மறுபடி தொடங்க வேண்டும். இப்போதே மறுபடி தொடங்கவும் அல்லது ரத்துசெய்யவும்."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"பணியிடம் <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"வயருள்ள ஹெட்ஃபோன்"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c4fda01..b7bb6e8 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"ఎనేబుల్ చేయబడింది"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"ఈ మార్పును వర్తింపజేయాలంటే మీరు మీ పరికరాన్ని తప్పనిసరిగా రీబూట్ చేయాలి. ఇప్పుడే రీబూట్ చేయండి లేదా రద్దు చేయండి."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"ఆఫీసు <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"వైర్ ఉన్న హెడ్ఫోన్"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index a9032eb..2f9ac46 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"เปิดใช้"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"คุณต้องรีบูตอุปกรณ์เพื่อให้การเปลี่ยนแปลงนี้มีผล รีบูตเลยหรือยกเลิก"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> ในโปรไฟล์งาน"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"หูฟังแบบมีสาย"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 712c06b..5064c1e 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Na-enable"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Dapat i-reboot ang iyong device para mailapat ang pagbabagong ito. Mag-reboot ngayon o kanselahin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> sa Trabaho"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Wired na headphone"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 8818498..454ca3b 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Etkin"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bu değişikliğin geçerli olması için cihazının yeniden başlatılması gerekir. Şimdi yeniden başlatın veya iptal edin."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> (İş)"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Kablolu kulaklık"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index f6568e3..7e969f7 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -556,6 +556,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Увімкнено"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Щоб застосувати ці зміни, перезапустіть пристрій. Перезапустіть пристрій або скасуйте зміни."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Робочий додаток <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Дротові навушники"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index f13f8fb..e3b28c7 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"فعال"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"اس تبدیلی کو لاگو کرنے کے ليے آپ کے آلہ کو ریبوٹ کرنا ضروری ہے۔ ابھی ریبوٹ کریں یا منسوخ کریں۔"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"دفتر <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"وائرڈ ہیڈ فون"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 7b8f627..04707c5 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Yoniq"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Oʻzgarishlar qurilma oʻchib yonganda bajariladi. Hoziroq oʻchib yoqish yoki bekor qilish."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Ish <xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Simli quloqlik"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 35cecc1..e7fbf46 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Đã bật"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Bạn phải khởi động lại thiết bị để áp dụng sự thay đổi này. Hãy khởi động lại ngay hoặc hủy."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"<xliff:g id="APP_NAME">%s</xliff:g> dành cho công việc"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Tai nghe có dây"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index c69ea2d..bda7d8b 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已启用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"设备必须重新启动才能应用此更改。您可以立即重新启动或取消。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作资料中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有线耳机"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index b0324d9..841de63 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"您的裝置必須重新開機,才能套用此變更。請立即重新開機或取消。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作設定檔入面嘅「<xliff:g id="APP_NAME">%s</xliff:g>」"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線耳機"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index 0574c7e..a6c1647 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"已啟用"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"裝置必須重新啟動才能套用這項變更。請立即重新啟動或取消變更。"</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"工作資料夾中的<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"有線耳機"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index b60553a..c437580 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -554,6 +554,5 @@
<string name="cached_apps_freezer_enabled" msgid="8866703500183051546">"Inikwe amandla"</string>
<string name="cached_apps_freezer_reboot_dialog_text" msgid="695330563489230096">"Kufanele idivayisi yakho iqaliswe ukuze lolu shintsho lusebenze. Qalisa manje noma khansela."</string>
<string name="accessibility_work_profile_app_description" msgid="5470883112342119165">"Umsebenzi we-<xliff:g id="APP_NAME">%s</xliff:g>"</string>
- <!-- no translation found for media_transfer_wired_usb_device_name (7699141088423210903) -->
- <skip />
+ <string name="media_transfer_wired_usb_device_name" msgid="7699141088423210903">"Ama-headphone anentambo"</string>
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index af72888..1d4cfdc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -500,6 +500,20 @@
}
}
+ /**
+ * To generate and cache the label description.
+ *
+ * @param entry contain the entries of an app
+ */
+ public void ensureLabelDescription(AppEntry entry) {
+ if (entry.labelDescription != null) {
+ return;
+ }
+ synchronized (entry) {
+ entry.ensureLabelDescriptionLocked(mContext);
+ }
+ }
+
public void requestSize(String packageName, int userId) {
if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
synchronized (mEntriesMap) {
@@ -1524,6 +1538,7 @@
public long size;
public long internalSize;
public long externalSize;
+ public String labelDescription;
public boolean mounted;
@@ -1616,6 +1631,24 @@
return "";
}
}
+
+ /**
+ * Get the label description which distinguishes a personal app from a work app for
+ * accessibility purpose. If the app is in a work profile, then add a "work" prefix to the
+ * app label.
+ *
+ * @param context The application context
+ */
+ public void ensureLabelDescriptionLocked(Context context) {
+ final int userId = UserHandle.getUserId(this.info.uid);
+ if (UserManager.get(context).isManagedProfile(userId)) {
+ this.labelDescription = context.getString(
+ com.android.settingslib.R.string.accessibility_work_profile_app_description,
+ this.label);
+ } else {
+ this.labelDescription = this.label;
+ }
+ }
}
private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 959c42fe..b83a9c4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -219,6 +219,33 @@
}
/**
+ * Get the MediaDevice list that can be removed from current media session.
+ *
+ * @return list of MediaDevice
+ */
+ List<MediaDevice> getDeselectableMediaDevice() {
+ final List<MediaDevice> deviceList = new ArrayList<>();
+ if (TextUtils.isEmpty(mPackageName)) {
+ Log.d(TAG, "getDeselectableMediaDevice() package name is null or empty!");
+ return deviceList;
+ }
+
+ final RoutingSessionInfo info = getRoutingSessionInfo();
+ if (info != null) {
+ for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
+ deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
+ route, mPackageName));
+ Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
+ }
+ return deviceList;
+ }
+ Log.d(TAG, "getDeselectableMediaDevice() cannot found deselectable MediaDevice from : "
+ + mPackageName);
+
+ return deviceList;
+ }
+
+ /**
* Get the MediaDevice list that has been selected to current media.
*
* @return list of MediaDevice
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index f34903c..e89f628 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -308,6 +308,15 @@
}
/**
+ * Get the MediaDevice list that can be removed from current media session.
+ *
+ * @return list of MediaDevice
+ */
+ public List<MediaDevice> getDeselectableMediaDevice() {
+ return mInfoMediaManager.getDeselectableMediaDevice();
+ }
+
+ /**
* Release session to stop playing media on MediaDevice.
*/
public boolean releaseSession() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index c514671..248eb5b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -416,6 +416,31 @@
}
@Test
+ public void getDeselectableMediaDevice_packageNameIsNull_returnFalse() {
+ mInfoMediaManager.mPackageName = null;
+
+ assertThat(mInfoMediaManager.getDeselectableMediaDevice()).isEmpty();
+ }
+
+ @Test
+ public void getDeselectableMediaDevice_checkList() {
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo info = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(info);
+ final List<MediaRoute2Info> mediaRoute2Infos = new ArrayList<>();
+ final MediaRoute2Info mediaRoute2Info = mock(MediaRoute2Info.class);
+ mediaRoute2Infos.add(mediaRoute2Info);
+ mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+ mShadowRouter2Manager.setDeselectableRoutes(mediaRoute2Infos);
+ when(mediaRoute2Info.getName()).thenReturn(TEST_NAME);
+
+ final List<MediaDevice> mediaDevices = mInfoMediaManager.getDeselectableMediaDevice();
+
+ assertThat(mediaDevices.size()).isEqualTo(1);
+ assertThat(mediaDevices.get(0).getName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
public void adjustSessionVolume_routingSessionInfoIsNull_noCrash() {
mInfoMediaManager.adjustSessionVolume(null, 10);
}
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index db0cb06..5bb5500 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -15,6 +15,7 @@
*/
package com.android.settingslib.testutils.shadow;
+import android.annotation.NonNull;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
@@ -31,6 +32,7 @@
private List<MediaRoute2Info> mAvailableRoutes;
private List<MediaRoute2Info> mAllRoutes;
+ private List<MediaRoute2Info> mDeselectableRoutes;
private List<RoutingSessionInfo> mActiveSessions;
private List<RoutingSessionInfo> mRoutingSessions;
@@ -70,6 +72,15 @@
mRoutingSessions = infos;
}
+ @Implementation
+ public List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo sessionInfo) {
+ return mDeselectableRoutes;
+ }
+
+ public void setDeselectableRoutes(List<MediaRoute2Info> routes) {
+ mDeselectableRoutes = routes;
+ }
+
public static ShadowRouter2Manager getShadow() {
return (ShadowRouter2Manager) Shadow.extract(
MediaRouter2Manager.getInstance(RuntimeEnvironment.application));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 3d7559b..aae72e5 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1131,6 +1131,10 @@
Settings.Global.NSD_ON,
GlobalSettingsProto.NSD_ON);
+ dumpSetting(s, p,
+ Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
+ GlobalSettingsProto.NR_NSA_TRACKING_SCREEN_OFF_MODE);
+
final long ntpToken = p.start(GlobalSettingsProto.NTP);
dumpSetting(s, p,
Settings.Global.NTP_SERVER,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index e537b76..fa87b62 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -391,6 +391,7 @@
Settings.Global.NITZ_UPDATE_DIFF,
Settings.Global.NITZ_UPDATE_SPACING,
Settings.Global.NOTIFICATION_SNOOZE_OPTIONS,
+ Settings.Global.NR_NSA_TRACKING_SCREEN_OFF_MODE,
Settings.Global.NSD_ON,
Settings.Global.NTP_SERVER,
Settings.Global.NTP_TIMEOUT,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b85c771..a0130f8 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -300,6 +300,9 @@
<!-- Permissions needed to test shared libraries -->
<uses-permission android:name="android.permission.ACCESS_SHARED_LIBRARIES" />
+ <!-- Permissions required for CTS test - TVInputManagerTest -->
+ <uses-permission android:name="android.permission.TV_INPUT_HARDWARE" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 055b2be..985269b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -607,7 +607,7 @@
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
- android:theme="@android:style/Theme.Black.NoTitleBar">
+ android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.app.action.CONFIRM_DEVICE_CREDENTIAL_WITH_USER" />
<category android:name="android.intent.category.DEFAULT" />
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index c8cf7f50..28491d6 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -41,6 +41,7 @@
tsuji@google.com
twickham@google.com
winsonc@google.com
+zakcohen@google.com
#Android Auto
stenning@google.com
diff --git a/packages/SystemUI/res/layout/keyguard_media_header.xml b/packages/SystemUI/res/layout/keyguard_media_header.xml
index a520719..63a878f 100644
--- a/packages/SystemUI/res/layout/keyguard_media_header.xml
+++ b/packages/SystemUI/res/layout/keyguard_media_header.xml
@@ -24,25 +24,4 @@
android:paddingEnd="0dp"
android:focusable="true"
android:clickable="true"
->
-
- <!-- Background views required by ActivatableNotificationView. -->
- <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
- android:id="@+id/backgroundNormal"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <com.android.systemui.statusbar.notification.row.NotificationBackgroundView
- android:id="@+id/backgroundDimmed"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
- <com.android.systemui.statusbar.notification.FakeShadowView
- android:id="@+id/fake_shadow"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- />
-
-</com.android.systemui.statusbar.notification.stack.MediaHeaderView>
+/>
diff --git a/packages/SystemUI/res/layout/qs_media_panel.xml b/packages/SystemUI/res/layout/media_view.xml
similarity index 85%
rename from packages/SystemUI/res/layout/qs_media_panel.xml
rename to packages/SystemUI/res/layout/media_view.xml
index bf06242..1a1fddb 100644
--- a/packages/SystemUI/res/layout/qs_media_panel.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -16,7 +16,7 @@
-->
<!-- Layout for media controls inside QSPanel carousel -->
-<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.systemui.util.animation.TransitionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/qs_media_controls"
android:layout_width="match_parent"
@@ -24,18 +24,7 @@
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center_horizontal|fill_vertical"
- app:layoutDescription="@xml/media_scene">
-
- <View
- android:id="@+id/media_background"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:background="@drawable/qs_media_background"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- />
+ android:background="@drawable/qs_media_background">
<FrameLayout
android:id="@+id/notification_media_progress_time"
@@ -185,15 +174,5 @@
<!-- Buttons to remove this view when no longer needed -->
<include
layout="@layout/qs_media_panel_options"
- android:visibility="gone"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <androidx.constraintlayout.widget.Guideline
- android:id="@+id/view_width"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- app:layout_constraintGuide_begin="300dp" />
-</androidx.constraintlayout.motion.widget.MotionLayout>
+ android:visibility="gone" />
+</com.android.systemui.util.animation.TransitionLayout>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 09918e7..d47ad7f 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -90,6 +90,8 @@
<item type="id" name="view_width_animator_end_tag"/>
<item type="id" name="view_width_current_value"/>
+ <item type="id" name="requires_remeasuring"/>
+
<!-- Whether the icon is from a notification for which targetSdk < L -->
<item type="id" name="icon_is_pre_L"/>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 52ace2b..8102043 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -573,6 +573,7 @@
<item name="android:textColor">?attr/wallpaperTextColor</item>
<item name="android:textAllCaps">false</item>
<item name="android:textSize">14sp</item>
+ <item name="android:minWidth">0dp</item>
</style>
<style name="TextAppearance.HeadsUpStatusBarText"
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
new file mode 100644
index 0000000..57e6f36
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -0,0 +1,184 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <Constraint
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="18dp"
+ android:layout_marginTop="22dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/app_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/icon"
+ app:layout_constraintEnd_toStartOf="@id/media_seamless"
+ app:layout_constraintHorizontal_bias="0"
+ />
+
+ <Constraint
+ android:id="@+id/media_seamless"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_min="60dp"
+ android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ />
+
+ <Constraint
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/qs_media_album_size"
+ android:layout_height="@dimen/qs_media_album_size"
+ android:layout_marginTop="16dp"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginBottom="24dp"
+ app:layout_constraintTop_toBottomOf="@id/icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ />
+
+ <!-- Song name -->
+ <Constraint
+ android:id="@+id/header_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="17dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintBottom_toTopOf="@id/header_artist"
+ app:layout_constraintStart_toEndOf="@id/album_art"
+ app:layout_constraintEnd_toStartOf="@id/action0"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Artist name -->
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ android:layout_marginBottom="24dp"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintEnd_toStartOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Seek Bar -->
+ <Constraint
+ android:id="@+id/media_progress_bar"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:alpha="0.0"
+ app:layout_constraintTop_toBottomOf="@id/album_art"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:visibility="gone"
+ />
+
+ <Constraint
+ android:id="@+id/notification_media_progress_time"
+ android:alpha="0.0"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="35dp"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@id/album_art"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ android:visibility="gone"
+ />
+
+ <Constraint
+ android:id="@+id/action0"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginTop="16dp"
+ android:visibility="gone"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/header_title"
+ app:layout_constraintRight_toLeftOf="@id/action1"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action1"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ >
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:visibility="gone"
+ android:layout_marginTop="18dp"
+ app:layout_constraintTop_toBottomOf="@id/app_name"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintRight_toRightOf="parent"
+ >
+ </Constraint>
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
new file mode 100644
index 0000000..78973f3
--- /dev/null
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <Constraint
+ android:id="@+id/icon"
+ android:layout_width="16dp"
+ android:layout_height="16dp"
+ android:layout_marginStart="18dp"
+ android:layout_marginTop="22dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/app_name"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="10dp"
+ android:layout_marginStart="10dp"
+ android:layout_marginTop="20dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/icon"
+ app:layout_constraintEnd_toStartOf="@id/media_seamless"
+ app:layout_constraintHorizontal_bias="0"
+ />
+
+ <Constraint
+ android:id="@+id/media_seamless"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_min="60dp"
+ android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ />
+
+ <Constraint
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/qs_media_album_size"
+ android:layout_height="@dimen/qs_media_album_size"
+ android:layout_marginTop="14dp"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@+id/app_name"
+ app:layout_constraintStart_toStartOf="parent"
+ />
+
+ <!-- Song name -->
+ <Constraint
+ android:id="@+id/header_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginTop="17dp"
+ android:layout_marginStart="16dp"
+ app:layout_constraintTop_toBottomOf="@+id/app_name"
+ app:layout_constraintStart_toEndOf="@id/album_art"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Artist name -->
+ <Constraint
+ android:id="@+id/header_artist"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginTop="3dp"
+ app:layout_constraintTop_toBottomOf="@id/header_title"
+ app:layout_constraintStart_toStartOf="@id/header_title"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <!-- Seek Bar -->
+ <Constraint
+ android:id="@+id/media_progress_bar"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="3dp"
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/notification_media_progress_time"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="38dp"
+ android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
+ android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintTop_toBottomOf="@id/header_artist"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ />
+
+ <Constraint
+ android:id="@+id/action0"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintHorizontal_chainStyle="packed"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toLeftOf="@id/action1"
+ app:layout_constraintTop_toBottomOf="@id/notification_media_progress_time"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action1"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action0"
+ app:layout_constraintRight_toLeftOf="@id/action2"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action2"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action1"
+ app:layout_constraintRight_toLeftOf="@id/action3"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action3"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ app:layout_constraintLeft_toRightOf="@id/action2"
+ app:layout_constraintRight_toLeftOf="@id/action4"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/action4"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
+ app:layout_constraintLeft_toRightOf="@id/action3"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toTopOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent">
+ </Constraint>
+</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_scene.xml b/packages/SystemUI/res/xml/media_scene.xml
deleted file mode 100644
index f61b2b0..0000000
--- a/packages/SystemUI/res/xml/media_scene.xml
+++ /dev/null
@@ -1,447 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2020 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
- -->
-<MotionScene
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
-
- <Transition
- app:constraintSetStart="@id/collapsed"
- app:constraintSetEnd="@id/expanded"
- app:duration="1000" >
- <KeyFrameSet >
- <KeyPosition
- app:motionTarget="@+id/action0"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyPosition
- app:motionTarget="@+id/action1"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyPosition
- app:motionTarget="@+id/action2"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyPosition
- app:motionTarget="@+id/action3"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyPosition
- app:motionTarget="@+id/action4"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyPosition
- app:motionTarget="@+id/media_progress_bar"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyAttribute
- app:motionTarget="@id/media_progress_bar"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/media_progress_bar"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyPosition
- app:motionTarget="@+id/notification_media_progress_time"
- app:keyPositionType="pathRelative"
- app:framePosition="70"
- app:sizePercent="0.9" />
- <KeyAttribute
- app:motionTarget="@id/notification_media_progress_time"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/notification_media_progress_time"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyAttribute
- app:motionTarget="@id/action0"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/action0"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyAttribute
- app:motionTarget="@id/action1"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/action1"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyAttribute
- app:motionTarget="@id/action2"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/action2"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyAttribute
- app:motionTarget="@id/action3"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/action3"
- app:framePosition="70"
- android:alpha="0.0"/>
- <KeyAttribute
- app:motionTarget="@id/action4"
- app:framePosition="0"
- android:alpha="0.0" />
- <KeyAttribute
- app:motionTarget="@+id/action4"
- app:framePosition="70"
- android:alpha="0.0"/>
- </KeyFrameSet>
- </Transition>
-
- <ConstraintSet android:id="@+id/expanded">
- <Constraint
- android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginStart="18dp"
- android:layout_marginTop="22dp"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- />
-
- <Constraint
- android:id="@+id/app_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="10dp"
- android:layout_marginStart="10dp"
- android:layout_marginTop="20dp"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_bias="0"
- />
-
- <Constraint
- android:id="@+id/media_seamless"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintWidth_min="60dp"
- android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- />
-
- <Constraint
- android:id="@+id/album_art"
- android:layout_width="@dimen/qs_media_album_size"
- android:layout_height="@dimen/qs_media_album_size"
- android:layout_marginTop="14dp"
- android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintTop_toBottomOf="@+id/app_name"
- app:layout_constraintStart_toStartOf="parent"
- />
-
- <!-- Song name -->
- <Constraint
- android:id="@+id/header_title"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- android:layout_marginTop="17dp"
- android:layout_marginStart="16dp"
- app:layout_constraintTop_toBottomOf="@+id/app_name"
- app:layout_constraintStart_toEndOf="@id/album_art"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintHorizontal_bias="0"/>
-
- <!-- Artist name -->
- <Constraint
- android:id="@+id/header_artist"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- android:layout_marginTop="3dp"
- app:layout_constraintTop_toBottomOf="@id/header_title"
- app:layout_constraintStart_toStartOf="@id/header_title"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintHorizontal_bias="0"/>
-
- <!-- Seek Bar -->
- <Constraint
- android:id="@+id/media_progress_bar"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="3dp"
- app:layout_constraintTop_toBottomOf="@id/header_artist"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- />
-
- <Constraint
- android:id="@+id/notification_media_progress_time"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="38dp"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintTop_toBottomOf="@id/header_artist"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- />
-
- <Constraint
- android:id="@+id/action0"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginTop="5dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintLeft_toLeftOf="parent"
- app:layout_constraintRight_toLeftOf="@id/action1"
- app:layout_constraintTop_toBottomOf="@id/notification_media_progress_time"
- app:layout_constraintBottom_toBottomOf="parent">
- </Constraint>
-
- <Constraint
- android:id="@+id/action1"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintLeft_toRightOf="@id/action0"
- app:layout_constraintRight_toLeftOf="@id/action2"
- app:layout_constraintTop_toTopOf="@id/action0"
- app:layout_constraintBottom_toBottomOf="parent">
- </Constraint>
-
- <Constraint
- android:id="@+id/action2"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintLeft_toRightOf="@id/action1"
- app:layout_constraintRight_toLeftOf="@id/action3"
- app:layout_constraintTop_toTopOf="@id/action0"
- app:layout_constraintBottom_toBottomOf="parent">
- </Constraint>
-
- <Constraint
- android:id="@+id/action3"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- app:layout_constraintLeft_toRightOf="@id/action2"
- app:layout_constraintRight_toLeftOf="@id/action4"
- app:layout_constraintTop_toTopOf="@id/action0"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintBottom_toBottomOf="parent">
- </Constraint>
-
- <Constraint
- android:id="@+id/action4"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginBottom="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintLeft_toRightOf="@id/action3"
- app:layout_constraintRight_toRightOf="@id/view_width"
- app:layout_constraintTop_toTopOf="@id/action0"
- app:layout_constraintBottom_toBottomOf="parent">
- </Constraint>
- </ConstraintSet>
-
- <ConstraintSet android:id="@+id/collapsed">
- <Constraint
- android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
- android:layout_marginStart="18dp"
- android:layout_marginTop="22dp"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- />
-
- <Constraint
- android:id="@+id/app_name"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="10dp"
- android:layout_marginStart="10dp"
- android:layout_marginTop="20dp"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/icon"
- app:layout_constraintEnd_toStartOf="@id/media_seamless"
- app:layout_constraintHorizontal_bias="0"
- />
-
- <Constraint
- android:id="@+id/media_seamless"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintWidth_min="60dp"
- android:layout_marginTop="@dimen/qs_media_panel_outer_padding"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- />
-
- <Constraint
- android:id="@+id/album_art"
- android:layout_width="@dimen/qs_media_album_size"
- android:layout_height="@dimen/qs_media_album_size"
- android:layout_marginTop="16dp"
- android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
- android:layout_marginBottom="24dp"
- app:layout_constraintTop_toBottomOf="@id/icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintBottom_toBottomOf="parent"
- />
-
- <!-- Song name -->
- <Constraint
- android:id="@+id/header_title"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="17dp"
- android:layout_marginStart="16dp"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintBottom_toTopOf="@id/header_artist"
- app:layout_constraintStart_toEndOf="@id/album_art"
- app:layout_constraintEnd_toStartOf="@id/action0"
- app:layout_constraintHorizontal_bias="0"/>
-
- <!-- Artist name -->
- <Constraint
- android:id="@+id/header_artist"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="3dp"
- android:layout_marginBottom="24dp"
- app:layout_constraintTop_toBottomOf="@id/header_title"
- app:layout_constraintStart_toStartOf="@id/header_title"
- app:layout_constraintEnd_toStartOf="@id/action0"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintHorizontal_bias="0"/>
-
- <!-- Seek Bar -->
- <Constraint
- android:id="@+id/media_progress_bar"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:alpha="0.0"
- app:layout_constraintTop_toBottomOf="@id/album_art"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/notification_media_progress_time"
- android:alpha="0.0"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginTop="35dp"
- android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
- android:layout_marginStart="@dimen/qs_media_panel_outer_padding"
- app:layout_constraintTop_toBottomOf="@id/album_art"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toEndOf="@id/view_width"
- android:visibility="gone"
- />
-
- <Constraint
- android:id="@+id/action0"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginTop="16dp"
- android:visibility="gone"
- app:layout_constraintHorizontal_chainStyle="packed"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toRightOf="@id/header_title"
- app:layout_constraintRight_toLeftOf="@id/action1"
- >
- </Constraint>
-
- <Constraint
- android:id="@+id/action1"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginTop="18dp"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toRightOf="@id/action0"
- app:layout_constraintRight_toLeftOf="@id/action2"
- >
- </Constraint>
-
- <Constraint
- android:id="@+id/action2"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginTop="18dp"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toRightOf="@id/action1"
- app:layout_constraintRight_toLeftOf="@id/action3"
- >
- </Constraint>
-
- <Constraint
- android:id="@+id/action3"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:layout_marginTop="18dp"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toRightOf="@id/action2"
- app:layout_constraintRight_toLeftOf="@id/action4"
- >
- </Constraint>
-
- <Constraint
- android:id="@+id/action4"
- android:layout_width="48dp"
- android:layout_height="48dp"
- android:layout_marginStart="4dp"
- android:layout_marginEnd="4dp"
- android:visibility="gone"
- android:layout_marginTop="18dp"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toRightOf="@id/action3"
- app:layout_constraintRight_toRightOf="@id/view_width"
- >
- </Constraint>
- </ConstraintSet>
-</MotionScene>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index f1b401e..ecd8b45 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -57,7 +57,6 @@
private NotificationEntry mEntry;
private final String mKey;
- private final String mGroupId;
private long mLastUpdated;
private long mLastAccessed;
@@ -69,6 +68,8 @@
/** Whether flyout text should be suppressed, regardless of any other flags or state. */
private boolean mSuppressFlyout;
+ /** Whether this bubble should auto expand regardless of the normal flag, used for overflow. */
+ private boolean mShouldAutoExpand;
// Items that are typically loaded later
private String mAppName;
@@ -96,17 +97,10 @@
private int mDotColor;
private Path mDotPath;
-
- public static String groupId(NotificationEntry entry) {
- UserHandle user = entry.getSbn().getUser();
- return user.getIdentifier() + "|" + entry.getSbn().getPackageName();
- }
-
// TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble
Bubble(ShortcutInfo shortcutInfo) {
mShortcutInfo = shortcutInfo;
mKey = shortcutInfo.getId();
- mGroupId = shortcutInfo.getId();
}
/** Used in tests when no UI is required. */
@@ -116,7 +110,6 @@
mEntry = e;
mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
- mGroupId = groupId(e);
mSuppressionListener = listener;
}
@@ -129,10 +122,6 @@
return mEntry;
}
- public String getGroupId() {
- return mGroupId;
- }
-
public String getPackageName() {
return mEntry.getSbn().getPackageName();
}
@@ -297,20 +286,13 @@
}
/**
- * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
+ * @return the last time this bubble was updated or accessed, whichever is most recent.
*/
long getLastActivity() {
return Math.max(mLastUpdated, mLastAccessed);
}
/**
- * @return the timestamp in milliseconds of the most recent notification entry for this bubble
- */
- long getLastUpdateTime() {
- return mLastUpdated;
- }
-
- /**
* @return if the bubble was ever expanded
*/
boolean getWasAccessed() {
@@ -411,15 +393,6 @@
return mFlyoutMessage;
}
- /**
- * Returns whether the notification for this bubble is a foreground service. It shows that this
- * is an ongoing bubble.
- */
- boolean isOngoing() {
- int flags = mEntry.getSbn().getNotification().flags;
- return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
- }
-
float getDesiredHeight(Context context) {
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
@@ -499,7 +472,11 @@
boolean shouldAutoExpand() {
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
- return metadata != null && metadata.getAutoExpandBubble();
+ return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand;
+ }
+
+ void setShouldAutoExpand(boolean shouldAutoExpand) {
+ mShouldAutoExpand = shouldAutoExpand;
}
@Override
@@ -562,7 +539,7 @@
normalX,
normalY,
this.showInShade(),
- this.isOngoing(),
+ false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 2587369..5f157c1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -729,8 +729,9 @@
mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
if (savedBubbleKeys.contains(e.getKey())
&& mNotificationInterruptStateProvider.shouldBubbleUp(e)
+ && e.isBubble()
&& canLaunchInActivityView(mContext, e)) {
- updateBubble(e, /* suppressFlyout= */ true);
+ updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
}
}
// Finally, remove the entries for this user now that bubbles are restored.
@@ -776,6 +777,10 @@
}
}
+ boolean inLandscape() {
+ return mOrientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
/**
* Set a listener to be notified of bubble expand events.
*/
@@ -840,25 +845,34 @@
void promoteBubbleFromOverflow(Bubble bubble) {
bubble.setInflateSynchronously(mInflateSynchronously);
- setIsBubble(bubble, /* isBubble */ true);
+ setIsBubble(bubble.getEntry(), /* isBubble */ true);
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
/**
* Request the stack expand if needed, then select the specified Bubble as current.
+ * If no bubble exists for this entry, one is created.
*
- * @param notificationKey the notification key for the bubble to be selected
+ * @param entry the notification for the bubble to be selected
*/
- public void expandStackAndSelectBubble(String notificationKey) {
- Bubble bubble = mBubbleData.getBubbleInStackWithKey(notificationKey);
- if (bubble == null) {
- bubble = mBubbleData.getOverflowBubbleWithKey(notificationKey);
- if (bubble != null) {
- mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
- }
- } else if (bubble.getEntry().isBubble()){
+ public void expandStackAndSelectBubble(NotificationEntry entry) {
+ String key = entry.getKey();
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
+ if (bubble != null) {
mBubbleData.setSelectedBubble(bubble);
+ } else {
+ bubble = mBubbleData.getOverflowBubbleWithKey(key);
+ if (bubble != null) {
+ bubble.setShouldAutoExpand(true);
+ promoteBubbleFromOverflow(bubble);
+ } else if (entry.canBubble()) {
+ // It can bubble but it's not -- it got aged out of the overflow before it
+ // was dismissed or opened, make it a bubble again.
+ setIsBubble(entry, true);
+ updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
+ }
}
+
mBubbleData.setExpanded(true);
}
@@ -878,11 +892,7 @@
* @param notif the notification associated with this bubble.
*/
void updateBubble(NotificationEntry notif) {
- updateBubble(notif, false /* suppressFlyout */);
- }
-
- void updateBubble(NotificationEntry notif, boolean suppressFlyout) {
- updateBubble(notif, suppressFlyout, true /* showInShade */);
+ updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
}
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
@@ -897,7 +907,8 @@
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.inflate(
b -> {
- mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade);
+ mBubbleData.notificationEntryUpdated(b, suppressFlyout,
+ showInShade);
if (bubble.getBubbleIntent() == null) {
return;
}
@@ -975,18 +986,20 @@
private void onEntryAdded(NotificationEntry entry) {
if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+ && entry.isBubble()
&& canLaunchInActivityView(mContext, entry)) {
updateBubble(entry);
}
}
private void onEntryUpdated(NotificationEntry entry) {
+ // shouldBubbleUp checks canBubble & for bubble metadata
boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
&& canLaunchInActivityView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
// It was previously a bubble but no longer a bubble -- lets remove it
removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
- } else if (shouldBubble) {
+ } else if (shouldBubble && entry.isBubble()) {
updateBubble(entry);
}
}
@@ -1032,14 +1045,14 @@
}
}
- private void setIsBubble(Bubble b, boolean isBubble) {
+ private void setIsBubble(NotificationEntry entry, boolean isBubble) {
if (isBubble) {
- b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
+ entry.getSbn().getNotification().flags |= FLAG_BUBBLE;
} else {
- b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
+ entry.getSbn().getNotification().flags &= ~FLAG_BUBBLE;
}
try {
- mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
+ mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, 0);
} catch (RemoteException e) {
// Bad things have happened
}
@@ -1088,7 +1101,7 @@
}
} else {
if (bubble.getEntry().isBubble() && bubble.showInShade()) {
- setIsBubble(bubble, /* isBubble */ false);
+ setIsBubble(bubble.getEntry(), false /* isBubble */);
}
if (bubble.getEntry().getRow() != null) {
bubble.getEntry().getRow().updateBubbleButton();
@@ -1323,7 +1336,8 @@
boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getDisplayId() == task.displayId) {
- expandStackAndSelectBubble(b.getKey());
+ mBubbleData.setSelectedBubble(b);
+ mBubbleData.setExpanded(true);
return;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 65d5beb..35647b0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -21,12 +21,9 @@
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static java.util.stream.Collectors.toList;
-
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
-import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.util.Pair;
import android.view.View;
@@ -45,7 +42,6 @@
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
@@ -62,9 +58,6 @@
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_MAX_SORT_KEY_DESCENDING =
- Comparator.<Map.Entry<String, Long>, Long>comparing(Map.Entry::getValue).reversed();
-
/** Contains information about changes that have been made to the state of bubbles. */
static final class Update {
boolean expandedChanged;
@@ -129,8 +122,6 @@
// State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange;
- private NotificationListenerService.Ranking mTmpRanking;
-
private TimeSource mTimeSource = System::currentTimeMillis;
@Nullable
@@ -216,15 +207,14 @@
}
moveOverflowBubbleToPending(bubble);
// Preserve new order for next repack, which sorts by last updated time.
- bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
bubble.inflate(
b -> {
- notificationEntryUpdated(bubble, /* suppressFlyout */
- false, /* showInShade */ true);
- setSelectedBubble(bubble);
+ b.setShouldAutoExpand(true);
+ b.markUpdatedAt(mTimeSource.currentTimeMillis());
+ notificationEntryUpdated(bubble, false /* suppressFlyout */,
+ true /* showInShade */);
},
mContext, stack, factory);
- dispatchPendingChanges();
}
void setShowingOverflow(boolean showingOverflow) {
@@ -290,13 +280,13 @@
bubble.setSuppressFlyout(suppressFlyout);
doUpdate(bubble);
}
+
if (bubble.shouldAutoExpand()) {
+ bubble.setShouldAutoExpand(false);
setSelectedBubbleInternal(bubble);
if (!mExpanded) {
setExpandedInternal(true);
}
- } else if (mSelectedBubble == null) {
- setSelectedBubbleInternal(bubble);
}
boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
@@ -370,20 +360,11 @@
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "doAdd: " + bubble);
}
- int minInsertPoint = 0;
- boolean newGroup = !hasBubbleWithGroupId(bubble.getGroupId());
- if (isExpanded()) {
- // 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) {
- mStateChange.orderChanged = true;
- }
+ mBubbles.add(0, bubble);
mStateChange.addedBubble = bubble;
-
+ // Adding the first bubble doesn't change the order
+ mStateChange.orderChanged = mBubbles.size() > 1;
if (!isExpanded()) {
- mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
- // Top bubble becomes selected.
setSelectedBubbleInternal(mBubbles.get(0));
}
}
@@ -406,14 +387,10 @@
}
mStateChange.updatedBubble = bubble;
if (!isExpanded()) {
- // while collapsed, update causes re-pack
int prevPos = mBubbles.indexOf(bubble);
mBubbles.remove(bubble);
- int newPos = insertBubble(0, bubble);
- if (prevPos != newPos) {
- packGroup(newPos);
- mStateChange.orderChanged = true;
- }
+ mBubbles.add(0, bubble);
+ mStateChange.orderChanged = prevPos != 0;
setSelectedBubbleInternal(mBubbles.get(0));
}
}
@@ -581,7 +558,6 @@
Log.e(TAG, "Attempt to expand stack without selected bubble!");
return;
}
- mSelectedBubble.markUpdatedAt(mTimeSource.currentTimeMillis());
mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
mStateChange.orderChanged |= repackAll();
} else if (!mBubbles.isEmpty()) {
@@ -596,17 +572,11 @@
}
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.
+ int index = mBubbles.indexOf(mSelectedBubble);
+ if (index != 0) {
mBubbles.remove(mSelectedBubble);
mBubbles.add(0, mSelectedBubble);
- mStateChange.orderChanged |= packGroup(0);
+ mStateChange.orderChanged = true;
}
}
}
@@ -616,91 +586,12 @@
}
private static long sortKey(Bubble bubble) {
- long key = bubble.getLastUpdateTime();
- if (bubble.isOngoing()) {
- // Set 2nd highest bit (signed long int), to partition between ongoing and regular
- key |= 0x4000000000000000L;
- }
- return key;
+ return bubble.getLastActivity();
}
/**
- * Locates and inserts the bubble into a sorted position. The is inserted
- * based on sort key, groupId is not considered. A call to {@link #packGroup(int)} may be
- * required to keep grouping intact.
- *
- * @param minPosition the first insert point to consider
- * @param newBubble the bubble to insert
- * @return the position where the bubble was inserted
- */
- private int insertBubble(int minPosition, Bubble newBubble) {
- long newBubbleSortKey = sortKey(newBubble);
- String previousGroupId = null;
-
- for (int pos = minPosition; pos < mBubbles.size(); pos++) {
- Bubble bubbleAtPos = mBubbles.get(pos);
- String groupIdAtPos = bubbleAtPos.getGroupId();
- boolean atStartOfGroup = !groupIdAtPos.equals(previousGroupId);
-
- if (atStartOfGroup && newBubbleSortKey > sortKey(bubbleAtPos)) {
- // Insert before the start of first group which has older bubbles.
- mBubbles.add(pos, newBubble);
- return pos;
- }
- previousGroupId = groupIdAtPos;
- }
- mBubbles.add(newBubble);
- return mBubbles.size() - 1;
- }
-
- private boolean hasBubbleWithGroupId(String groupId) {
- return mBubbles.stream().anyMatch(b -> b.getGroupId().equals(groupId));
- }
-
- private int findFirstIndexForGroup(String appId) {
- for (int i = 0; i < mBubbles.size(); i++) {
- Bubble bubbleAtPos = mBubbles.get(i);
- if (bubbleAtPos.getGroupId().equals(appId)) {
- return i;
- }
- }
- return 0;
- }
-
- /**
- * Starting at the given position, moves all bubbles with the same group id to follow. Bubbles
- * at positions lower than {@code position} are unchanged. Relative order within the group
- * 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 boolean packGroup(int position) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "packGroup: position=" + position);
- }
- Bubble groupStart = mBubbles.get(position);
- final String groupAppId = groupStart.getGroupId();
- List<Bubble> moving = new ArrayList<>();
-
- // Walk backward, collect bubbles within the group
- for (int i = mBubbles.size() - 1; i > position; i--) {
- if (mBubbles.get(i).getGroupId().equals(groupAppId)) {
- moving.add(0, mBubbles.get(i));
- }
- }
- if (moving.isEmpty()) {
- return false;
- }
- mBubbles.removeAll(moving);
- mBubbles.addAll(position + 1, moving);
- return true;
- }
-
- /**
- * 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 its bubbles. Bubbles
- * within each group are then sorted by lastUpdated descending.
+ * This applies a full sort and group pass to all existing bubbles.
+ * Bubbles are sorted by lastUpdated descending.
*
* @return true if the position of any bubbles changed as a result
*/
@@ -711,31 +602,11 @@
if (mBubbles.isEmpty()) {
return false;
}
- Map<String, Long> groupLastActivity = new HashMap<>();
- for (Bubble bubble : mBubbles) {
- long maxSortKeyForGroup = groupLastActivity.getOrDefault(bubble.getGroupId(), 0L);
- long sortKeyForBubble = sortKey(bubble);
- if (sortKeyForBubble > maxSortKeyForGroup) {
- groupLastActivity.put(bubble.getGroupId(), sortKeyForBubble);
- }
- }
-
- // Sort groups by their most recently active bubble
- List<String> groupsByMostRecentActivity =
- groupLastActivity.entrySet().stream()
- .sorted(GROUPS_BY_MAX_SORT_KEY_DESCENDING)
- .map(Map.Entry::getKey)
- .collect(toList());
-
List<Bubble> repacked = new ArrayList<>(mBubbles.size());
-
- // For each group, add bubbles, freshest to oldest
- for (String appId : groupsByMostRecentActivity) {
- mBubbles.stream()
- .filter((b) -> b.getGroupId().equals(appId))
- .sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
- .forEachOrdered(repacked::add);
- }
+ // Add bubbles, freshest to oldest
+ mBubbles.stream()
+ .sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
+ .forEachOrdered(repacked::add);
if (repacked.equals(mBubbles)) {
return false;
}
@@ -778,11 +649,12 @@
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
+
/**
* The set of bubbles in overflow.
*/
@VisibleForTesting(visibility = PRIVATE)
- public List<Bubble> getOverflowBubbles() {
+ List<Bubble> getOverflowBubbles() {
return Collections.unmodifiableList(mOverflowBubbles);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
index 19733a5..d98fee3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
@@ -69,10 +69,10 @@
&& selected.getKey() != BubbleOverflow.KEY
&& bubble == selected);
String arrow = isSelected ? "=>" : " ";
- sb.append(String.format("%s Bubble{act=%12d, ongoing=%d, key=%s}\n",
+ sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n",
arrow,
bubble.getLastActivity(),
- (bubble.isOngoing() ? 1 : 0),
+ (bubble.showInShade() ? 1 : 0),
bubble.getKey()));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 7697400..c4b4f43 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -226,8 +226,12 @@
public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
+ updateDimensions();
+ }
+
+ void updateDimensions() {
mDisplaySize = new Point();
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// Get the real size -- this includes screen decorations (notches, statusbar, navbar).
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
Resources res = getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
index f4eb580..af6e66a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java
@@ -61,9 +61,7 @@
}
void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
- mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.bubble_overflow_icon_bitmap_size);
+ updateDimensions();
mExpandedView = (BubbleExpandedView) mInflater.inflate(
R.layout.bubble_expanded_view, parentViewGroup /* root */,
@@ -74,6 +72,15 @@
updateIcon(mContext, parentViewGroup);
}
+ void updateDimensions() {
+ mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+ mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
+ R.dimen.bubble_overflow_icon_bitmap_size);
+ if (mExpandedView != null) {
+ mExpandedView.updateDimensions();
+ }
+ }
+
void updateIcon(Context context, ViewGroup parentViewGroup) {
mContext = context;
mInflater = LayoutInflater.from(context);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 08ec789..8fec338 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -70,6 +70,9 @@
}
@Override
public boolean canScrollVertically() {
+ if (mBubbleController.inLandscape()) {
+ return super.canScrollVertically();
+ }
return false;
}
}
@@ -89,6 +92,14 @@
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
+ updateDimensions();
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ mBubbleController.setOverflowCallback(() -> {
+ onDataChanged(mBubbleController.getOverflowBubbles());
+ });
+ }
+
+ void updateDimensions() {
Resources res = getResources();
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
@@ -96,8 +107,9 @@
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
- final int recyclerViewWidth = (displayMetrics.widthPixels
- - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding));
+
+ final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
+ final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
final int viewWidth = recyclerViewWidth / columns;
final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
@@ -109,17 +121,12 @@
mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
- onDataChanged(mBubbleController.getOverflowBubbles());
- mBubbleController.setOverflowCallback(() -> {
- onDataChanged(mBubbleController.getOverflowBubbles());
- });
- onThemeChanged();
}
/**
* Handle theme changes.
*/
- void onThemeChanged() {
+ void updateTheme() {
final int mode =
getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (mode) {
@@ -178,7 +185,8 @@
@Override
public void onResume() {
super.onResume();
- onThemeChanged();
+ updateDimensions();
+ updateTheme();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index c9f362b8..418cc50 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -49,7 +49,6 @@
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
-import android.os.Vibrator;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
@@ -188,7 +187,6 @@
private final SpringAnimation mExpandedViewYAnim;
private final BubbleData mBubbleData;
- private final Vibrator mVibrator;
private final ValueAnimator mDesaturateAndDarkenAnimator;
private final Paint mDesaturateAndDarkenPaint = new Paint();
@@ -701,8 +699,6 @@
// We use the real size & subtract screen decorations / window insets ourselves when needed
wm.getDefaultDisplay().getRealSize(mDisplaySize);
- mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
-
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
@@ -1033,6 +1029,7 @@
mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
} else {
mBubbleContainer.removeView(mBubbleOverflow.getBtn());
+ mBubbleOverflow.updateDimensions();
mBubbleOverflow.updateIcon(mContext,this);
overflowBtnIndex = mBubbleContainer.getChildCount();
}
@@ -2333,7 +2330,7 @@
getNormalizedXPosition(),
getNormalizedYPosition(),
bubble.showInShade(),
- bubble.isOngoing(),
+ false /* isOngoing (unused) */,
false /* isAppForeground (unused) */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 23bcb29..8368b2c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -96,12 +96,6 @@
return new AmbientDisplayConfiguration(context);
}
- /** */
- @Provides
- public Handler provideHandler() {
- return new Handler();
- }
-
@Singleton
@Provides
public DataSaverController provideDataSaverController(NetworkController networkController) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 3a8212c..58aad86 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -28,6 +28,7 @@
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -314,4 +315,9 @@
return context.getSystemService(WindowManager.class);
}
+ @Provides
+ @Singleton
+ static RoleManager provideRoleManager(Context context) {
+ return context.getSystemService(RoleManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 6572937..95a9006 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -28,6 +28,7 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.FalsingManager;
@@ -69,7 +70,7 @@
WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
DockManager dockManager, @Nullable IWallpaperManager wallpaperManager,
ProximitySensor proximitySensor,
- DelayedWakeLock.Builder delayedWakeLockBuilder, Handler handler,
+ DelayedWakeLock.Builder delayedWakeLockBuilder, @Main Handler handler,
DelayableExecutor delayableExecutor,
BiometricUnlockController biometricUnlockController,
BroadcastDispatcher broadcastDispatcher, DozeHost dozeHost) {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ac289cb..61c9a96 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -129,6 +129,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -238,10 +239,10 @@
private final Executor mBackgroundExecutor;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
private ControlsController mControlsController;
- private SharedPreferences mControlsPreferences;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
private Handler mMainHandler;
+ private CurrentUserContextTracker mCurrentUserContextTracker;
@VisibleForTesting
boolean mShowLockScreenCardsAndControls = false;
@@ -301,7 +302,8 @@
@Background Executor backgroundExecutor,
ControlsListingController controlsListingController,
ControlsController controlsController, UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler) {
+ RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+ CurrentUserContextTracker currentUserContextTracker) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -330,6 +332,7 @@
mControlsController = controlsController;
mSysUiState = sysUiState;
mMainHandler = handler;
+ mCurrentUserContextTracker = currentUserContextTracker;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -382,12 +385,6 @@
controlsListingController.addCallback(list -> mControlsServiceInfos = list);
- // Need to be user-specific with the context to make sure we read the correct prefs
- Context userContext = context.createContextAsUser(
- new UserHandle(mUserManager.getUserHandle()), 0);
- mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
- Context.MODE_PRIVATE);
-
// Listen for changes to show controls on the power menu while locked
onPowerMenuLockScreenSettingsChanged();
mContext.getContentResolver().registerContentObserver(
@@ -403,8 +400,14 @@
private void seedFavorites() {
if (mControlsServiceInfos.isEmpty()
- || mControlsController.getFavorites().size() > 0
- || mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
+ || mControlsController.getFavorites().size() > 0) {
+ return;
+ }
+
+ // Need to be user-specific with the context to make sure we read the correct prefs
+ SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext()
+ .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
+ if (prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
return;
}
@@ -426,7 +429,7 @@
if (preferredComponent == null) {
Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
- mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
+ prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
return;
}
@@ -434,8 +437,7 @@
preferredComponent,
(accepted) -> {
Log.i(TAG, "Controls seeded: " + accepted);
- mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
- accepted).apply();
+ prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, accepted).apply();
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt b/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
deleted file mode 100644
index 2fe0d9f..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/GoneChildrenHideHelper.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.graphics.Rect
-import android.view.View
-import android.view.ViewGroup
-
-private val EMPTY_RECT = Rect(0,0,0,0)
-
-private val LAYOUT_CHANGE_LISTENER = object : View.OnLayoutChangeListener {
-
- override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
- oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
- v?.let {
- if (v.visibility == View.GONE) {
- v.clipBounds = EMPTY_RECT
- } else {
- v.clipBounds = null
- }
- }
- }
-}
-/**
- * A helper class that clips all GONE children. Useful for transitions in motionlayout which
- * don't clip its children.
- */
-class GoneChildrenHideHelper private constructor() {
- companion object {
- @JvmStatic
- fun clipGoneChildrenOnLayout(layout: ViewGroup) {
- val childCount = layout.childCount
- for (i in 0 until childCount) {
- val child = layout.getChildAt(i)
- child.addOnLayoutChangeListener(LAYOUT_CHANGE_LISTENER)
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 85e1c6b..5f43e43 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -45,6 +45,7 @@
}
})
}
+
private var view: MediaHeaderView? = null
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt b/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
deleted file mode 100644
index a366725..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/LayoutAnimationHelper.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import android.graphics.Rect
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewTreeObserver
-import com.android.systemui.statusbar.notification.AnimatableProperty
-import com.android.systemui.statusbar.notification.PropertyAnimator
-import com.android.systemui.statusbar.notification.stack.AnimationProperties
-
-/**
- * A utility class that helps with animations of bound changes designed for motionlayout which
- * doesn't work together with regular changeBounds.
- */
-class LayoutAnimationHelper {
-
- private val layout: ViewGroup
- private var sizeAnimationPending = false
- private val desiredBounds = mutableMapOf<View, Rect>()
- private val animationProperties = AnimationProperties()
- private val layoutListener = object : View.OnLayoutChangeListener {
- override fun onLayoutChange(v: View?, left: Int, top: Int, right: Int, bottom: Int,
- oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int) {
- v?.let {
- if (v.alpha == 0.0f || v.visibility == View.GONE || oldLeft - oldRight == 0 ||
- oldTop - oldBottom == 0) {
- return
- }
- if (oldLeft != left || oldTop != top || oldBottom != bottom || oldRight != right) {
- val rect = desiredBounds.getOrPut(v, { Rect() })
- rect.set(left, top, right, bottom)
- onDesiredLocationChanged(v, rect)
- }
- }
- }
- }
-
- constructor(layout: ViewGroup) {
- this.layout = layout
- val childCount = this.layout.childCount
- for (i in 0 until childCount) {
- val child = this.layout.getChildAt(i)
- child.addOnLayoutChangeListener(layoutListener)
- }
- }
-
- private fun onDesiredLocationChanged(v: View, rect: Rect) {
- if (!sizeAnimationPending) {
- applyBounds(v, rect, animate = false)
- }
- // We need to reapply the current bounds in every frame since the layout may override
- // the layout bounds making this view jump and not all calls to apply bounds actually
- // reapply them, for example if there's already an animator to the same target
- reapplyProperty(v, AnimatableProperty.ABSOLUTE_X);
- reapplyProperty(v, AnimatableProperty.ABSOLUTE_Y);
- reapplyProperty(v, AnimatableProperty.WIDTH);
- reapplyProperty(v, AnimatableProperty.HEIGHT);
- }
-
- private fun reapplyProperty(v: View, property: AnimatableProperty) {
- property.property.set(v, property.property.get(v))
- }
-
- private fun applyBounds(v: View, newBounds: Rect, animate: Boolean) {
- PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_X, newBounds.left.toFloat(),
- animationProperties, animate)
- PropertyAnimator.setProperty(v, AnimatableProperty.ABSOLUTE_Y, newBounds.top.toFloat(),
- animationProperties, animate)
- PropertyAnimator.setProperty(v, AnimatableProperty.WIDTH, newBounds.width().toFloat(),
- animationProperties, animate)
- PropertyAnimator.setProperty(v, AnimatableProperty.HEIGHT, newBounds.height().toFloat(),
- animationProperties, animate)
- }
-
- private fun startBoundAnimation(v: View) {
- val target = desiredBounds[v] ?: return
- applyBounds(v, target, animate = true)
- }
-
- fun animatePendingSizeChange(duration: Long, delay: Long) {
- animationProperties.duration = duration
- animationProperties.delay = delay
- if (!sizeAnimationPending) {
- sizeAnimationPending = true
- layout.viewTreeObserver.addOnPreDrawListener (
- object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- layout.viewTreeObserver.removeOnPreDrawListener(this)
- sizeAnimationPending = false
- val childCount = layout.childCount
- for (i in 0 until childCount) {
- val child = layout.getChildAt(i)
- startBoundAnimation(child)
- }
- return true
- }
- })
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index c7b9326..8e1e1b2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -43,12 +43,9 @@
import android.widget.SeekBar;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
-import androidx.constraintlayout.motion.widget.Key;
-import androidx.constraintlayout.motion.widget.KeyAttributes;
-import androidx.constraintlayout.motion.widget.KeyFrames;
-import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
@@ -59,11 +56,11 @@
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSMediaBrowser;
+import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import org.jetbrains.annotations.NotNull;
-import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
@@ -87,16 +84,15 @@
private final Executor mForegroundExecutor;
protected final Executor mBackgroundExecutor;
private final ActivityStarter mActivityStarter;
- private LayoutAnimationHelper mLayoutAnimationHelper;
private Context mContext;
private PlayerViewHolder mViewHolder;
+ private MediaViewController mMediaViewController;
private MediaSession.Token mToken;
private MediaController mController;
private int mBackgroundColor;
protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
- private List<KeyFrames> mKeyFrames;
private String mKey;
private int mAlbumArtSize;
private int mAlbumArtRadius;
@@ -133,12 +129,14 @@
* @param activityStarter activity starter
*/
public MediaControlPanel(Context context, Executor foregroundExecutor,
- DelayableExecutor backgroundExecutor, ActivityStarter activityStarter) {
+ DelayableExecutor backgroundExecutor, ActivityStarter activityStarter,
+ MediaHostStatesManager mediaHostStatesManager) {
mContext = context;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mActivityStarter = activityStarter;
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
+ mMediaViewController = new MediaViewController(context, mediaHostStatesManager);
loadDimens();
}
@@ -147,6 +145,7 @@
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
}
mSeekBarViewModel.onDestroy();
+ mMediaViewController.onDestroy();
}
private void loadDimens() {
@@ -165,6 +164,15 @@
}
/**
+ * Get the view controller used to display media controls
+ * @return the media view controller
+ */
+ @NonNull
+ public MediaViewController getMediaViewController() {
+ return mMediaViewController;
+ }
+
+ /**
* Sets the listening state of the player.
*
* Should be set to true when the QS panel is open. Otherwise, false. This is a signal to avoid
@@ -187,15 +195,13 @@
/** Attaches the player to the view holder. */
public void attach(PlayerViewHolder vh) {
mViewHolder = vh;
- MotionLayout motionView = vh.getPlayer();
- mLayoutAnimationHelper = new LayoutAnimationHelper(motionView);
- GoneChildrenHideHelper.clipGoneChildrenOnLayout(motionView);
- mKeyFrames = motionView.getDefinedTransitions().get(0).getKeyFrameList();
+ TransitionLayout player = vh.getPlayer();
mSeekBarObserver = new SeekBarObserver(vh);
mSeekBarViewModel.getProgress().observeForever(mSeekBarObserver);
SeekBar bar = vh.getSeekBar();
bar.setOnSeekBarChangeListener(mSeekBarViewModel.getSeekBarListener());
bar.setOnTouchListener(mSeekBarViewModel.getSeekBarTouchListener());
+ mMediaViewController.attach(player);
}
/**
@@ -220,8 +226,8 @@
mController = new MediaController(mContext, mToken);
- ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
- ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
// Try to find a browser service component for this app
// TODO also check for a media button receiver intended for restarting (b/154127084)
@@ -247,7 +253,7 @@
mController.registerCallback(mSessionCallback);
- mViewHolder.getBackground().setBackgroundTintList(
+ mViewHolder.getPlayer().setBackgroundTintList(
ColorStateList.valueOf(mBackgroundColor));
// Click action
@@ -356,7 +362,6 @@
}
});
boolean visibleInCompat = actionsWhenCollapsed.contains(i);
- updateKeyFrameVisibility(actionId, visibleInCompat);
setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat);
setVisibleAndAlpha(expandedSet, actionId, true /*visible */);
}
@@ -374,9 +379,9 @@
// Set up long press menu
// TODO: b/156036025 bring back media guts
- // Update both constraint sets to regenerate the animation.
- mViewHolder.getPlayer().updateState(R.id.collapsed, collapsedSet);
- mViewHolder.getPlayer().updateState(R.id.expanded, expandedSet);
+ // TODO: We don't need to refresh this state constantly, only if the state actually changed
+ // to something which might impact the measurement
+ mMediaViewController.refreshState();
}
@UiThread
@@ -412,30 +417,6 @@
}
/**
- * Updates the keyframe visibility such that only views that are not visible actually go
- * through a transition and fade in.
- *
- * @param actionId the id to change
- * @param visible is the view visible
- */
- private void updateKeyFrameVisibility(int actionId, boolean visible) {
- if (mKeyFrames == null) {
- return;
- }
- for (int i = 0; i < mKeyFrames.size(); i++) {
- KeyFrames keyframe = mKeyFrames.get(i);
- ArrayList<Key> viewKeyFrames = keyframe.getKeyFramesForView(actionId);
- for (int j = 0; j < viewKeyFrames.size(); j++) {
- Key key = viewKeyFrames.get(j);
- if (key instanceof KeyAttributes) {
- KeyAttributes attributes = (KeyAttributes) key;
- attributes.setValue("alpha", visible ? 1.0f : 0.0f);
- }
- }
- }
- }
-
- /**
* Return the token for the current media session
* @return the token
*/
@@ -528,8 +509,8 @@
}
// Hide all the old buttons
- ConstraintSet expandedSet = mViewHolder.getPlayer().getConstraintSet(R.id.expanded);
- ConstraintSet collapsedSet = mViewHolder.getPlayer().getConstraintSet(R.id.collapsed);
+ ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
+ ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
for (int i = 1; i < ACTION_IDS.length; i++) {
setVisibleAndAlpha(expandedSet, ACTION_IDS[i], false /*visible */);
setVisibleAndAlpha(collapsedSet, ACTION_IDS[i], false /*visible */);
@@ -571,6 +552,7 @@
options.setVisibility(View.VISIBLE);
return true; // consumed click
});
+ mMediaViewController.refreshState();
}
private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) {
@@ -649,33 +631,4 @@
* Called when a player can't be resumed to give it an opportunity to hide or remove itself
*/
protected void removePlayer() { }
-
- public void measure(@Nullable MediaMeasurementInput input) {
- if (mViewHolder == null) {
- return;
- }
- if (input != null) {
- int width = input.getWidth();
- setPlayerWidth(width);
- mViewHolder.getPlayer().measure(input.getWidthMeasureSpec(),
- input.getHeightMeasureSpec());
- }
- }
-
- public void setPlayerWidth(int width) {
- if (mViewHolder == null) {
- return;
- }
- MotionLayout view = mViewHolder.getPlayer();
- ConstraintSet expandedSet = view.getConstraintSet(R.id.expanded);
- ConstraintSet collapsedSet = view.getConstraintSet(R.id.collapsed);
- collapsedSet.setGuidelineBegin(R.id.view_width, width);
- expandedSet.setGuidelineBegin(R.id.view_width, width);
- view.updateState(R.id.collapsed, collapsedSet);
- view.updateState(R.id.expanded, expandedSet);
- }
-
- public void animatePendingSizeChange(long duration, long startDelay) {
- mLayoutAnimationHelper.animatePendingSizeChange(duration, startDelay);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index 0b04fd0..552fea6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -81,7 +81,7 @@
private fun processDevice(key: String, device: MediaDevice?) {
val enabled = device != null
- val data = MediaDeviceData(enabled, device?.icon, device?.name)
+ val data = MediaDeviceData(enabled, device?.iconWithoutBackground, device?.name)
listeners.forEach {
it.onMediaDeviceChanged(key, data)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index cce1d3e..775a1649 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -21,10 +21,13 @@
import android.animation.ValueAnimator
import android.annotation.IntDef
import android.content.Context
+import android.graphics.Rect
+import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import com.android.systemui.Interpolators
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -47,8 +50,8 @@
private val keyguardStateController: KeyguardStateController,
private val bypassController: KeyguardBypassController,
private val mediaViewManager: MediaViewManager,
- private val mediaMeasurementProvider: MediaMeasurementManager,
- private val notifLockscreenUserManager: NotificationLockscreenUserManager
+ private val notifLockscreenUserManager: NotificationLockscreenUserManager,
+ wakefulnessLifecycle: WakefulnessLifecycle
) {
/**
* The root overlay of the hierarchy. This is where the media notification is attached to
@@ -56,23 +59,31 @@
* view is always in its final state when it is attached to a view host.
*/
private var rootOverlay: ViewGroupOverlay? = null
- private lateinit var currentState: MediaState
- private val mediaCarousel
+
+ private var rootView: View? = null
+ private var currentBounds = Rect()
+ private var animationStartBounds: Rect = Rect()
+ private var targetBounds: Rect = Rect()
+ private val mediaFrame
get() = mediaViewManager.mediaFrame
- private var animationStartState: MediaState? = null
private var statusbarState: Int = statusBarStateController.state
private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply {
interpolator = Interpolators.FAST_OUT_SLOW_IN
addUpdateListener {
updateTargetState()
- applyState(animationStartState!!.interpolate(targetState!!, animatedFraction))
+ interpolateBounds(animationStartBounds, targetBounds, animatedFraction,
+ result = currentBounds)
+ applyState(currentBounds)
}
addListener(object : AnimatorListenerAdapter() {
private var cancelled: Boolean = false
override fun onAnimationCancel(animation: Animator?) {
cancelled = true
+ animationPending = false
+ rootView?.removeCallbacks(startAnimation)
}
+
override fun onAnimationEnd(animation: Animator?) {
if (!cancelled) {
applyTargetStateIfNotAnimating()
@@ -81,30 +92,41 @@
override fun onAnimationStart(animation: Animator?) {
cancelled = false
+ animationPending = false
}
})
}
- private var targetState: MediaState? = null
- private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
+ private val mediaHosts = arrayOfNulls<MediaHost>(LOCATION_LOCKSCREEN + 1)
/**
* The last location where this view was at before going to the desired location. This is
* useful for guided transitions.
*/
- @MediaLocation private var previousLocation = -1
-
+ @MediaLocation
+ private var previousLocation = -1
/**
* The desired location where the view will be at the end of the transition.
*/
- @MediaLocation private var desiredLocation = -1
+ @MediaLocation
+ private var desiredLocation = -1
/**
* The current attachment location where the view is currently attached.
* Usually this matches the desired location except for animations whenever a view moves
* to the new desired location, during which it is in [IN_OVERLAY].
*/
- @MediaLocation private var currentAttachmentLocation = -1
+ @MediaLocation
+ private var currentAttachmentLocation = -1
+ /**
+ * Are we currently waiting on an animation to start?
+ */
+ private var animationPending: Boolean = false
+ private val startAnimation: Runnable = Runnable { animator.start() }
+
+ /**
+ * The expansion of quick settings
+ */
var qsExpansion: Float = 0.0f
set(value) {
if (field != value) {
@@ -117,6 +139,40 @@
}
}
+ /**
+ * Are location changes currently blocked?
+ */
+ private val blockLocationChanges: Boolean
+ get() {
+ return goingToSleep || dozeAnimationRunning
+ }
+
+ /**
+ * Are we currently going to sleep
+ */
+ private var goingToSleep: Boolean = false
+ set(value) {
+ if (field != value) {
+ field = value
+ if (!value) {
+ updateDesiredLocation()
+ }
+ }
+ }
+
+ /**
+ * Is the doze animation currently Running
+ */
+ private var dozeAnimationRunning: Boolean = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ if (!value) {
+ updateDesiredLocation()
+ }
+ }
+ }
+
init {
statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
override fun onStatePreChange(oldState: Int, newState: Int) {
@@ -129,6 +185,34 @@
override fun onStateChanged(newState: Int) {
updateTargetState()
}
+
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ dozeAnimationRunning = linear != 0.0f && linear != 1.0f
+ }
+
+ override fun onDozingChanged(isDozing: Boolean) {
+ if (!isDozing) {
+ dozeAnimationRunning = false
+ }
+ }
+ })
+
+ wakefulnessLifecycle.addObserver(object : WakefulnessLifecycle.Observer {
+ override fun onFinishedGoingToSleep() {
+ goingToSleep = false
+ }
+
+ override fun onStartedGoingToSleep() {
+ goingToSleep = true
+ }
+
+ override fun onFinishedWakingUp() {
+ goingToSleep = false
+ }
+
+ override fun onStartedWakingUp() {
+ goingToSleep = false
+ }
})
}
@@ -138,8 +222,8 @@
*
* @return the hostView associated with this location
*/
- fun register(mediaObject: MediaHost): ViewGroup {
- val viewHost = createUniqueObjectHost(mediaObject)
+ fun register(mediaObject: MediaHost): UniqueObjectHostView {
+ val viewHost = createUniqueObjectHost()
mediaObject.hostView = viewHost
mediaHosts[mediaObject.location] = mediaObject
if (mediaObject.location == desiredLocation) {
@@ -154,22 +238,13 @@
return viewHost
}
- private fun createUniqueObjectHost(host: MediaHost): UniqueObjectHostView {
+ private fun createUniqueObjectHost(): UniqueObjectHostView {
val viewHost = UniqueObjectHostView(context)
- viewHost.measurementCache = mediaMeasurementProvider.obtainCache(host)
- viewHost.onMeasureListener = { input ->
- if (host.location == desiredLocation) {
- // Measurement of the currently active player is happening, Let's make
- // sure the player width is up to date
- val measuringInput = host.getMeasuringInput(input)
- mediaViewManager.setPlayerWidth(measuringInput.width)
- }
- }
-
viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(p0: View?) {
if (rootOverlay == null) {
- rootOverlay = (viewHost.viewRootImpl.view.overlay as ViewGroupOverlay)
+ rootView = viewHost.viewRootImpl.view
+ rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
}
viewHost.removeOnAttachStateChangeListener(this)
}
@@ -195,8 +270,9 @@
// Let's perform a transition
val animate = shouldAnimateTransition(desiredLocation, previousLocation)
val (animDuration, delay) = getAnimationParams(previousLocation, desiredLocation)
- mediaViewManager.onDesiredLocationChanged(getHost(desiredLocation)?.currentState,
- animate, animDuration, delay)
+ val host = getHost(desiredLocation)
+ mediaViewManager.onDesiredLocationChanged(desiredLocation, host, animate, animDuration,
+ delay)
performTransitionToNewLocation(isNewView, animate)
}
}
@@ -222,14 +298,18 @@
// Let's animate to the new position, starting from the current position
// We also go in here in case the view was detached, since the bounds wouldn't
// be correct anymore
- animationStartState = currentState.copy()
+ animationStartBounds.set(currentBounds)
} else {
// otherwise, let's take the freshest state, since the current one could
// be outdated
- animationStartState = previousHost.currentState.copy()
+ animationStartBounds.set(previousHost.currentBounds)
}
adjustAnimatorForTransition(desiredLocation, previousLocation)
- animator.start()
+ rootView?.let {
+ // Let's delay the animation start until we finished laying out
+ animationPending = true
+ it.postOnAnimation(startAnimation)
+ }
} else {
cancelAnimationAndApplyDesiredState()
}
@@ -239,6 +319,9 @@
@MediaLocation currentLocation: Int,
@MediaLocation previousLocation: Int
): Boolean {
+ if (isCurrentlyInGuidedTransformation()) {
+ return false
+ }
if (currentLocation == LOCATION_QQS &&
previousLocation == LOCATION_LOCKSCREEN &&
(statusBarStateController.leaveOpenOnKeyguardHide() ||
@@ -247,7 +330,7 @@
// non-trivial reattaching logic happening that will make the view not-shown earlier
return true
}
- return mediaCarousel.isShown || animator.isRunning
+ return mediaFrame.isShown || animator.isRunning || animationPending
}
private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
@@ -279,7 +362,7 @@
// Let's immediately apply the target state (which is interpolated) if there is
// no animation running. Otherwise the animation update will already update
// the location
- applyState(targetState!!)
+ applyState(targetBounds)
}
}
@@ -291,14 +374,34 @@
val progress = getTransformationProgress()
val currentHost = getHost(desiredLocation)!!
val previousHost = getHost(previousLocation)!!
- val newState = currentHost.currentState
- val previousState = previousHost.currentState
- targetState = previousState.interpolate(newState, progress)
+ val newBounds = currentHost.currentBounds
+ val previousBounds = previousHost.currentBounds
+ targetBounds = interpolateBounds(previousBounds, newBounds, progress)
} else {
- targetState = getHost(desiredLocation)?.currentState
+ val bounds = getHost(desiredLocation)?.currentBounds ?: return
+ targetBounds.set(bounds)
}
}
+ private fun interpolateBounds(
+ startBounds: Rect,
+ endBounds: Rect,
+ progress: Float,
+ result: Rect? = null
+ ): Rect {
+ val left = MathUtils.lerp(startBounds.left.toFloat(),
+ endBounds.left.toFloat(), progress).toInt()
+ val top = MathUtils.lerp(startBounds.top.toFloat(),
+ endBounds.top.toFloat(), progress).toInt()
+ val right = MathUtils.lerp(startBounds.right.toFloat(),
+ endBounds.right.toFloat(), progress).toInt()
+ val bottom = MathUtils.lerp(startBounds.bottom.toFloat(),
+ endBounds.bottom.toFloat(), progress).toInt()
+ val resultBounds = result ?: Rect()
+ resultBounds.set(left, top, right, bottom)
+ return resultBounds
+ }
+
/**
* @return true if this transformation is guided by an external progress like a finger
*/
@@ -339,21 +442,27 @@
private fun cancelAnimationAndApplyDesiredState() {
animator.cancel()
getHost(desiredLocation)?.let {
- applyState(it.currentState)
+ applyState(it.currentBounds, immediately = true)
}
}
- private fun applyState(state: MediaState) {
- currentState = state.copy()
- mediaViewManager.setCurrentState(currentState)
+ /**
+ * Apply the current state to the view, updating it's bounds and desired state
+ */
+ private fun applyState(bounds: Rect, immediately: Boolean = false) {
+ currentBounds.set(bounds)
+ val currentlyInGuidedTransformation = isCurrentlyInGuidedTransformation()
+ val startLocation = if (currentlyInGuidedTransformation) previousLocation else -1
+ val progress = if (currentlyInGuidedTransformation) getTransformationProgress() else 1.0f
+ val endLocation = desiredLocation
+ mediaViewManager.setCurrentState(startLocation, endLocation, progress, immediately)
updateHostAttachment()
if (currentAttachmentLocation == IN_OVERLAY) {
- val boundsOnScreen = state.boundsOnScreen
- mediaCarousel.setLeftTopRightBottom(
- boundsOnScreen.left,
- boundsOnScreen.top,
- boundsOnScreen.right,
- boundsOnScreen.bottom)
+ mediaFrame.setLeftTopRightBottom(
+ currentBounds.left,
+ currentBounds.top,
+ currentBounds.right,
+ currentBounds.bottom)
}
}
@@ -364,26 +473,29 @@
currentAttachmentLocation = newLocation
// Remove the carousel from the old host
- (mediaCarousel.parent as ViewGroup?)?.removeView(mediaCarousel)
+ (mediaFrame.parent as ViewGroup?)?.removeView(mediaFrame)
// Add it to the new one
val targetHost = getHost(desiredLocation)!!.hostView
if (inOverlay) {
- rootOverlay!!.add(mediaCarousel)
+ rootOverlay!!.add(mediaFrame)
} else {
- targetHost.addView(mediaCarousel)
- mediaViewManager.onViewReattached()
+ targetHost.addView(mediaFrame)
}
}
}
private fun isTransitionRunning(): Boolean {
return isCurrentlyInGuidedTransformation() && getTransformationProgress() != 1.0f ||
- animator.isRunning
+ animator.isRunning || animationPending
}
@MediaLocation
private fun calculateLocation(): Int {
+ if (blockLocationChanges) {
+ // Keep the current location until we're allowed to again
+ return desiredLocation
+ }
val onLockscreen = (!bypassController.bypassEnabled &&
(statusbarState == StatusBarState.KEYGUARD ||
statusbarState == StatusBarState.FULLSCREEN_USER_SWITCHER))
@@ -396,13 +508,6 @@
}
}
- /**
- * The expansion of quick settings
- */
- @IntDef(prefix = ["LOCATION_"], value = [LOCATION_QS, LOCATION_QQS, LOCATION_LOCKSCREEN])
- @Retention(AnnotationRetention.SOURCE)
- annotation class MediaLocation
-
companion object {
/**
* Attached in expanded quick settings
@@ -425,3 +530,8 @@
const val IN_OVERLAY = -1000
}
}
+
+@IntDef(prefix = ["LOCATION_"], value = [MediaHierarchyManager.LOCATION_QS,
+ MediaHierarchyManager.LOCATION_QQS, MediaHierarchyManager.LOCATION_LOCKSCREEN])
+@Retention(AnnotationRetention.SOURCE)
+annotation class MediaLocation
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 240e44c..e904e93 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -1,20 +1,22 @@
package com.android.systemui.media
import android.graphics.Rect
-import android.util.MathUtils
import android.view.View
import android.view.View.OnAttachStateChangeListener
-import android.view.ViewGroup
-import com.android.systemui.media.MediaHierarchyManager.MediaLocation
import com.android.systemui.util.animation.MeasurementInput
+import com.android.systemui.util.animation.MeasurementOutput
+import com.android.systemui.util.animation.UniqueObjectHostView
+import java.util.Objects
import javax.inject.Inject
class MediaHost @Inject constructor(
- private val state: MediaHostState,
+ private val state: MediaHostStateHolder,
private val mediaHierarchyManager: MediaHierarchyManager,
- private val mediaDataManager: MediaDataManager
-) : MediaState by state {
- lateinit var hostView: ViewGroup
+ private val mediaDataManager: MediaDataManager,
+ private val mediaDataManagerCombineLatest: MediaDataCombineLatest,
+ private val mediaHostStatesManager: MediaHostStatesManager
+) : MediaHostState by state {
+ lateinit var hostView: UniqueObjectHostView
var location: Int = -1
private set
var visibleChangedListener: ((Boolean) -> Unit)? = null
@@ -24,9 +26,9 @@
private val tmpLocationOnScreen: IntArray = intArrayOf(0, 0)
/**
- * Get the current Media state. This also updates the location on screen
+ * Get the current bounds on the screen. This makes sure the state is fresh and up to date
*/
- val currentState: MediaState
+ val currentBounds: Rect = Rect()
get() {
hostView.getLocationOnScreen(tmpLocationOnScreen)
var left = tmpLocationOnScreen[0] + hostView.paddingLeft
@@ -43,8 +45,8 @@
bottom = 0
top = 0
}
- state.boundsOnScreen.set(left, top, right, bottom)
- return state
+ field.set(left, top, right, bottom)
+ return field
}
private val listener = object : MediaDataManager.Listener {
@@ -59,6 +61,8 @@
/**
* Initialize this MediaObject and create a host view.
+ * All state should already be set on this host before calling this method in order to avoid
+ * unnecessary state changes which lead to remeasurings later on.
*
* @param location the location this host name has. Used to identify the host during
* transitions.
@@ -68,14 +72,39 @@
hostView = mediaHierarchyManager.register(this)
hostView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
- mediaDataManager.addListener(listener)
+ // we should listen to the combined state change, since otherwise there might
+ // be a delay until the views and the controllers are initialized, leaving us
+ // with either a blank view or the controllers not yet initialized and the
+ // measuring wrong
+ mediaDataManagerCombineLatest.addListener(listener)
updateViewVisibility()
}
override fun onViewDetachedFromWindow(v: View?) {
- mediaDataManager.removeListener(listener)
+ mediaDataManagerCombineLatest.removeListener(listener)
}
})
+
+ // Listen to measurement updates and update our state with it
+ hostView.measurementManager = object : UniqueObjectHostView.MeasurementManager {
+ override fun onMeasure(input: MeasurementInput): MeasurementOutput {
+ // Modify the measurement to exactly match the dimensions
+ if (View.MeasureSpec.getMode(input.widthMeasureSpec) == View.MeasureSpec.AT_MOST) {
+ input.widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(
+ View.MeasureSpec.getSize(input.widthMeasureSpec),
+ View.MeasureSpec.EXACTLY)
+ }
+ // This will trigger a state change that ensures that we now have a state available
+ state.measurementInput = input
+ return mediaHostStatesManager.getPlayerDimensions(state)
+ }
+ }
+
+ // Whenever the state changes, let our state manager know
+ state.changedListener = {
+ mediaHostStatesManager.updateHostState(location, state)
+ }
+
updateViewVisibility()
}
@@ -89,71 +118,93 @@
visibleChangedListener?.invoke(visible)
}
- class MediaHostState @Inject constructor() : MediaState {
- var measurementInput: MediaMeasurementInput? = null
- override var expansion: Float = 0.0f
- override var showsOnlyActiveMedia: Boolean = false
- override val boundsOnScreen: Rect = Rect()
+ class MediaHostStateHolder @Inject constructor() : MediaHostState {
- override fun copy(): MediaState {
- val mediaHostState = MediaHostState()
+ override var measurementInput: MeasurementInput? = null
+ set(value) {
+ if (value?.equals(field) != true) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
+ override var expansion: Float = 0.0f
+ set(value) {
+ if (!value.equals(field)) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
+ override var showsOnlyActiveMedia: Boolean = false
+ set(value) {
+ if (!value.equals(field)) {
+ field = value
+ changedListener?.invoke()
+ }
+ }
+
+ /**
+ * A listener for all changes. This won't be copied over when invoking [copy]
+ */
+ var changedListener: (() -> Unit)? = null
+
+ /**
+ * Get a copy of this state. This won't copy any listeners it may have set
+ */
+ override fun copy(): MediaHostState {
+ val mediaHostState = MediaHostStateHolder()
mediaHostState.expansion = expansion
mediaHostState.showsOnlyActiveMedia = showsOnlyActiveMedia
- mediaHostState.boundsOnScreen.set(boundsOnScreen)
- mediaHostState.measurementInput = measurementInput
+ mediaHostState.measurementInput = measurementInput?.copy()
return mediaHostState
}
- override fun interpolate(other: MediaState, amount: Float): MediaState {
- val result = MediaHostState()
- result.expansion = MathUtils.lerp(expansion, other.expansion, amount)
- val left = MathUtils.lerp(boundsOnScreen.left.toFloat(),
- other.boundsOnScreen.left.toFloat(), amount).toInt()
- val top = MathUtils.lerp(boundsOnScreen.top.toFloat(),
- other.boundsOnScreen.top.toFloat(), amount).toInt()
- val right = MathUtils.lerp(boundsOnScreen.right.toFloat(),
- other.boundsOnScreen.right.toFloat(), amount).toInt()
- val bottom = MathUtils.lerp(boundsOnScreen.bottom.toFloat(),
- other.boundsOnScreen.bottom.toFloat(), amount).toInt()
- result.boundsOnScreen.set(left, top, right, bottom)
- result.showsOnlyActiveMedia = other.showsOnlyActiveMedia || showsOnlyActiveMedia
- if (amount > 0.0f) {
- if (other is MediaHostState) {
- result.measurementInput = other.measurementInput
- }
- } else {
- result.measurementInput
+ override fun equals(other: Any?): Boolean {
+ if (!(other is MediaHostState)) {
+ return false
}
+ if (!Objects.equals(measurementInput, other.measurementInput)) {
+ return false
+ }
+ if (expansion != other.expansion) {
+ return false
+ }
+ if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) {
+ return false
+ }
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = measurementInput?.hashCode() ?: 0
+ result = 31 * result + expansion.hashCode()
+ result = 31 * result + showsOnlyActiveMedia.hashCode()
return result
}
-
- override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput {
- measurementInput = MediaMeasurementInput(input, expansion)
- return measurementInput as MediaMeasurementInput
- }
}
}
-interface MediaState {
+interface MediaHostState {
+
+ /**
+ * The last measurement input that this state was measured with. Infers with and height of
+ * the players.
+ */
+ var measurementInput: MeasurementInput?
+
+ /**
+ * The expansion of the player, 0 for fully collapsed, 1 for fully expanded
+ */
var expansion: Float
- var showsOnlyActiveMedia: Boolean
- val boundsOnScreen: Rect
- fun copy(): MediaState
- fun interpolate(other: MediaState, amount: Float): MediaState
- fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput
-}
-/**
- * The measurement input for a Media View
- */
-data class MediaMeasurementInput(
- private val viewInput: MeasurementInput,
- val expansion: Float
-) : MeasurementInput by viewInput {
- override fun sameAs(input: MeasurementInput?): Boolean {
- if (!(input is MediaMeasurementInput)) {
- return false
- }
- return width == input.width && expansion == input.expansion
- }
+ /**
+ * Is this host only showing active media or is it showing all of them including resumption?
+ */
+ var showsOnlyActiveMedia: Boolean
+
+ /**
+ * Get a copy of this view state, deepcopying all appropriate members
+ */
+ fun copy(): MediaHostState
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
new file mode 100644
index 0000000..f90af2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2020 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.media
+
+import com.android.systemui.util.animation.MeasurementOutput
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * A class responsible for managing all media host states of the various host locations and
+ * coordinating the heights among different players. This class can be used to get the most up to
+ * date state for any location.
+ */
+@Singleton
+class MediaHostStatesManager @Inject constructor() {
+
+ private val callbacks: MutableSet<Callback> = mutableSetOf()
+ private val controllers: MutableSet<MediaViewController> = mutableSetOf()
+
+ /**
+ * A map with all media states of all locations.
+ */
+ val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
+
+ /**
+ * Notify that a media state for a given location has changed. Should only be called from
+ * Media hosts themselves.
+ */
+ fun updateHostState(@MediaLocation location: Int, hostState: MediaHostState) {
+ val currentState = mediaHostStates.get(location)
+ if (!hostState.equals(currentState)) {
+ val newState = hostState.copy()
+ mediaHostStates.put(location, newState)
+ // First update all the controllers to ensure they get the chance to measure
+ for (controller in controllers) {
+ controller.stateCallback.onHostStateChanged(location, newState)
+ }
+
+ // Then update all other callbacks which may depend on the controllers above
+ for (callback in callbacks) {
+ callback.onHostStateChanged(location, newState)
+ }
+ }
+ }
+
+ /**
+ * Get the dimensions of all players combined, which determines the overall height of the
+ * media carousel and the media hosts.
+ */
+ fun getPlayerDimensions(hostState: MediaHostState): MeasurementOutput {
+ val result = MeasurementOutput(0, 0)
+ for (controller in controllers) {
+ val measurement = controller.getMeasurementsForState(hostState)
+ measurement?.let {
+ if (it.measuredHeight > result.measuredHeight) {
+ result.measuredHeight = it.measuredHeight
+ }
+ if (it.measuredWidth > result.measuredWidth) {
+ result.measuredWidth = it.measuredWidth
+ }
+ }
+ }
+ return result
+ }
+
+ /**
+ * Add a callback to be called when a MediaState has updated
+ */
+ fun addCallback(callback: Callback) {
+ callbacks.add(callback)
+ }
+
+ /**
+ * Remove a callback that listens to media states
+ */
+ fun removeCallback(callback: Callback) {
+ callbacks.remove(callback)
+ }
+
+ /**
+ * Register a controller that listens to media states and is used to determine the size of
+ * the media carousel
+ */
+ fun addController(controller: MediaViewController) {
+ controllers.add(controller)
+ }
+
+ /**
+ * Notify the manager about the removal of a controller.
+ */
+ fun removeController(controller: MediaViewController) {
+ controllers.remove(controller)
+ }
+
+ interface Callback {
+ /**
+ * Notify the callbacks that a media state for a host has changed, and that the
+ * corresponding view states should be updated and applied
+ */
+ fun onHostStateChanged(@MediaLocation location: Int, mediaHostState: MediaHostState)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
deleted file mode 100644
index 4bbf5eb..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/MediaMeasurementManager.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2020 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.media
-
-import com.android.systemui.util.animation.BaseMeasurementCache
-import com.android.systemui.util.animation.GuaranteedMeasurementCache
-import com.android.systemui.util.animation.MeasurementCache
-import com.android.systemui.util.animation.MeasurementInput
-import com.android.systemui.util.animation.MeasurementOutput
-import javax.inject.Inject
-import javax.inject.Singleton
-
-/**
- * A class responsible creating measurement caches for media hosts which also coordinates with
- * the view manager to obtain sizes for unknown measurement inputs.
- */
-@Singleton
-class MediaMeasurementManager @Inject constructor(
- private val mediaViewManager: MediaViewManager
-) {
- private val baseCache: MeasurementCache
-
- init {
- baseCache = BaseMeasurementCache()
- }
-
- private fun provideMeasurement(input: MediaMeasurementInput) : MeasurementOutput? {
- return mediaViewManager.obtainMeasurement(input)
- }
-
- /**
- * Obtain a guaranteed measurement cache for a host view. The measurement cache makes sure that
- * requesting any size from the cache will always return the correct value.
- */
- fun obtainCache(host: MediaState): GuaranteedMeasurementCache {
- val remapper = { input: MeasurementInput ->
- host.getMeasuringInput(input)
- }
- val provider = { input: MeasurementInput ->
- provideMeasurement(input as MediaMeasurementInput)
- }
- return GuaranteedMeasurementCache(baseCache, remapper, provider)
- }
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
new file mode 100644
index 0000000..e82bb40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2020 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.media
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.util.animation.TransitionLayout
+import com.android.systemui.util.animation.TransitionLayoutController
+import com.android.systemui.util.animation.TransitionViewState
+import com.android.systemui.util.animation.MeasurementOutput
+
+/**
+ * A class responsible for controlling a single instance of a media player handling interactions
+ * with the view instance and keeping the media view states up to date.
+ */
+class MediaViewController(
+ context: Context,
+ val mediaHostStatesManager: MediaHostStatesManager
+) {
+
+ private var firstRefresh: Boolean = true
+ private var transitionLayout: TransitionLayout? = null
+ private val layoutController = TransitionLayoutController()
+ private var animationDelay: Long = 0
+ private var animationDuration: Long = 0
+ private var animateNextStateChange: Boolean = false
+ private val measurement = MeasurementOutput(0, 0)
+
+ /**
+ * A map containing all viewStates for all locations of this mediaState
+ */
+ private val mViewStates: MutableMap<MediaHostState, TransitionViewState?> = mutableMapOf()
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ private var currentEndLocation: Int = -1
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ private var currentStartLocation: Int = -1
+
+ /**
+ * The progress of the transition or 1.0 if there is no transition happening
+ */
+ private var currentTransitionProgress: Float = 1.0f
+
+ /**
+ * A temporary state used to store intermediate measurements.
+ */
+ private val tmpState = TransitionViewState()
+
+ /**
+ * A callback for media state changes
+ */
+ val stateCallback = object : MediaHostStatesManager.Callback {
+ override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+ if (location == currentEndLocation || location == currentStartLocation) {
+ setCurrentState(currentStartLocation,
+ currentEndLocation,
+ currentTransitionProgress,
+ applyImmediately = false)
+ }
+ }
+ }
+
+ /**
+ * The expanded constraint set used to render a expanded player. If it is modified, make sure
+ * to call [refreshState]
+ */
+ val collapsedLayout = ConstraintSet()
+
+ /**
+ * The expanded constraint set used to render a collapsed player. If it is modified, make sure
+ * to call [refreshState]
+ */
+ val expandedLayout = ConstraintSet()
+
+ init {
+ collapsedLayout.load(context, R.xml.media_collapsed)
+ expandedLayout.load(context, R.xml.media_expanded)
+ mediaHostStatesManager.addController(this)
+ }
+
+ /**
+ * Notify this controller that the view has been removed and all listeners should be destroyed
+ */
+ fun onDestroy() {
+ mediaHostStatesManager.removeController(this)
+ }
+
+ private fun ensureAllMeasurements() {
+ val mediaStates = mediaHostStatesManager.mediaHostStates
+ for (entry in mediaStates) {
+ obtainViewState(entry.value)
+ }
+ }
+
+ /**
+ * Get the constraintSet for a given expansion
+ */
+ private fun constraintSetForExpansion(expansion: Float): ConstraintSet =
+ if (expansion > 0) expandedLayout else collapsedLayout
+
+ /**
+ * Obtain a new viewState for a given media state. This usually returns a cached state, but if
+ * it's not available, it will recreate one by measuring, which may be expensive.
+ */
+ private fun obtainViewState(state: MediaHostState): TransitionViewState? {
+ val viewState = mViewStates[state]
+ if (viewState != null) {
+ // we already have cached this measurement, let's continue
+ return viewState
+ }
+
+ val result: TransitionViewState?
+ if (transitionLayout != null && state.measurementInput != null) {
+ // Let's create a new measurement
+ if (state.expansion == 0.0f || state.expansion == 1.0f) {
+ result = transitionLayout!!.calculateViewState(
+ state.measurementInput!!,
+ constraintSetForExpansion(state.expansion),
+ TransitionViewState())
+
+ // We don't want to cache interpolated or null states as this could quickly fill up
+ // our cache. We only cache the start and the end states since the interpolation
+ // is cheap
+ mViewStates[state.copy()] = result
+ } else {
+ // This is an interpolated state
+ val startState = state.copy().also { it.expansion = 0.0f }
+
+ // Given that we have a measurement and a view, let's get (guaranteed) viewstates
+ // from the start and end state and interpolate them
+ val startViewState = obtainViewState(startState) as TransitionViewState
+ val endState = state.copy().also { it.expansion = 1.0f }
+ val endViewState = obtainViewState(endState) as TransitionViewState
+ result = TransitionViewState()
+ layoutController.getInterpolatedState(
+ startViewState,
+ endViewState,
+ state.expansion,
+ result)
+ }
+ } else {
+ result = null
+ }
+ return result
+ }
+
+ /**
+ * Attach a view to this controller. This may perform measurements if it's not available yet
+ * and should therefore be done carefully.
+ */
+ fun attach(transitionLayout: TransitionLayout) {
+ this.transitionLayout = transitionLayout
+ layoutController.attach(transitionLayout)
+ ensureAllMeasurements()
+ if (currentEndLocation == -1) {
+ return
+ }
+ // Set the previously set state immediately to the view, now that it's finally attached
+ setCurrentState(
+ startLocation = currentStartLocation,
+ endLocation = currentEndLocation,
+ transitionProgress = currentTransitionProgress,
+ applyImmediately = true)
+ }
+
+ /**
+ * Obtain a measurement for a given location. This makes sure that the state is up to date
+ * and all widgets know their location. Calling this method may create a measurement if we
+ * don't have a cached value available already.
+ */
+ fun getMeasurementsForState(hostState: MediaHostState): MeasurementOutput? {
+ val viewState = obtainViewState(hostState) ?: return null
+ measurement.measuredWidth = viewState.width
+ measurement.measuredHeight = viewState.height
+ return measurement
+ }
+
+ /**
+ * Set a new state for the controlled view which can be an interpolation between multiple
+ * locations.
+ */
+ fun setCurrentState(
+ @MediaLocation startLocation: Int,
+ @MediaLocation endLocation: Int,
+ transitionProgress: Float,
+ applyImmediately: Boolean
+ ) {
+ currentEndLocation = endLocation
+ currentStartLocation = startLocation
+ currentTransitionProgress = transitionProgress
+
+ val shouldAnimate = animateNextStateChange && !applyImmediately
+
+ // Obtain the view state that we'd want to be at the end
+ // The view might not be bound yet or has never been measured and in that case will be
+ // reset once the state is fully available
+ val endState = obtainViewStateForLocation(endLocation) ?: return
+ layoutController.setMeasureState(endState)
+
+ // If the view isn't bound, we can drop the animation, otherwise we'll executute it
+ animateNextStateChange = false
+ if (transitionLayout == null) {
+ return
+ }
+
+ val startState = obtainViewStateForLocation(startLocation)
+ val result: TransitionViewState?
+ if (transitionProgress == 1.0f || startState == null) {
+ result = endState
+ } else if (transitionProgress == 0.0f) {
+ result = startState
+ } else {
+ layoutController.getInterpolatedState(startState, endState, transitionProgress,
+ tmpState)
+ result = tmpState
+ }
+ layoutController.setState(result, applyImmediately, shouldAnimate, animationDuration,
+ animationDelay)
+ }
+
+ private fun obtainViewStateForLocation(location: Int): TransitionViewState? {
+ val mediaState = mediaHostStatesManager.mediaHostStates[location] ?: return null
+ return obtainViewState(mediaState)
+ }
+
+ /**
+ * Notify that the location is changing right now and a [setCurrentState] change is imminent.
+ * This updates the width the view will me measured with.
+ */
+ fun onLocationPreChange(@MediaLocation newLocation: Int) {
+ val viewState = obtainViewStateForLocation(newLocation)
+ viewState?.let {
+ layoutController.setMeasureState(it)
+ }
+ }
+
+ /**
+ * Request that the next state change should be animated with the given parameters.
+ */
+ fun animatePendingStateChange(duration: Long, delay: Long) {
+ animateNextStateChange = true
+ animationDuration = duration
+ animationDelay = delay
+ }
+
+ /**
+ * Clear all existing measurements and refresh the state to match the view.
+ */
+ fun refreshState() {
+ if (!firstRefresh) {
+ // Let's clear all of our measurements and recreate them!
+ mViewStates.clear()
+ setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
+ applyImmediately = false)
+ }
+ firstRefresh = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
index 16302d1..8ab30c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt
@@ -16,8 +16,8 @@
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.PageIndicator
import com.android.systemui.statusbar.notification.VisualStabilityManager
-import com.android.systemui.util.animation.MeasurementOutput
import com.android.systemui.util.animation.UniqueObjectHostView
+import com.android.systemui.util.animation.requiresRemeasuring
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -36,16 +36,51 @@
@Background private val backgroundExecutor: DelayableExecutor,
private val visualStabilityManager: VisualStabilityManager,
private val activityStarter: ActivityStarter,
+ private val mediaHostStatesManager: MediaHostStatesManager,
mediaManager: MediaDataCombineLatest
) {
- private var playerWidth: Int = 0
+
+ /**
+ * The desired location where we'll be at the end of the transformation. Usually this matches
+ * the end location, except when we're still waiting on a state update call.
+ */
+ @MediaLocation
+ private var desiredLocation: Int = -1
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ @MediaLocation
+ private var currentEndLocation: Int = -1
+
+ /**
+ * The ending location of the view where it ends when all animations and transitions have
+ * finished
+ */
+ @MediaLocation
+ private var currentStartLocation: Int = -1
+
+ /**
+ * The progress of the transition or 1.0 if there is no transition happening
+ */
+ private var currentTransitionProgress: Float = 1.0f
+
+ /**
+ * The measured width of the carousel
+ */
+ private var carouselMeasureWidth: Int = 0
+
+ /**
+ * The measured height of the carousel
+ */
+ private var carouselMeasureHeight: Int = 0
private var playerWidthPlusPadding: Int = 0
- private var desiredState: MediaHost.MediaHostState? = null
- private var currentState: MediaState? = null
+ private var desiredHostState: MediaHostState? = null
private val mediaCarousel: HorizontalScrollView
val mediaFrame: ViewGroup
+ val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
private val mediaContent: ViewGroup
- private val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf()
private val pageIndicator: PageIndicator
private val gestureDetector: GestureDetectorCompat
private val visualStabilityCallback: VisualStabilityManager.Callback
@@ -115,6 +150,7 @@
override fun onMediaDataLoaded(key: String, data: MediaData) {
updateView(key, data)
updatePlayerVisibilities()
+ mediaCarousel.requiresRemeasuring = true
}
override fun onMediaDataRemoved(key: String) {
@@ -137,6 +173,13 @@
}
}
})
+ mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback {
+ override fun onHostStateChanged(location: Int, mediaHostState: MediaHostState) {
+ if (location == desiredLocation) {
+ onDesiredLocationChanged(desiredLocation, mediaHostState, animate = false)
+ }
+ }
+ })
}
private fun inflateMediaCarousel(): ViewGroup {
@@ -220,7 +263,7 @@
var existingPlayer = mediaPlayers[key]
if (existingPlayer == null) {
existingPlayer = MediaControlPanel(context, foregroundExecutor, backgroundExecutor,
- activityStarter)
+ activityStarter, mediaHostStatesManager)
existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context),
mediaContent))
mediaPlayers[key] = existingPlayer
@@ -228,14 +271,14 @@
ViewGroup.LayoutParams.WRAP_CONTENT)
existingPlayer.view?.player?.setLayoutParams(lp)
existingPlayer.setListening(currentlyExpanded)
+ updatePlayerToState(existingPlayer, noAnimation = true)
if (existingPlayer.isPlaying) {
mediaContent.addView(existingPlayer.view?.player, 0)
} else {
mediaContent.addView(existingPlayer.view?.player)
}
- updatePlayerToCurrentState(existingPlayer)
} else if (existingPlayer.isPlaying &&
- mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+ mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
if (visualStabilityManager.isReorderingAllowed) {
mediaContent.removeView(existingPlayer.view?.player)
mediaContent.addView(existingPlayer.view?.player, 0)
@@ -244,20 +287,10 @@
}
}
existingPlayer.bind(data)
- // Resetting the progress to make sure it's taken into account for the latest
- // motion model
- existingPlayer.view?.player?.progress = currentState?.expansion ?: 0.0f
updateMediaPaddings()
updatePageIndicator()
}
- private fun updatePlayerToCurrentState(existingPlayer: MediaControlPanel) {
- if (desiredState != null && desiredState!!.measurementInput != null) {
- // make sure the player width is set to the current state
- existingPlayer.setPlayerWidth(playerWidth)
- }
- }
-
private fun updateMediaPaddings() {
val padding = context.resources.getDimensionPixelSize(R.dimen.qs_media_padding)
val childCount = mediaContent.childCount
@@ -281,117 +314,101 @@
}
/**
- * Set the current state of a view. This is updated often during animations and we shouldn't
- * do anything expensive.
+ * Set a new interpolated state for all players. This is a state that is usually controlled
+ * by a finger movement where the user drags from one state to the next.
*/
- fun setCurrentState(state: MediaState) {
- currentState = state
- currentlyExpanded = state.expansion > 0
+ fun setCurrentState(
+ @MediaLocation startLocation: Int,
+ @MediaLocation endLocation: Int,
+ progress: Float,
+ immediately: Boolean
+ ) {
// Hack: Since the indicator doesn't move with the player expansion, just make it disappear
// and then reappear at the end.
- pageIndicator.alpha = if (state.expansion == 1f || state.expansion == 0f) 1f else 0f
- for (mediaPlayer in mediaPlayers.values) {
- val view = mediaPlayer.view?.player
- view?.progress = state.expansion
+ pageIndicator.alpha = if (progress == 1f || progress == 0f) 1f else 0f
+ if (startLocation != currentStartLocation ||
+ endLocation != currentEndLocation ||
+ progress != currentTransitionProgress ||
+ immediately
+ ) {
+ currentStartLocation = startLocation
+ currentEndLocation = endLocation
+ currentTransitionProgress = progress
+ for (mediaPlayer in mediaPlayers.values) {
+ updatePlayerToState(mediaPlayer, immediately)
+ }
}
}
+ private fun updatePlayerToState(mediaPlayer: MediaControlPanel, noAnimation: Boolean) {
+ mediaPlayer.mediaViewController.setCurrentState(
+ startLocation = currentStartLocation,
+ endLocation = currentEndLocation,
+ transitionProgress = currentTransitionProgress,
+ applyImmediately = noAnimation)
+ }
+
/**
* The desired location of this view has changed. We should remeasure the view to match
* the new bounds and kick off bounds animations if necessary.
* If an animation is happening, an animation is kicked of externally, which sets a new
* current state until we reach the targetState.
*
- * @param desiredState the target state we're transitioning to
+ * @param desiredLocation the location we're going to
+ * @param desiredHostState the target state we're transitioning to
* @param animate should this be animated
*/
fun onDesiredLocationChanged(
- desiredState: MediaState?,
+ desiredLocation: Int,
+ desiredHostState: MediaHostState?,
animate: Boolean,
- duration: Long,
- startDelay: Long
+ duration: Long = 200,
+ startDelay: Long = 0
) {
- if (desiredState is MediaHost.MediaHostState) {
+ desiredHostState?.let {
// This is a hosting view, let's remeasure our players
- this.desiredState = desiredState
- val width = desiredState.boundsOnScreen.width()
- if (playerWidth != width) {
- setPlayerWidth(width)
- for (mediaPlayer in mediaPlayers.values) {
- if (animate && mediaPlayer.view?.player?.visibility == View.VISIBLE) {
- mediaPlayer.animatePendingSizeChange(duration, startDelay)
- }
- }
- val widthSpec = desiredState.measurementInput?.widthMeasureSpec ?: 0
- val heightSpec = desiredState.measurementInput?.heightMeasureSpec ?: 0
- var left = 0
- for (i in 0 until mediaContent.childCount) {
- val view = mediaContent.getChildAt(i)
- view.measure(widthSpec, heightSpec)
- view.layout(left, 0, left + width, view.measuredHeight)
- left = left + playerWidthPlusPadding
- }
- }
- }
- }
-
- fun setPlayerWidth(width: Int) {
- if (width != playerWidth) {
- playerWidth = width
- playerWidthPlusPadding = playerWidth + context.resources.getDimensionPixelSize(
- R.dimen.qs_media_padding)
+ this.desiredLocation = desiredLocation
+ this.desiredHostState = it
+ currentlyExpanded = it.expansion > 0
for (mediaPlayer in mediaPlayers.values) {
- mediaPlayer.setPlayerWidth(width)
+ if (animate) {
+ mediaPlayer.mediaViewController.animatePendingStateChange(
+ duration = duration,
+ delay = startDelay)
+ }
+ mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
}
- // The player width has changed, let's update the scroll position to make sure
- // it's still at the same place
- var newScroll = activeMediaIndex * playerWidthPlusPadding
- if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
- newScroll += playerWidthPlusPadding
- - (scrollIntoCurrentMedia - playerWidthPlusPadding)
- } else {
- newScroll += scrollIntoCurrentMedia
- }
- mediaCarousel.scrollX = newScroll
+ updateCarouselSize()
}
}
/**
- * Get a measurement for the given input state. This measures the first player and returns
- * its bounds as if it were measured with the given measurement dimensions
+ * Update the size of the carousel, remeasuring it if necessary.
*/
- fun obtainMeasurement(input: MediaMeasurementInput): MeasurementOutput? {
- val firstPlayer = mediaPlayers.values.firstOrNull() ?: return null
- var result: MeasurementOutput? = null
- firstPlayer.view?.player?.let {
- // Let's measure the size of the first player and return its height
- val previousProgress = it.progress
- val previousRight = it.right
- val previousBottom = it.bottom
- it.progress = input.expansion
- firstPlayer.measure(input)
- // Relayouting is necessary in motionlayout to obtain its size properly ....
- it.layout(0, 0, it.measuredWidth, it.measuredHeight)
- result = MeasurementOutput(it.measuredWidth, it.measuredHeight)
- it.progress = previousProgress
- if (desiredState != null) {
- // remeasure it to the old size again!
- firstPlayer.measure(desiredState!!.measurementInput)
- it.layout(0, 0, previousRight, previousBottom)
+ private fun updateCarouselSize() {
+ val width = desiredHostState?.measurementInput?.width ?: 0
+ val height = desiredHostState?.measurementInput?.height ?: 0
+ if (width != carouselMeasureWidth && width != 0 ||
+ height != carouselMeasureWidth && height != 0) {
+ carouselMeasureWidth = width
+ carouselMeasureHeight = height
+ playerWidthPlusPadding = carouselMeasureWidth + context.resources.getDimensionPixelSize(
+ R.dimen.qs_media_padding)
+ // The player width has changed, let's update the scroll position to make sure
+ // it's still at the same place
+ var newScroll = activeMediaIndex * playerWidthPlusPadding
+ if (scrollIntoCurrentMedia > playerWidthPlusPadding) {
+ newScroll += playerWidthPlusPadding -
+ (scrollIntoCurrentMedia - playerWidthPlusPadding)
+ } else {
+ newScroll += scrollIntoCurrentMedia
}
- }
- return result
- }
-
- fun onViewReattached() {
- if (desiredState is MediaHost.MediaHostState) {
- // HACK: MotionLayout doesn't always properly reevalate the state, let's kick of
- // a measure to force it.
- val widthSpec = desiredState!!.measurementInput?.widthMeasureSpec ?: 0
- val heightSpec = desiredState!!.measurementInput?.heightMeasureSpec ?: 0
- for (mediaPlayer in mediaPlayers.values) {
- mediaPlayer.view?.player?.measure(widthSpec, heightSpec)
- }
+ mediaCarousel.scrollX = newScroll
+ // Let's remeasure the carousel
+ val widthSpec = desiredHostState?.measurementInput?.widthMeasureSpec ?: 0
+ val heightSpec = desiredHostState?.measurementInput?.heightMeasureSpec ?: 0
+ mediaCarousel.measure(widthSpec, heightSpec)
+ mediaCarousel.layout(0, 0, width, mediaCarousel.measuredHeight)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 764dbe6..60c576b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -23,18 +23,15 @@
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
-
-import androidx.constraintlayout.motion.widget.MotionLayout
-
import com.android.systemui.R
+import com.android.systemui.util.animation.TransitionLayout
/**
* ViewHolder for a media player.
*/
class PlayerViewHolder private constructor(itemView: View) {
- val player = itemView as MotionLayout
- val background = itemView.requireViewById<View>(R.id.media_background)
+ val player = itemView as TransitionLayout
// Player information
val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
@@ -61,7 +58,7 @@
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
init {
- (background.background as IlluminationDrawable).let {
+ (player.background as IlluminationDrawable).let {
it.setupTouch(seamless, player)
it.setupTouch(action0, player)
it.setupTouch(action1, player)
@@ -95,7 +92,7 @@
* @param parent Parent of inflated view.
*/
@JvmStatic fun create(inflater: LayoutInflater, parent: ViewGroup): PlayerViewHolder {
- val v = inflater.inflate(R.layout.qs_media_panel, parent, false)
+ val v = inflater.inflate(R.layout.media_view, parent, false)
return PlayerViewHolder(v)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index b9b8a25..a10972e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -36,10 +36,11 @@
import android.util.TypedValue;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.IWindowManager;
-import android.view.WindowManagerGlobal;
import android.window.WindowContainerTransaction;
+import com.android.systemui.wm.DisplayController;
+import com.android.systemui.wm.DisplayLayout;
+
import java.io.PrintWriter;
import javax.inject.Inject;
@@ -56,10 +57,10 @@
private static final float INVALID_SNAP_FRACTION = -1f;
private final Context mContext;
- private final IWindowManager mWindowManager;
private final PipSnapAlgorithm mSnapAlgorithm;
private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final Rect mTmpInsets = new Rect();
+ private final DisplayController mDisplayController;
+ private final DisplayLayout mDisplayLayout;
private ComponentName mLastPipComponentName;
private float mReentrySnapFraction = INVALID_SNAP_FRACTION;
@@ -80,11 +81,24 @@
private boolean mIsShelfShowing;
private int mShelfHeight;
+ private final DisplayController.OnDisplaysChangedListener mDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (displayId == mContext.getDisplayId()) {
+ mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
+ }
+ }
+ };
+
@Inject
- public PipBoundsHandler(Context context, PipSnapAlgorithm pipSnapAlgorithm) {
+ public PipBoundsHandler(Context context, PipSnapAlgorithm pipSnapAlgorithm,
+ DisplayController displayController) {
mContext = context;
mSnapAlgorithm = pipSnapAlgorithm;
- mWindowManager = WindowManagerGlobal.getWindowManagerService();
+ mDisplayLayout = new DisplayLayout();
+ mDisplayController = displayController;
+ mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
reloadResources();
// Initialize the aspect ratio to the default aspect ratio. Don't do this in reload
// resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -272,8 +286,8 @@
*
* @return {@code true} if internal {@link DisplayInfo} is rotated, {@code false} otherwise.
*/
- public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, int displayId,
- int fromRotation, int toRotation, WindowContainerTransaction t) {
+ public boolean onDisplayRotationChanged(Rect outBounds, Rect oldBounds, Rect outInsetBounds,
+ int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) {
// Bail early if the event is not sent to current {@link #mDisplayInfo}
if ((displayId != mDisplayInfo.displayId) || (fromRotation == toRotation)) {
return false;
@@ -294,6 +308,9 @@
final Rect postChangeStackBounds = new Rect(oldBounds);
final float snapFraction = getSnapFraction(postChangeStackBounds);
+ // Update the display layout
+ mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+
// Populate the new {@link #mDisplayInfo}.
// The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
// therefore, the width/height may require a swap first.
@@ -308,6 +325,7 @@
mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds,
snapFraction);
+ getInsetBounds(outInsetBounds);
outBounds.set(postChangeStackBounds);
t.setBounds(pinnedStackInfo.stackToken, outBounds);
return true;
@@ -425,15 +443,11 @@
* Populates the bounds on the screen that the PIP can be visible in.
*/
protected void getInsetBounds(Rect outRect) {
- try {
- mWindowManager.getStableInsets(mContext.getDisplayId(), mTmpInsets);
- outRect.set(mTmpInsets.left + mScreenEdgeInsets.x,
- mTmpInsets.top + mScreenEdgeInsets.y,
- mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
- mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get stable insets from WM", e);
- }
+ Rect insets = mDisplayLayout.stableInsets();
+ outRect.set(insets.left + mScreenEdgeInsets.x,
+ insets.top + mScreenEdgeInsets.y,
+ mDisplayInfo.logicalWidth - insets.right - mScreenEdgeInsets.x,
+ mDisplayInfo.logicalHeight - insets.bottom - mScreenEdgeInsets.y);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 64df2ff..02bf745 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -95,8 +95,21 @@
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
- mPipTaskOrganizer.getLastReportedBounds(), displayId, fromRotation, toRotation, t);
+ mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds, displayId, fromRotation,
+ toRotation, t);
if (changed) {
+ // If the pip was in the offset zone earlier, adjust the new bounds to the bottom of the
+ // movement bounds
+ mTouchHandler.adjustBoundsForRotation(mTmpNormalBounds,
+ mPipTaskOrganizer.getLastReportedBounds(), mTmpInsetBounds);
+
+ // The bounds are being applied to a specific snap fraction, so reset any known offsets
+ // for the previous orientation before updating the movement bounds
+ mPipBoundsHandler.setShelfHeight(false , 0);
+ mPipBoundsHandler.onImeVisibilityChanged(false, 0);
+ mTouchHandler.onShelfVisibilityChanged(false, 0);
+ mTouchHandler.onImeVisibilityChanged(false, 0);
+
updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
}
@@ -290,9 +303,10 @@
@Override
public void setShelfHeight(boolean visible, int height) {
mHandler.post(() -> {
- final boolean changed = mPipBoundsHandler.setShelfHeight(visible, height);
+ final int shelfHeight = visible ? height : 0;
+ final boolean changed = mPipBoundsHandler.setShelfHeight(visible, shelfHeight);
if (changed) {
- mTouchHandler.onShelfVisibilityChanged(visible, height);
+ mTouchHandler.onShelfVisibilityChanged(visible, shelfHeight);
updateMovementBounds(mPipTaskOrganizer.getLastReportedBounds(),
false /* fromRotation */, false /* fromImeAdjustment */,
true /* fromShelfAdjustment */);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 3396f70..a3185a2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -99,6 +99,7 @@
public static final int MESSAGE_ANIMATION_ENDED = 6;
public static final int MESSAGE_POINTER_EVENT = 7;
public static final int MESSAGE_MENU_EXPANDED = 8;
+ public static final int MESSAGE_FADE_OUT_MENU = 9;
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
@@ -182,6 +183,10 @@
mMenuContainerAnimator.start();
break;
}
+ case MESSAGE_FADE_OUT_MENU: {
+ fadeOutMenu();
+ break;
+ }
}
}
};
@@ -409,6 +414,18 @@
}
}
+ /**
+ * Different from {@link #hideMenu()}, this function does not try to finish this menu activity
+ * and instead, it fades out the controls by setting the alpha to 0 directly without menu
+ * visibility callbacks invoked.
+ */
+ private void fadeOutMenu() {
+ mMenuContainer.setAlpha(0f);
+ mSettingsButton.setAlpha(0f);
+ mDismissButton.setAlpha(0f);
+ mResizeHandle.setAlpha(0f);
+ }
+
private void hideMenu() {
hideMenu(null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index bf2c3e9..8b4d932 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -262,6 +262,9 @@
*/
public void showMenuWithDelay(int menuState, Rect stackBounds, boolean allowMenuTimeout,
boolean willResizeMenu, boolean showResizeHandle) {
+ // hide all visible controls including close button and etc. first, this is to ensure
+ // menu is totally invisible during the transition to eliminate unpleasant artifacts
+ fadeOutMenu();
showMenuInternal(menuState, stackBounds, allowMenuTimeout, willResizeMenu,
true /* withDelay */, showResizeHandle);
}
@@ -347,6 +350,23 @@
}
}
+ private void fadeOutMenu() {
+ if (DEBUG) {
+ Log.d(TAG, "fadeOutMenu() state=" + mMenuState
+ + " hasActivity=" + (mToActivityMessenger != null)
+ + " callers=\n" + Debug.getCallers(5, " "));
+ }
+ if (mToActivityMessenger != null) {
+ Message m = Message.obtain();
+ m.what = PipMenuActivity.MESSAGE_FADE_OUT_MENU;
+ try {
+ mToActivityMessenger.send(m);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not notify menu to fade out", e);
+ }
+ }
+ }
+
/**
* Hides the menu activity.
*/
@@ -513,7 +533,8 @@
private void onMenuStateChanged(int menuState, boolean resize, Runnable callback) {
if (DEBUG) {
Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState
- + " menuState=" + menuState + " resize=" + resize);
+ + " menuState=" + menuState + " resize=" + resize
+ + " callers=\n" + Debug.getCallers(5, " "));
}
if (menuState != mMenuState) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index cc3ab29d..4b23e67 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -37,6 +37,7 @@
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.systemui.R;
@@ -77,10 +78,12 @@
private final Runnable mUpdateMovementBoundsRunnable;
private int mDelta;
+ private float mTouchSlop;
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
private boolean mEnableUserResize;
+ private boolean mThresholdCrossed;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -100,6 +103,7 @@
mPipTaskOrganizer = pipTaskOrganizer;
mMovementBoundsSupplier = movementBoundsSupplier;
mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+
context.getDisplay().getRealSize(mMaxSize);
reloadResources();
@@ -126,6 +130,7 @@
private void reloadResources() {
final Resources res = mContext.getResources();
mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
}
private void resetDragCorners() {
@@ -270,7 +275,12 @@
break;
case MotionEvent.ACTION_MOVE:
// Capture inputs
- mInputMonitor.pilferPointers();
+ float dx = Math.abs(ev.getX() - mDownPoint.x);
+ float dy = Math.abs(ev.getY() - mDownPoint.y);
+ if (!mThresholdCrossed && dx > mTouchSlop && dy > mTouchSlop) {
+ mThresholdCrossed = true;
+ mInputMonitor.pilferPointers();
+ }
final Rect currentPipBounds = mMotionHelper.getBounds();
mLastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(ev.getX(), ev.getY(),
mDownPoint.x, mDownPoint.y, currentPipBounds, mCtrlType, mMinSize.x,
@@ -288,6 +298,7 @@
mUpdateMovementBoundsRunnable.run();
mCtrlType = CTRL_NONE;
mAllowGesture = false;
+ mThresholdCrossed = false;
});
});
break;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index c408caa..c274ee9 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -397,6 +397,15 @@
mShelfHeight = shelfHeight;
}
+ public void adjustBoundsForRotation(Rect outBounds, Rect curBounds, Rect insetBounds) {
+ final Rect toMovementBounds = new Rect();
+ mSnapAlgorithm.getMovementBounds(outBounds, insetBounds, toMovementBounds, 0);
+ final int prevBottom = mMovementBounds.bottom - mMovementBoundsExtraOffsets;
+ if ((prevBottom - mBottomOffsetBufferPx) <= curBounds.top) {
+ outBounds.offsetTo(outBounds.left, toMovementBounds.bottom);
+ }
+ }
+
public void onMovementBoundsChanged(Rect insetBounds, Rect normalBounds, Rect curBounds,
boolean fromImeAdjustment, boolean fromShelfAdjustment, int displayRotation) {
final int bottomOffset = mIsImeShowing ? mImeHeight : 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index b877e87..8f9e9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -427,7 +427,7 @@
(ViewGroup.MarginLayoutParams) hostView.getLayoutParams();
float targetPosition = absoluteBottomPosition - params.bottomMargin
- hostView.getHeight();
- float currentPosition = mediaHost.getCurrentState().getBoundsOnScreen().top
+ float currentPosition = mediaHost.getCurrentBounds().top
- hostView.getTranslationY();
hostView.setTranslationY(targetPosition - currentPosition);
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index cdde06b..4f0b56e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -187,9 +187,9 @@
}
protected void addMediaHostView() {
- mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
mMediaHost.setExpansion(1.0f);
mMediaHost.setShowsOnlyActiveMedia(false);
+ mMediaHost.init(MediaHierarchyManager.LOCATION_QS);
ViewGroup hostView = mMediaHost.getHostView();
addView(hostView);
int sidePaddings = getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 2f06c4b..75507be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -190,9 +190,9 @@
switchTileLayout();
return null;
});
- mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
mMediaHost.setExpansion(0.0f);
mMediaHost.setShowsOnlyActiveMedia(true);
+ mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
reAttachMediaHost();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index e88ce5a..f0a81e9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -26,7 +26,6 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
@@ -146,7 +145,7 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
ScreenshotSmartActions.getSmartActionsFuture(
mScreenshotId, uri, image, mSmartActionsProvider,
- mSmartActionsEnabled, isManagedProfile(mContext));
+ mSmartActionsEnabled, getUserHandle(mContext));
try {
// First, write the actual data for our screenshot
@@ -382,10 +381,9 @@
}
}
- private boolean isManagedProfile(Context context) {
+ private UserHandle getUserHandle(Context context) {
UserManager manager = UserManager.get(context);
- UserInfo info = manager.getUserInfo(getUserHandleOfForegroundApplication(context));
- return info.isManagedProfile();
+ return manager.getUserInfo(getUserHandleOfForegroundApplication(context)).getUserHandle();
}
private List<Notification.Action> buildSmartActions(
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
index 3edb33d..63f323e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -20,6 +20,7 @@
import android.content.ComponentName;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.UserHandle;
import android.util.Log;
import java.util.Collections;
@@ -64,14 +65,14 @@
* @param componentName Contains package and activity class names where the screenshot was
* taken. This is used as an additional signal to generate and rank
* more relevant actions.
- * @param isManagedProfile The screenshot was taken for a work profile app.
+ * @param userHandle The user handle of the app where the screenshot was taken.
*/
public CompletableFuture<List<Notification.Action>> getActions(
String screenshotId,
Uri screenshotUri,
Bitmap bitmap,
ComponentName componentName,
- boolean isManagedProfile) {
+ UserHandle userHandle) {
Log.d(TAG, "Returning empty smart action list.");
return CompletableFuture.completedFuture(Collections.emptyList());
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index c228fe2..442b373 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -26,6 +26,7 @@
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -48,7 +49,7 @@
static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
String screenshotId, Uri screenshotUri, Bitmap image,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
- boolean smartActionsEnabled, boolean isManagedProfile) {
+ boolean smartActionsEnabled, UserHandle userHandle) {
if (!smartActionsEnabled) {
Slog.i(TAG, "Screenshot Intelligence not enabled, returning empty list.");
return CompletableFuture.completedFuture(Collections.emptyList());
@@ -60,7 +61,7 @@
return CompletableFuture.completedFuture(Collections.emptyList());
}
- Slog.d(TAG, "Screenshot from a managed profile: " + isManagedProfile);
+ Slog.d(TAG, "Screenshot from user profile: " + userHandle.getIdentifier());
CompletableFuture<List<Notification.Action>> smartActionsFuture;
long startTimeMs = SystemClock.uptimeMillis();
try {
@@ -71,7 +72,7 @@
? runningTask.topActivity
: new ComponentName("", "");
smartActionsFuture = smartActionsProvider.getActions(
- screenshotId, screenshotUri, image, componentName, isManagedProfile);
+ screenshotId, screenshotUri, image, componentName, userHandle);
} catch (Throwable e) {
long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index b57b22f..8e6398f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -133,7 +133,7 @@
}
public static void fadeIn(View view, float fadeInAmount) {
- fadeIn(view, fadeInAmount, true /* remap */);
+ fadeIn(view, fadeInAmount, false /* remap */);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
index dea1a07..cb7da4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/CustomInterpolatorTransformation.java
@@ -66,7 +66,7 @@
return false;
}
View view = ownState.getTransformedView();
- CrossFadeHelper.fadeIn(view, transformationAmount);
+ CrossFadeHelper.fadeIn(view, transformationAmount, true /* remap */);
ownState.transformViewFullyFrom(otherState, this, transformationAmount);
otherState.recycle();
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 27109d2..9a8cff0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,7 +95,7 @@
if (sameAs(otherState)) {
ensureVisible();
} else {
- CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
+ CrossFadeHelper.fadeIn(mTransformedView, transformationAmount, true /* remap */);
}
transformViewFullyFrom(otherState, transformationAmount);
}
@@ -424,7 +424,7 @@
if (transformationAmount == 0.0f) {
prepareFadeIn();
}
- CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
+ CrossFadeHelper.fadeIn(mTransformedView, transformationAmount, true /* remap */);
}
public void disappear(float transformationAmount, TransformableView otherView) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index fefad53..71f6dac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -146,14 +146,6 @@
return false;
}
- if (!entry.isBubble()) {
- if (DEBUG) {
- Log.d(TAG, "No bubble up: notification " + sbn.getKey()
- + " is bubble? " + entry.isBubble());
- }
- return false;
- }
-
if (entry.getBubbleMetadata() == null
|| (entry.getBubbleMetadata().getShortcutId() == null
&& entry.getBubbleMetadata().getIntent() == null)) {
@@ -206,6 +198,13 @@
return false;
}
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG_HEADS_UP) {
+ Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
+ }
+ return false;
+ }
+
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
if (DEBUG_HEADS_UP) {
@@ -365,14 +364,6 @@
return false;
}
}
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG_HEADS_UP) {
- Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
- }
- return false;
- }
-
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 92b597b..f1727ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -123,8 +123,6 @@
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
private int mNormalColor;
- private boolean mLastInSection;
- private boolean mFirstInSection;
private boolean mIsBelowSpeedBump;
private float mNormalBackgroundVisibilityAmount;
@@ -430,27 +428,21 @@
mBackgroundDimmed.setDistanceToTopRoundness(distanceToTopRoundness);
}
- public boolean isLastInSection() {
- return mLastInSection;
- }
-
- public boolean isFirstInSection() {
- return mFirstInSection;
- }
-
/** Sets whether this view is the last notification in a section. */
+ @Override
public void setLastInSection(boolean lastInSection) {
if (lastInSection != mLastInSection) {
- mLastInSection = lastInSection;
+ super.setLastInSection(lastInSection);
mBackgroundNormal.setLastInSection(lastInSection);
mBackgroundDimmed.setLastInSection(lastInSection);
}
}
/** Sets whether this view is the first notification in a section. */
+ @Override
public void setFirstInSection(boolean firstInSection) {
if (firstInSection != mFirstInSection) {
- mFirstInSection = firstInSection;
+ super.setFirstInSection(firstInSection);
mBackgroundNormal.setFirstInSection(firstInSection);
mBackgroundDimmed.setFirstInSection(firstInSection);
}
@@ -963,6 +955,7 @@
return false;
}
+ @Override
public int getHeadsUpHeightWithoutHeader() {
return getHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 049cafa..26ccd72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -262,10 +262,7 @@
setClipToOutline(mAlwaysRoundBothCorners);
}
- /**
- * Set the topRoundness of this view.
- * @return Whether the roundness was changed.
- */
+ @Override
public boolean setTopRoundness(float topRoundness, boolean animate) {
if (mTopRoundness != topRoundness) {
mTopRoundness = topRoundness;
@@ -302,10 +299,7 @@
return mCurrentBottomRoundness * mOutlineRadius;
}
- /**
- * Set the bottom roundness of this view.
- * @return Whether the roundness was changed.
- */
+ @Override
public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
if (mBottomRoundness != bottomRoundness) {
mBottomRoundness = bottomRoundness;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 0831c0b..7ed8350 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -67,6 +67,8 @@
protected int mContentShift;
private final ExpandableViewState mViewState;
private float mContentTranslation;
+ protected boolean mLastInSection;
+ protected boolean mFirstInSection;
public ExpandableView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -771,6 +773,44 @@
return true;
}
+ /** Sets whether this view is the first notification in a section. */
+ public void setFirstInSection(boolean firstInSection) {
+ mFirstInSection = firstInSection;
+ }
+
+ /** Sets whether this view is the last notification in a section. */
+ public void setLastInSection(boolean lastInSection) {
+ mLastInSection = lastInSection;
+ }
+
+ public boolean isLastInSection() {
+ return mLastInSection;
+ }
+
+ public boolean isFirstInSection() {
+ return mFirstInSection;
+ }
+
+ /**
+ * Set the topRoundness of this view.
+ * @return Whether the roundness was changed.
+ */
+ public boolean setTopRoundness(float topRoundness, boolean animate) {
+ return false;
+ }
+
+ /**
+ * Set the bottom roundness of this view.
+ * @return Whether the roundness was changed.
+ */
+ public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
+ return false;
+ }
+
+ public int getHeadsUpHeightWithoutHeader() {
+ return getHeight();
+ }
+
/**
* A listener notifying when {@link #getActualHeight} changes.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index 2071449..bc2adac3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -92,7 +92,7 @@
// We want to transform from the same y location as the title
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeIn(mTextView, transformationAmount);
+ CrossFadeHelper.fadeIn(mTextView, transformationAmount, true /* remap */);
if (otherState != null) {
ownState.transformViewVerticalFrom(otherState, transformationAmount);
otherState.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 2d99ab1..14aab9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -23,7 +23,6 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.PorterDuffColorFilter;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -107,7 +106,7 @@
TransformState otherState = notification.getCurrentState(
TRANSFORMING_VIEW_TITLE);
final View text = ownState.getTransformedView();
- CrossFadeHelper.fadeIn(text, transformationAmount);
+ CrossFadeHelper.fadeIn(text, transformationAmount, true /* remap */);
if (otherState != null) {
ownState.transformViewVerticalFrom(otherState, this,
transformationAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index fa7f282..02e537d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -284,7 +284,7 @@
@Override
public void transformFrom(TransformableView notification, float transformationAmount) {
- CrossFadeHelper.fadeIn(mView, transformationAmount);
+ CrossFadeHelper.fadeIn(mView, transformationAmount, true /* remap */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index ecab188..b4220f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -64,7 +64,7 @@
private int mZDistanceBetweenElements;
private int mBaseZHeight;
private int mMaxLayoutHeight;
- private ActivatableNotificationView mLastVisibleBackgroundChild;
+ private ExpandableView mLastVisibleBackgroundChild;
private float mCurrentScrollVelocity;
private int mStatusBarState;
private float mExpandingVelocity;
@@ -346,11 +346,11 @@
* view in the shade, without the clear all button.
*/
public void setLastVisibleBackgroundChild(
- ActivatableNotificationView lastVisibleBackgroundChild) {
+ ExpandableView lastVisibleBackgroundChild) {
mLastVisibleBackgroundChild = lastVisibleBackgroundChild;
}
- public ActivatableNotificationView getLastVisibleBackgroundChild() {
+ public ExpandableView getLastVisibleBackgroundChild() {
return mLastVisibleBackgroundChild;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
index 3ac322f..383f2a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaHeaderView.java
@@ -16,45 +16,35 @@
package com.android.systemui.statusbar.notification.stack;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
-import android.view.View;
import android.view.ViewGroup;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
/**
* Root view to insert Lock screen media controls into the notification stack.
*/
-public class MediaHeaderView extends ActivatableNotificationView {
-
- private View mContentView;
+public class MediaHeaderView extends ExpandableView {
public MediaHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
+ public long performRemoveAnimation(long duration, long delay, float translationDirection,
+ boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable,
+ AnimatorListenerAdapter animationListener) {
+ return 0;
}
@Override
- protected View getContentView() {
- return mContentView;
- }
-
- /**
- * Sets the background color, to be used when album art changes.
- * @param color background
- */
- public void setBackgroundColor(int color) {
- setTintColor(color);
+ public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
+ // No animation, it doesn't need it, this would be local
}
public void setContentView(ViewGroup contentView) {
- mContentView = contentView;
addView(contentView);
ViewGroup.LayoutParams layoutParams = contentView.getLayoutParams();
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index b4f7b59..2c3239a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -20,7 +20,6 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -37,10 +36,10 @@
@Singleton
public class NotificationRoundnessManager implements OnHeadsUpChangedListener {
- private final ActivatableNotificationView[] mFirstInSectionViews;
- private final ActivatableNotificationView[] mLastInSectionViews;
- private final ActivatableNotificationView[] mTmpFirstInSectionViews;
- private final ActivatableNotificationView[] mTmpLastInSectionViews;
+ private final ExpandableView[] mFirstInSectionViews;
+ private final ExpandableView[] mLastInSectionViews;
+ private final ExpandableView[] mTmpFirstInSectionViews;
+ private final ExpandableView[] mTmpLastInSectionViews;
private final KeyguardBypassController mBypassController;
private boolean mExpanded;
private HashSet<ExpandableView> mAnimatedChildren;
@@ -53,10 +52,10 @@
KeyguardBypassController keyguardBypassController,
NotificationSectionsFeatureManager sectionsFeatureManager) {
int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
- mFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
- mLastInSectionViews = new ActivatableNotificationView[numberOfSections];
- mTmpFirstInSectionViews = new ActivatableNotificationView[numberOfSections];
- mTmpLastInSectionViews = new ActivatableNotificationView[numberOfSections];
+ mFirstInSectionViews = new ExpandableView[numberOfSections];
+ mLastInSectionViews = new ExpandableView[numberOfSections];
+ mTmpFirstInSectionViews = new ExpandableView[numberOfSections];
+ mTmpLastInSectionViews = new ExpandableView[numberOfSections];
mBypassController = keyguardBypassController;
}
@@ -80,14 +79,14 @@
updateView(entry.getRow(), false /* animate */);
}
- private void updateView(ActivatableNotificationView view, boolean animate) {
+ private void updateView(ExpandableView view, boolean animate) {
boolean changed = updateViewWithoutCallback(view, animate);
if (changed) {
mRoundingChangedCallback.run();
}
}
- private boolean updateViewWithoutCallback(ActivatableNotificationView view,
+ private boolean updateViewWithoutCallback(ExpandableView view,
boolean animate) {
float topRoundness = getRoundness(view, true /* top */);
float bottomRoundness = getRoundness(view, false /* top */);
@@ -100,8 +99,7 @@
return (firstInSection || lastInSection) && (topChanged || bottomChanged);
}
- private boolean isFirstInSection(ActivatableNotificationView view,
- boolean includeFirstSection) {
+ private boolean isFirstInSection(ExpandableView view, boolean includeFirstSection) {
int numNonEmptySections = 0;
for (int i = 0; i < mFirstInSectionViews.length; i++) {
if (view == mFirstInSectionViews[i]) {
@@ -114,7 +112,7 @@
return false;
}
- private boolean isLastInSection(ActivatableNotificationView view, boolean includeLastSection) {
+ private boolean isLastInSection(ExpandableView view, boolean includeLastSection) {
int numNonEmptySections = 0;
for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
if (view == mLastInSectionViews[i]) {
@@ -127,7 +125,7 @@
return false;
}
- private float getRoundness(ActivatableNotificationView view, boolean top) {
+ private float getRoundness(ExpandableView view, boolean top) {
if ((view.isPinned() || view.isHeadsUpAnimatingAway()) && !mExpanded) {
return 1.0f;
}
@@ -174,14 +172,14 @@
}
private boolean handleRemovedOldViews(NotificationSection[] sections,
- ActivatableNotificationView[] oldViews, boolean first) {
+ ExpandableView[] oldViews, boolean first) {
boolean anyChanged = false;
- for (ActivatableNotificationView oldView : oldViews) {
+ for (ExpandableView oldView : oldViews) {
if (oldView != null) {
boolean isStillPresent = false;
boolean adjacentSectionChanged = false;
for (NotificationSection section : sections) {
- ActivatableNotificationView newView =
+ ExpandableView newView =
(first ? section.getFirstVisibleChild()
: section.getLastVisibleChild());
if (newView == oldView) {
@@ -207,14 +205,14 @@
}
private boolean handleAddedNewViews(NotificationSection[] sections,
- ActivatableNotificationView[] oldViews, boolean first) {
+ ExpandableView[] oldViews, boolean first) {
boolean anyChanged = false;
for (NotificationSection section : sections) {
- ActivatableNotificationView newView =
+ ExpandableView newView =
(first ? section.getFirstVisibleChild() : section.getLastVisibleChild());
if (newView != null) {
boolean wasAlreadyPresent = false;
- for (ActivatableNotificationView oldView : oldViews) {
+ for (ExpandableView oldView : oldViews) {
if (oldView == newView) {
wasAlreadyPresent = true;
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
index bad36bf..1131a65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.stack;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
@@ -26,7 +28,7 @@
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.notification.ShadeViewRefactor;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
/**
* Represents the bounds of a section of the notification shade and handles animation when the
@@ -41,8 +43,8 @@
private Rect mEndAnimationRect = new Rect();
private ObjectAnimator mTopAnimator = null;
private ObjectAnimator mBottomAnimator = null;
- private ActivatableNotificationView mFirstVisibleChild;
- private ActivatableNotificationView mLastVisibleChild;
+ private ExpandableView mFirstVisibleChild;
+ private ExpandableView mLastVisibleChild;
NotificationSection(View owningView, @PriorityBucket int bucket) {
mOwningView = owningView;
@@ -198,21 +200,21 @@
mOwningView.invalidate();
}
- public ActivatableNotificationView getFirstVisibleChild() {
+ public ExpandableView getFirstVisibleChild() {
return mFirstVisibleChild;
}
- public ActivatableNotificationView getLastVisibleChild() {
+ public ExpandableView getLastVisibleChild() {
return mLastVisibleChild;
}
- public boolean setFirstVisibleChild(ActivatableNotificationView child) {
+ public boolean setFirstVisibleChild(ExpandableView child) {
boolean changed = mFirstVisibleChild != child;
mFirstVisibleChild = child;
return changed;
}
- public boolean setLastVisibleChild(ActivatableNotificationView child) {
+ public boolean setLastVisibleChild(ExpandableView child) {
boolean changed = mLastVisibleChild != child;
mLastVisibleChild = child;
return changed;
@@ -251,7 +253,7 @@
boolean shiftBackgroundWithFirst) {
int top = minTopPosition;
int bottom = minTopPosition;
- ActivatableNotificationView firstView = getFirstVisibleChild();
+ ExpandableView firstView = getFirstVisibleChild();
if (firstView != null) {
// Round Y up to avoid seeing the background during animation
int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
@@ -276,7 +278,7 @@
}
}
top = Math.max(minTopPosition, top);
- ActivatableNotificationView lastView = getLastVisibleChild();
+ ExpandableView lastView = getLastVisibleChild();
if (lastView != null) {
float finalTranslationY = ViewState.getFinalTranslationY(lastView);
int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
@@ -302,4 +304,8 @@
mBounds.bottom = bottom;
return bottom;
}
+
+ public boolean needsBackground() {
+ return mFirstVisibleChild != null && mBucket != BUCKET_MEDIA_CONTROLS;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index e39a4a0..ba7675f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.notification.people.PeopleHubViewBoundary
import com.android.systemui.statusbar.notification.people.PersonViewModel
import com.android.systemui.statusbar.notification.people.Subscription
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView
@@ -456,14 +455,14 @@
private sealed class SectionBounds {
data class Many(
- val first: ActivatableNotificationView,
- val last: ActivatableNotificationView
+ val first: ExpandableView,
+ val last: ExpandableView
) : SectionBounds()
- data class One(val lone: ActivatableNotificationView) : SectionBounds()
+ data class One(val lone: ExpandableView) : SectionBounds()
object None : SectionBounds()
- fun addNotif(notif: ActivatableNotificationView): SectionBounds = when (this) {
+ fun addNotif(notif: ExpandableView): SectionBounds = when (this) {
is None -> One(notif)
is One -> Many(lone, notif)
is Many -> copy(last = notif)
@@ -476,8 +475,8 @@
}
private fun NotificationSection.setFirstAndLastVisibleChildren(
- first: ActivatableNotificationView?,
- last: ActivatableNotificationView?
+ first: ExpandableView?,
+ last: ExpandableView?
): Boolean {
val firstChanged = setFirstVisibleChild(first)
val lastChanged = setLastVisibleChild(last)
@@ -492,7 +491,7 @@
*/
fun updateFirstAndLastViewsForAllSections(
sections: Array<NotificationSection>,
- children: List<ActivatableNotificationView>
+ children: List<ExpandableView>
): Boolean {
// Create mapping of bucket to section
val sectionBounds = children.asSequence()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index e33cc60..bcafd0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -701,7 +701,7 @@
* @return the height at which we will wake up when pulsing
*/
public float getWakeUpHeight() {
- ActivatableNotificationView firstChild = getFirstChildWithBackground();
+ ExpandableView firstChild = getFirstChildWithBackground();
if (firstChild != null) {
if (mKeyguardBypassController.getBypassEnabled()) {
return firstChild.getHeadsUpHeightWithoutHeader();
@@ -907,7 +907,7 @@
// TODO(kprevas): this may not be necessary any more since we don't display the shelf in AOD
boolean anySectionHasVisibleChild = false;
for (NotificationSection section : mSections) {
- if (section.getFirstVisibleChild() != null) {
+ if (section.needsBackground()) {
anySectionHasVisibleChild = true;
break;
}
@@ -950,7 +950,7 @@
int currentRight = right;
boolean first = true;
for (NotificationSection section : mSections) {
- if (section.getFirstVisibleChild() == null) {
+ if (!section.needsBackground()) {
continue;
}
int sectionTop = section.getCurrentBounds().top + animationYOffset;
@@ -2685,40 +2685,40 @@
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private ActivatableNotificationView getLastChildWithBackground() {
+ private ExpandableView getLastChildWithBackground() {
int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
- return (ActivatableNotificationView) child;
+ return child;
}
}
return null;
}
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
- private ActivatableNotificationView getFirstChildWithBackground() {
+ private ExpandableView getFirstChildWithBackground() {
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
- return (ActivatableNotificationView) child;
+ return child;
}
}
return null;
}
//TODO: We shouldn't have to generate this list every time
- private List<ActivatableNotificationView> getChildrenWithBackground() {
- ArrayList<ActivatableNotificationView> children = new ArrayList<>();
+ private List<ExpandableView> getChildrenWithBackground() {
+ ArrayList<ExpandableView> children = new ArrayList<>();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+ ExpandableView child = (ExpandableView) getChildAt(i);
+ if (child.getVisibility() != View.GONE && !(child instanceof StackScrollerDecorView)
&& child != mShelf) {
- children.add((ActivatableNotificationView) child);
+ children.add(child);
}
}
@@ -3283,13 +3283,13 @@
private void updateFirstAndLastBackgroundViews() {
NotificationSection firstSection = getFirstVisibleSection();
NotificationSection lastSection = getLastVisibleSection();
- ActivatableNotificationView previousFirstChild =
+ ExpandableView previousFirstChild =
firstSection == null ? null : firstSection.getFirstVisibleChild();
- ActivatableNotificationView previousLastChild =
+ ExpandableView previousLastChild =
lastSection == null ? null : lastSection.getLastVisibleChild();
- ActivatableNotificationView firstChild = getFirstChildWithBackground();
- ActivatableNotificationView lastChild = getLastChildWithBackground();
+ ExpandableView firstChild = getFirstChildWithBackground();
+ ExpandableView lastChild = getLastChildWithBackground();
boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections(
mSections, getChildrenWithBackground());
@@ -4575,7 +4575,7 @@
? (ExpandableNotificationRow) view
: null;
NotificationSection firstSection = getFirstVisibleSection();
- ActivatableNotificationView firstVisibleChild =
+ ExpandableView firstVisibleChild =
firstSection == null ? null : firstSection.getFirstVisibleChild();
if (row != null) {
if (row == firstVisibleChild
@@ -4611,7 +4611,7 @@
}
int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
NotificationSection lastSection = getLastVisibleSection();
- ActivatableNotificationView lastVisibleChild =
+ ExpandableView lastVisibleChild =
lastSection == null ? null : lastSection.getLastVisibleChild();
if (row != lastVisibleChild && mShelf.getVisibility() != GONE) {
layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 64e5f0a..7bcfb46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -350,7 +350,6 @@
}
Intent fillInIntent = null;
NotificationEntry entry = row.getEntry();
- final boolean isBubble = entry.isBubble();
CharSequence remoteInputText = null;
if (!TextUtils.isEmpty(entry.remoteInputText)) {
remoteInputText = entry.remoteInputText;
@@ -359,14 +358,15 @@
fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
remoteInputText.toString());
}
- if (isBubble) {
+ final boolean canBubble = entry.canBubble();
+ if (canBubble) {
mLogger.logExpandingBubble(notificationKey);
- expandBubbleStackOnMainThread(notificationKey);
+ expandBubbleStackOnMainThread(entry);
} else {
startNotificationIntent(
intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
}
- if (isActivityIntent || isBubble) {
+ if (isActivityIntent || canBubble) {
mAssistManagerLazy.get().hideAssist();
}
if (shouldCollapse()) {
@@ -381,7 +381,7 @@
rank, count, true, location);
mClickNotifier.onNotificationClick(notificationKey, nv);
- if (!isBubble) {
+ if (!canBubble) {
if (parentToCancelFinal != null) {
// TODO: (b/145659174) remove - this cancels the parent if the notification clicked
// on will auto-cancel and is the only child in the group. This won't be
@@ -398,12 +398,12 @@
mIsCollapsingToShowActivityOverLockscreen = false;
}
- private void expandBubbleStackOnMainThread(String notificationKey) {
+ private void expandBubbleStackOnMainThread(NotificationEntry entry) {
if (Looper.getMainLooper().isCurrentThread()) {
- mBubbleController.expandStackAndSelectBubble(notificationKey);
+ mBubbleController.expandStackAndSelectBubble(entry);
} else {
mMainThreadHandler.post(
- () -> mBubbleController.expandStackAndSelectBubble(notificationKey));
+ () -> mBubbleController.expandStackAndSelectBubble(entry));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
deleted file mode 100644
index 2be698b..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementCache.kt
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2020 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.util.animation
-
-/**
- * A class responsible for caching view Measurements which guarantees that we always obtain a value
- */
-class GuaranteedMeasurementCache constructor(
- private val baseCache : MeasurementCache,
- private val inputMapper: (MeasurementInput) -> MeasurementInput,
- private val measurementProvider: (MeasurementInput) -> MeasurementOutput?
-) : MeasurementCache {
-
- override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
- val mappedInput = inputMapper.invoke(input)
- if (!baseCache.contains(mappedInput)) {
- var measurement = measurementProvider.invoke(mappedInput)
- if (measurement != null) {
- // Only cache measurings that actually have a size
- baseCache.putMeasurement(mappedInput, measurement)
- } else {
- measurement = MeasurementOutput(0, 0)
- }
- return measurement
- } else {
- return baseCache.obtainMeasurement(mappedInput)
- }
- }
-
- override fun contains(input: MeasurementInput): Boolean {
- return baseCache.contains(inputMapper.invoke(input))
- }
-
- override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
- if (output.measuredWidth == 0 || output.measuredHeight == 0) {
- // Only cache measurings that actually have a size
- return;
- }
- val remappedInput = inputMapper.invoke(input)
- baseCache.putMeasurement(remappedInput, output)
- }
-}
-
-/**
- * A base implementation class responsible for caching view Measurements
- */
-class BaseMeasurementCache : MeasurementCache {
- private val dataCache: MutableMap<MeasurementInput, MeasurementOutput> = mutableMapOf()
-
- override fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput {
- val measurementOutput = dataCache[input]
- if (measurementOutput == null) {
- return MeasurementOutput(0, 0)
- } else {
- return measurementOutput
- }
- }
-
- override fun contains(input: MeasurementInput) : Boolean {
- return dataCache[input] != null
- }
-
- override fun putMeasurement(input: MeasurementInput, output: MeasurementOutput) {
- dataCache[input] = output
- }
-}
-
-interface MeasurementCache {
- fun obtainMeasurement(input: MeasurementInput) : MeasurementOutput
- fun contains(input: MeasurementInput) : Boolean
- fun putMeasurement(input: MeasurementInput, output: MeasurementOutput)
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt
new file mode 100644
index 0000000..c7edd51
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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.util.animation
+
+import android.view.View
+
+/**
+ * The output of a view measurement
+ */
+data class MeasurementOutput(
+ var measuredWidth: Int,
+ var measuredHeight: Int
+)
+
+/**
+ * The data object holding a basic view measurement input
+ */
+data class MeasurementInput(
+ var widthMeasureSpec: Int,
+ var heightMeasureSpec: Int
+) {
+ val width: Int
+ get() {
+ return View.MeasureSpec.getSize(widthMeasureSpec)
+ }
+ val height: Int
+ get() {
+ return View.MeasureSpec.getSize(heightMeasureSpec)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
new file mode 100644
index 0000000..701ff5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2020 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.util.animation
+
+import android.content.Context
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.statusbar.CrossFadeHelper
+
+/**
+ * A view that handles displaying of children and transitions of them in an optimized way,
+ * minimizing the number of measure passes, while allowing for maximum flexibility
+ * and interruptibility.
+ */
+class TransitionLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+
+ private val originalGoneChildrenSet: MutableSet<Int> = mutableSetOf()
+ private var measureAsConstraint: Boolean = false
+ private var currentState: TransitionViewState = TransitionViewState()
+ private var updateScheduled = false
+
+ /**
+ * The measured state of this view which is the one we will lay ourselves out with. This
+ * may differ from the currentState if there is an external animation or transition running.
+ * This state will not be used to measure the widgets, where the current state is preferred.
+ */
+ var measureState: TransitionViewState = TransitionViewState()
+ private val preDrawApplicator = object : ViewTreeObserver.OnPreDrawListener {
+ override fun onPreDraw(): Boolean {
+ updateScheduled = false
+ viewTreeObserver.removeOnPreDrawListener(this)
+ applyCurrentState()
+ return true
+ }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val childCount = childCount
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ if (child.id == View.NO_ID) {
+ child.id = i
+ }
+ if (child.visibility == GONE) {
+ originalGoneChildrenSet.add(child.id)
+ }
+ }
+ }
+
+ /**
+ * Apply the current state to the view and its widgets
+ */
+ private fun applyCurrentState() {
+ val childCount = childCount
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ val widgetState = currentState.widgetStates.get(child.id) ?: continue
+ if (child.measuredWidth != widgetState.measureWidth ||
+ child.measuredHeight != widgetState.measureHeight) {
+ val measureWidthSpec = MeasureSpec.makeMeasureSpec(widgetState.measureWidth,
+ MeasureSpec.EXACTLY)
+ val measureHeightSpec = MeasureSpec.makeMeasureSpec(widgetState.measureHeight,
+ MeasureSpec.EXACTLY)
+ child.measure(measureWidthSpec, measureHeightSpec)
+ child.layout(0, 0, child.measuredWidth, child.measuredHeight)
+ }
+ val left = widgetState.x.toInt()
+ val top = widgetState.y.toInt()
+ child.setLeftTopRightBottom(left, top, left + widgetState.width,
+ top + widgetState.height)
+ child.scaleX = widgetState.scale
+ child.scaleY = widgetState.scale
+ val clipBounds = child.clipBounds ?: Rect()
+ clipBounds.set(0, 0, widgetState.width, widgetState.height)
+ child.clipBounds = clipBounds
+ CrossFadeHelper.fadeIn(child, widgetState.alpha)
+ child.visibility = if (widgetState.gone || widgetState.alpha == 0.0f) {
+ View.INVISIBLE
+ } else {
+ View.VISIBLE
+ }
+ }
+ updateBounds()
+ }
+
+ private fun applyCurrentStateOnPredraw() {
+ if (!updateScheduled) {
+ updateScheduled = true
+ viewTreeObserver.addOnPreDrawListener(preDrawApplicator)
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (measureAsConstraint) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ } else {
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ val widgetState = currentState.widgetStates.get(child.id) ?: continue
+ val measureWidthSpec = MeasureSpec.makeMeasureSpec(widgetState.measureWidth,
+ MeasureSpec.EXACTLY)
+ val measureHeightSpec = MeasureSpec.makeMeasureSpec(widgetState.measureHeight,
+ MeasureSpec.EXACTLY)
+ child.measure(measureWidthSpec, measureHeightSpec)
+ }
+ setMeasuredDimension(measureState.width, measureState.height)
+ }
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ if (measureAsConstraint) {
+ super.onLayout(changed, left, top, right, bottom)
+ } else {
+ val childCount = childCount
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ child.layout(0, 0, child.measuredWidth, child.measuredHeight)
+ }
+ // Reapply the bounds to update the background
+ applyCurrentState()
+ }
+ }
+
+ private fun updateBounds() {
+ val layoutLeft = left
+ val layoutTop = top
+ setLeftTopRightBottom(layoutLeft, layoutTop, layoutLeft + currentState.width,
+ layoutTop + currentState.height)
+ }
+
+ /**
+ * Calculates a view state for a given ConstraintSet and measurement, saving all positions
+ * of all widgets.
+ *
+ * @param input the measurement input this should be done with
+ * @param constraintSet the constraint set to apply
+ * @param resusableState the result that we can reuse to minimize memory impact
+ */
+ fun calculateViewState(
+ input: MeasurementInput,
+ constraintSet: ConstraintSet,
+ existing: TransitionViewState? = null
+ ): TransitionViewState {
+
+ val result = existing ?: TransitionViewState()
+ // Reset gone children to the original state
+ applySetToFullLayout(constraintSet)
+ val previousHeight = measuredHeight
+ val previousWidth = measuredWidth
+
+ // Let's measure outselves as a ConstraintLayout
+ measureAsConstraint = true
+ measure(input.widthMeasureSpec, input.heightMeasureSpec)
+ val layoutLeft = left
+ val layoutTop = top
+ layout(layoutLeft, layoutTop, layoutLeft + measuredWidth, layoutTop + measuredHeight)
+ measureAsConstraint = false
+ result.initFromLayout(this)
+ ensureViewsNotGone()
+
+ // Let's reset our layout to have the right size again
+ setMeasuredDimension(previousWidth, previousHeight)
+ applyCurrentStateOnPredraw()
+ return result
+ }
+
+ private fun applySetToFullLayout(constraintSet: ConstraintSet) {
+ // Let's reset our views to the initial gone state of the layout, since the constraintset
+ // might only be a subset of the views. Otherwise the gone state would be calculated
+ // wrongly later if we made this invisible in the layout (during apply we make sure they
+ // are invisible instead
+ val childCount = childCount
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ if (originalGoneChildrenSet.contains(child.id)) {
+ child.visibility = View.GONE
+ }
+ }
+ // Let's now apply the constraintSet to get the full state
+ constraintSet.applyTo(this)
+ }
+
+ /**
+ * Ensures that our views are never gone but invisible instead, this allows us to animate them
+ * without remeasuring.
+ */
+ private fun ensureViewsNotGone() {
+ val childCount = childCount
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ val widgetState = currentState.widgetStates.get(child.id)
+ child.visibility = if (widgetState?.gone != false) View.INVISIBLE else View.VISIBLE
+ }
+ }
+
+ /**
+ * Set the state that should be applied to this View
+ *
+ */
+ fun setState(state: TransitionViewState) {
+ currentState = state
+ applyCurrentState()
+ }
+}
+
+class TransitionViewState {
+ var widgetStates: MutableMap<Int, WidgetState> = mutableMapOf()
+ var width: Int = 0
+ var height: Int = 0
+ fun copy(reusedState: TransitionViewState? = null): TransitionViewState {
+ // we need a deep copy of this, so we can't use a data class
+ val copy = reusedState ?: TransitionViewState()
+ copy.width = width
+ copy.height = height
+ for (entry in widgetStates) {
+ copy.widgetStates[entry.key] = entry.value.copy()
+ }
+ return copy
+ }
+
+ fun initFromLayout(transitionLayout: TransitionLayout) {
+ val childCount = transitionLayout.childCount
+ for (i in 0 until childCount) {
+ val child = transitionLayout.getChildAt(i)
+ val widgetState = widgetStates.getOrPut(child.id, {
+ WidgetState(0.0f, 0.0f, 0, 0, 0, 0, 0.0f)
+ })
+ widgetState.initFromLayout(child)
+ }
+ width = transitionLayout.measuredWidth
+ height = transitionLayout.measuredHeight
+ }
+}
+
+data class WidgetState(
+ var x: Float = 0.0f,
+ var y: Float = 0.0f,
+ var width: Int = 0,
+ var height: Int = 0,
+ var measureWidth: Int = 0,
+ var measureHeight: Int = 0,
+ var alpha: Float = 1.0f,
+ var scale: Float = 1.0f,
+ var gone: Boolean = false
+) {
+ fun initFromLayout(view: View) {
+ gone = view.visibility == View.GONE
+ if (gone) {
+ val layoutParams = view.layoutParams as ConstraintLayout.LayoutParams
+ x = layoutParams.constraintWidget.left.toFloat()
+ y = layoutParams.constraintWidget.top.toFloat()
+ width = layoutParams.constraintWidget.width
+ height = layoutParams.constraintWidget.height
+ measureHeight = height
+ measureWidth = width
+ alpha = 0.0f
+ scale = 0.0f
+ } else {
+ x = view.left.toFloat()
+ y = view.top.toFloat()
+ width = view.width
+ height = view.height
+ measureWidth = width
+ measureHeight = height
+ gone = view.visibility == View.GONE
+ alpha = view.alpha
+ // No scale by default. Only during transitions!
+ scale = 1.0f
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
new file mode 100644
index 0000000..9ee1410
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 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.util.animation
+
+import android.animation.ValueAnimator
+import android.util.MathUtils
+import com.android.systemui.Interpolators
+
+/**
+ * The fraction after which we start fading in when going from a gone widget to a visible one
+ */
+private const val GONE_FADE_FRACTION = 0.8f
+
+/**
+ * The amont we're scaling appearing views
+ */
+private const val GONE_SCALE_AMOUNT = 0.8f
+
+/**
+ * A controller for a [TransitionLayout] which handles state transitions and keeps the transition
+ * layout up to date with the desired state.
+ */
+open class TransitionLayoutController {
+
+ /**
+ * The layout that this controller controls
+ */
+ private var transitionLayout: TransitionLayout? = null
+ private var currentState = TransitionViewState()
+ private var animationStartState: TransitionViewState? = null
+ private var state = TransitionViewState()
+ private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
+
+ init {
+ animator.apply {
+ addUpdateListener {
+ updateStateFromAnimation()
+ }
+ interpolator = Interpolators.FAST_OUT_SLOW_IN
+ }
+ }
+
+ private fun updateStateFromAnimation() {
+ if (animationStartState == null || !animator.isRunning) {
+ return
+ }
+ val view = transitionLayout ?: return
+ getInterpolatedState(
+ startState = animationStartState!!,
+ endState = state,
+ progress = animator.animatedFraction,
+ resultState = currentState)
+ view.setState(currentState)
+ }
+
+ /**
+ * Get an interpolated state between two viewstates. This interpolates all positions for all
+ * widgets as well as it's bounds based on the given input.
+ */
+ fun getInterpolatedState(
+ startState: TransitionViewState,
+ endState: TransitionViewState,
+ progress: Float,
+ resultState: TransitionViewState
+ ) {
+ val view = transitionLayout ?: return
+ val childCount = view.childCount
+ for (i in 0 until childCount) {
+ val id = view.getChildAt(i).id
+ val resultWidgetState = resultState.widgetStates[id] ?: WidgetState()
+ val widgetStart = startState.widgetStates[id] ?: continue
+ val widgetEnd = endState.widgetStates[id] ?: continue
+ var alphaProgress = progress
+ var widthProgress = progress
+ val resultMeasureWidth: Int
+ val resultMeasureHeight: Int
+ val newScale: Float
+ val resultX: Float
+ val resultY: Float
+ if (widgetStart.gone != widgetEnd.gone) {
+ // A view is appearing or disappearing. Let's not just interpolate between them as
+ // this looks quite ugly
+ val nowGone: Boolean
+ if (widgetStart.gone) {
+
+ // Only fade it in at the very end
+ alphaProgress = MathUtils.map(GONE_FADE_FRACTION, 1.0f, 0.0f, 1.0f, progress)
+ nowGone = progress < GONE_FADE_FRACTION
+
+ // Scale it just a little, not all the way
+ val endScale = widgetEnd.scale
+ newScale = MathUtils.lerp(GONE_SCALE_AMOUNT * endScale, endScale, progress)
+
+ // don't clip
+ widthProgress = 1.0f
+
+ // Let's directly measure it with the end state
+ resultMeasureWidth = widgetEnd.measureWidth
+ resultMeasureHeight = widgetEnd.measureHeight
+
+ // Let's make sure we're centering the view in the gone view instead of having
+ // the left at 0
+ resultX = MathUtils.lerp(widgetStart.x - resultMeasureWidth / 2.0f,
+ widgetEnd.x,
+ progress)
+ resultY = MathUtils.lerp(widgetStart.y - resultMeasureHeight / 2.0f,
+ widgetEnd.y,
+ progress)
+ } else {
+
+ // Fadeout in the very beginning
+ alphaProgress = MathUtils.map(0.0f, 1.0f - GONE_FADE_FRACTION, 0.0f, 1.0f,
+ progress)
+ nowGone = progress > 1.0f - GONE_FADE_FRACTION
+
+ // Scale it just a little, not all the way
+ val startScale = widgetStart.scale
+ newScale = MathUtils.lerp(startScale, startScale * GONE_SCALE_AMOUNT, progress)
+
+ // Don't clip
+ widthProgress = 0.0f
+
+ // Let's directly measure it with the start state
+ resultMeasureWidth = widgetStart.measureWidth
+ resultMeasureHeight = widgetStart.measureHeight
+
+ // Let's make sure we're centering the view in the gone view instead of having
+ // the left at 0
+ resultX = MathUtils.lerp(widgetStart.x,
+ widgetEnd.x - resultMeasureWidth / 2.0f,
+ progress)
+ resultY = MathUtils.lerp(widgetStart.y,
+ widgetEnd.y - resultMeasureHeight / 2.0f,
+ progress)
+ }
+ resultWidgetState.gone = nowGone
+ } else {
+ resultWidgetState.gone = widgetStart.gone
+ // Let's directly measure it with the end state
+ resultMeasureWidth = widgetEnd.measureWidth
+ resultMeasureHeight = widgetEnd.measureHeight
+ newScale = MathUtils.lerp(widgetStart.scale, widgetEnd.scale, progress)
+ resultX = MathUtils.lerp(widgetStart.x, widgetEnd.x, progress)
+ resultY = MathUtils.lerp(widgetStart.y, widgetEnd.y, progress)
+ }
+ resultWidgetState.apply {
+ x = resultX
+ y = resultY
+ alpha = MathUtils.lerp(widgetStart.alpha, widgetEnd.alpha, alphaProgress)
+ width = MathUtils.lerp(widgetStart.width.toFloat(), widgetEnd.width.toFloat(),
+ widthProgress).toInt()
+ height = MathUtils.lerp(widgetStart.height.toFloat(), widgetEnd.height.toFloat(),
+ widthProgress).toInt()
+ scale = newScale
+
+ // Let's directly measure it with the end state
+ measureWidth = resultMeasureWidth
+ measureHeight = resultMeasureHeight
+ }
+ resultState.widgetStates[id] = resultWidgetState
+ }
+ resultState.apply {
+ width = MathUtils.lerp(startState.width.toFloat(), endState.width.toFloat(),
+ progress).toInt()
+ height = MathUtils.lerp(startState.height.toFloat(), endState.height.toFloat(),
+ progress).toInt()
+ }
+ }
+
+ fun attach(transitionLayout: TransitionLayout) {
+ this.transitionLayout = transitionLayout
+ }
+
+ /**
+ * Set a new state to be applied to the dynamic view.
+ *
+ * @param state the state to be applied
+ * @param animate should this change be animated. If [false] the we will either apply the
+ * state immediately if no animation is running, and if one is running, we will update the end
+ * value to match the new state.
+ * @param applyImmediately should this change be applied immediately, canceling all running
+ * animations
+ */
+ fun setState(
+ state: TransitionViewState,
+ applyImmediately: Boolean,
+ animate: Boolean,
+ duration: Long = 0,
+ delay: Long = 0
+ ) {
+ val animated = animate && currentState.width != 0
+ this.state = state.copy()
+ if (applyImmediately || transitionLayout == null) {
+ animator.cancel()
+ transitionLayout?.setState(this.state)
+ currentState = state.copy(reusedState = currentState)
+ } else if (animated) {
+ animationStartState = currentState.copy()
+ animator.duration = duration
+ animator.startDelay = delay
+ animator.start()
+ } else if (!animator.isRunning) {
+ transitionLayout?.setState(this.state)
+ currentState = state.copy(reusedState = currentState)
+ }
+ // otherwise the desired state was updated and the animation will go to the new target
+ }
+
+ /**
+ * Set a new state that will be used to measure the view itself and is useful during
+ * transitions, where the state set via [setState] may differ from how the view
+ * should be measured.
+ */
+ fun setMeasureState(
+ state: TransitionViewState
+ ) {
+ transitionLayout?.measureState = state
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
index bf94c5d..5b6444d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt
@@ -19,7 +19,9 @@
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
+import android.view.ViewGroup
import android.widget.FrameLayout
+import com.android.systemui.R
/**
* A special view that is designed to host a single "unique object". The unique object is
@@ -34,8 +36,7 @@
class UniqueObjectHostView(
context: Context
) : FrameLayout(context) {
- lateinit var measurementCache : GuaranteedMeasurementCache
- var onMeasureListener: ((MeasurementInput) -> Unit)? = null
+ lateinit var measurementManager: MeasurementManager
@SuppressLint("DrawAllocation")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -45,64 +46,63 @@
val widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.getMode(widthMeasureSpec))
val height = MeasureSpec.getSize(heightMeasureSpec) - paddingVertical
val heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.getMode(heightMeasureSpec))
- val measurementInput = MeasurementInputData(widthSpec, heightSpec)
- onMeasureListener?.apply {
- invoke(measurementInput)
- }
+ val measurementInput = MeasurementInput(widthSpec, heightSpec)
+
+ // Let's make sure the measurementManager knows about our size, to ensure that we have
+ // a value available. This might perform a measure internally if we don't have a cached
+ // size.
+ val (cachedWidth, cachedHeight) = measurementManager.onMeasure(measurementInput)
+
if (!isCurrentHost()) {
- // We're not currently the host, let's get the dimension from our cache (this might
- // perform a measuring if the cache doesn't have it yet)
+ // We're not currently the host, let's use the dimension from our cache
// The goal here is that the view will always have a consistent measuring, regardless
// if it's attached or not.
// The behavior is therefore very similar to the view being persistently attached to
// this host, which can prevent flickers. It also makes sure that we always know
// the size of the view during transitions even if it has never been attached here
// before.
- val (cachedWidth, cachedHeight) = measurementCache.obtainMeasurement(measurementInput)
setMeasuredDimension(cachedWidth + paddingHorizontal, cachedHeight + paddingVertical)
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Let's update our cache
- val child = getChildAt(0)!!
- val output = MeasurementOutput(child.measuredWidth, child.measuredHeight)
- measurementCache.putMeasurement(measurementInput, output)
+ getChildAt(0)?.requiresRemeasuring = false
}
}
+ override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
+ if (child?.measuredWidth == 0 || measuredWidth == 0 || child?.requiresRemeasuring == true) {
+ super.addView(child, index, params)
+ return
+ }
+ // Suppress layouts when adding a view. The view should already be laid out with the
+ // right size when being attached to this view
+ invalidate()
+ addViewInLayout(child, index, params, true /* preventRequestLayout */)
+ val left = paddingLeft
+ val top = paddingTop
+ val paddingHorizontal = paddingStart + paddingEnd
+ val paddingVertical = paddingTop + paddingBottom
+ child!!.layout(left,
+ top,
+ left + measuredWidth - paddingHorizontal,
+ top + measuredHeight - paddingVertical)
+ }
+
private fun isCurrentHost() = childCount != 0
-}
-/**
- * A basic view measurement input
- */
-interface MeasurementInput {
- fun sameAs(input: MeasurementInput?): Boolean {
- return equals(input)
+ interface MeasurementManager {
+ fun onMeasure(input: MeasurementInput): MeasurementOutput
}
- val width : Int
- get() {
- return View.MeasureSpec.getSize(widthMeasureSpec)
- }
- val height : Int
- get() {
- return View.MeasureSpec.getSize(heightMeasureSpec)
- }
- var widthMeasureSpec: Int
- var heightMeasureSpec: Int
}
/**
- * The output of a view measurement
+ * Does this view require remeasuring currently outside of the regular measure flow?
*/
-data class MeasurementOutput(
- val measuredWidth: Int,
- val measuredHeight: Int
-)
-
-/**
- * The data object holding a basic view measurement input
- */
-data class MeasurementInputData(
- override var widthMeasureSpec: Int,
- override var heightMeasureSpec: Int
-) : MeasurementInput
+var View.requiresRemeasuring: Boolean
+ get() {
+ val required = getTag(R.id.requires_remeasuring)
+ return required?.equals(true) ?: false
+ }
+ set(value) {
+ setTag(R.id.requires_remeasuring, value)
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7729965..7c9ea6b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -92,6 +92,15 @@
}
/**
+ * @deprecated Please specify @Main or @Background when injecting a Handler or use an Executor.
+ */
+ @Deprecated
+ @Provides
+ public static Handler provideHandler() {
+ return new Handler();
+ }
+
+ /**
* Provide a Background-Thread Executor by default.
*/
@Provides
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 96e868d..9b377ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -50,6 +50,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -68,6 +69,7 @@
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
+import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -674,7 +676,7 @@
mRemoveInterceptor.onNotificationRemoveRequested(
mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL);
- mBubbleController.expandStackAndSelectBubble(key);
+ mBubbleController.expandStackAndSelectBubble(mRow.getEntry());
assertTrue(mSysUiStateBubblesExpanded);
}
@@ -727,6 +729,9 @@
assertTrue(mBubbleController.hasBubbles());
mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
+ NotificationListenerService.Ranking ranking = new RankingBuilder(
+ mRow.getEntry().getRanking()).setCanBubble(false).build();
+ mRow.getEntry().setRanking(ranking);
mEntryListener.onPreEntryUpdated(mRow.getEntry());
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 66f119a..eca78ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -20,11 +20,8 @@
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
@@ -258,14 +255,15 @@
assertThat(update.updatedBubble.showFlyout()).isFalse();
}
- // COLLAPSED / ADD
+ //
+ // Overflow
+ //
/**
- * 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).
+ * Verifies that when the bubble stack reaches its maximum, the oldest bubble is overflowed.
*/
@Test
- public void test_collapsed_addBubble_atMaxBubbles_overflowsOldest() {
+ public void testOverflow_add_stackAtMaxBubbles_overflowsOldest() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -288,8 +286,12 @@
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
}
+ /**
+ * Verifies that once the number of overflowed bubbles reaches its maximum, the oldest
+ * overflow bubble is removed.
+ */
@Test
- public void testOverflowBubble_maxReached_bubbleRemoved() {
+ public void testOverflow_maxReached_bubbleRemoved() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -308,16 +310,41 @@
}
/**
- * 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()
+ * Verifies that overflow bubbles are canceled on notif entry removal.
*/
@Test
- public void test_collapsed_addBubble_sortAndGrouping() {
+ public void testOverflow_notifCanceled_removesOverflowBubble() {
+ // Setup
+ sendUpdatedEntryAtTime(mEntryA1, 1000);
+ sendUpdatedEntryAtTime(mEntryA2, 2000);
+ sendUpdatedEntryAtTime(mEntryA3, 3000);
+ sendUpdatedEntryAtTime(mEntryB1, 4000);
+ sendUpdatedEntryAtTime(mEntryB2, 5000);
+ sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
+ sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
+ mBubbleData.setListener(mListener);
+
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
+
+ // Test
+ mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
+ verifyUpdateReceived();
+ assertOverflowChangedTo(ImmutableList.of());
+ }
+
+ // COLLAPSED / ADD
+
+ /**
+ * Verifies that new bubbles insert to the left when collapsed.
+ * <p>
+ * Placement within the list is based on {@link Bubble#getLastActivity()}, descending
+ * order (with most recent first).
+ */
+ @Test
+ public void test_collapsed_addBubble() {
// Setup
mBubbleData.setListener(mListener);
@@ -336,41 +363,7 @@
sendUpdatedEntryAtTime(mEntryA2, 4000);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1);
- }
-
- /**
- * 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_sortAndGrouping_withOngoing() {
- // Setup
- mBubbleData.setListener(mListener);
-
- // Test
- setOngoing(mEntryA1, true);
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- verifyUpdateReceived();
- assertOrderNotChanged();
-
- sendUpdatedEntryAtTime(mEntryB1, 2000);
- verifyUpdateReceived();
- assertOrderNotChanged();
-
- sendUpdatedEntryAtTime(mEntryB2, 3000);
- verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1);
-
- sendUpdatedEntryAtTime(mEntryA2, 4000);
- verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1);
+ assertOrderChangedTo(mBubbleA2, mBubbleB2, mBubbleB1, mBubbleA1);
}
/**
@@ -378,7 +371,6 @@
* the collapsed state.
*
* @see #test_collapsed_updateBubble_selectionChanges()
- * @see #test_collapsed_updateBubble_noSelectionChanges_withOngoing()
*/
@Test
public void test_collapsed_addBubble_selectionChanges() {
@@ -403,58 +395,27 @@
assertSelectionChangedTo(mBubbleA2);
}
- /**
- * 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_addBubble_noSelectionChanges_withOngoing() {
- // Setup
- setOngoing(mEntryA1, true);
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1);
- mBubbleData.setListener(mListener);
-
- // Test
- sendUpdatedEntryAtTime(mEntryB1, 2000);
- verifyUpdateReceived();
- assertSelectionNotChanged();
-
- sendUpdatedEntryAtTime(mEntryB2, 3000);
- verifyUpdateReceived();
- assertSelectionNotChanged();
-
- sendUpdatedEntryAtTime(mEntryA2, 4000);
- verifyUpdateReceived();
- assertSelectionNotChanged();
-
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(mBubbleA1); // selection unchanged
- }
-
// COLLAPSED / REMOVE
/**
- * Verifies that groups may reorder when bubbles are removed, while the stack is in the
- * collapsed state.
+ * Verifies order of bubbles after a removal.
*/
@Test
- public void test_collapsed_removeBubble_sortAndGrouping() {
+ public void test_collapsed_removeBubble_sort() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
+ // TODO: this should fail if things work as I expect them to?
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
}
-
/**
* Verifies that onOrderChanged is not called when a bubble is removed if the removal does not
* cause other bubbles to change position.
@@ -465,62 +426,16 @@
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderNotChanged();
}
/**
- * 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_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);
-
- // Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
- verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA2);
- }
-
- /**
- * Verifies that overflow bubbles are canceled on notif entry removal.
- */
- @Test
- public void test_removeOverflowBubble_forCanceledNotif() {
- // Setup
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- sendUpdatedEntryAtTime(mEntryA2, 2000);
- sendUpdatedEntryAtTime(mEntryA3, 3000);
- sendUpdatedEntryAtTime(mEntryB1, 4000);
- sendUpdatedEntryAtTime(mEntryB2, 5000);
- sendUpdatedEntryAtTime(mEntryB3, 6000); // [A2, A3, B1, B2, B3], overflow: [A1]
- sendUpdatedEntryAtTime(mEntryC1, 7000); // [A3, B1, B2, B3, C1], overflow: [A2, A1]
- mBubbleData.setListener(mListener);
-
- // Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
- verifyUpdateReceived();
- assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
-
- // Test
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
- verifyUpdateReceived();
- assertOverflowChangedTo(ImmutableList.of());
- }
-
- /**
* 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.
*/
@@ -530,7 +445,7 @@
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -542,26 +457,26 @@
// COLLAPSED / UPDATE
/**
- * Verifies that bubble and group ordering may change with updates while the stack is in the
+ * Verifies that bubble ordering changes with updates while the stack is in the
* collapsed state.
*/
@Test
- public void test_collapsed_updateBubble_orderAndGrouping() {
+ public void test_collapsed_updateBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
sendUpdatedEntryAtTime(mEntryB1, 5000);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB1, mBubbleB2, mBubbleA2, mBubbleA1);
+ assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleB2, mBubbleA1);
sendUpdatedEntryAtTime(mEntryA1, 6000);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
+ assertOrderChangedTo(mBubbleA1, mBubbleB1, mBubbleA2, mBubbleB2);
}
/**
@@ -573,7 +488,7 @@
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, A1, B2, B1]
+ sendUpdatedEntryAtTime(mEntryA2, 4000); // [A2, B2, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -587,26 +502,6 @@
}
/**
- * Verifies that selection does not change in response to updates when collapsed, if the
- * selected bubble is ongoing.
- */
- @Test
- public void test_collapsed_updateBubble_noSelectionChanges_withOngoing() {
- // Setup
- setOngoing(mEntryA1, true);
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- sendUpdatedEntryAtTime(mEntryB1, 2000);
- sendUpdatedEntryAtTime(mEntryB2, 3000);
- sendUpdatedEntryAtTime(mEntryA2, 4000); // [A1*, A2, B2, B1]
- mBubbleData.setListener(mListener);
-
- // Test
- sendUpdatedEntryAtTime(mEntryB2, 5000); // [A1*, A2, B2, B1]
- verifyUpdateReceived();
- assertSelectionNotChanged();
- }
-
- /**
* Verifies that a request to expand the stack has no effect if there are no bubbles.
*/
@Test
@@ -618,6 +513,9 @@
verifyZeroInteractions(mListener);
}
+ /**
+ * Verifies that removing the last bubble clears the selected bubble and collapses the stack.
+ */
@Test
public void test_collapsed_removeLastBubble_clearsSelectedBubble() {
// Setup
@@ -629,23 +527,22 @@
// Verify the selection was cleared.
verifyUpdateReceived();
+ assertThat(mBubbleData.isExpanded()).isFalse();
assertSelectionCleared();
}
- // EXPANDED / ADD
+ // EXPANDED / ADD / UPDATE
/**
- * Verifies that bubbles added as part of a new group insert before existing groups while
- * expanded.
+ * Verifies that bubbles are added at the front of the stack.
* <p>
- * Placement within the list is based on lastUpdate (post time of the notification), descending
+ * Placement within the list is based on {@link Bubble#getLastActivity()}, descending
* order (with most recent first).
*
- * @see #test_collapsed_addBubble_sortAndGrouping()
- * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ * @see #test_collapsed_addBubble()
*/
@Test
- public void test_expanded_addBubble_sortAndGrouping_newGroup() {
+ public void test_expanded_addBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -656,65 +553,15 @@
// Test
sendUpdatedEntryAtTime(mEntryC1, 4000);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB1, mBubbleC1, mBubbleA2, mBubbleA1);
+ assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1);
}
/**
- * 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_addBubble_sortAndGrouping_newGroup_withOngoing() {
- // Setup
- setOngoing(mEntryA1, true);
- sendUpdatedEntryAtTime(mEntryA1, 1000);
- sendUpdatedEntryAtTime(mEntryA2, 2000);
- sendUpdatedEntryAtTime(mEntryB1, 3000); // [A1*, A2, B1]
- changeExpandedStateAtTime(true, 4000L);
- mBubbleData.setListener(mListener);
-
- // Test
- sendUpdatedEntryAtTime(mEntryC1, 4000);
- verifyUpdateReceived();
- assertOrderChangedTo(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);
-
- // Test
- sendUpdatedEntryAtTime(mEntryA3, 4000);
- verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB1, mBubbleA3, mBubbleA2, mBubbleA1);
- }
-
- // EXPANDED / UPDATE
-
- /**
* 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()
+ * of bubbles.
*/
@Test
- public void test_expanded_updateBubble_sortAndGrouping_noChanges() {
+ public void test_expanded_updateBubble_noChanges() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryA2, 2000);
@@ -733,7 +580,6 @@
* 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_updateBubble_noSelectionChanges() {
@@ -762,26 +608,24 @@
// EXPANDED / REMOVE
/**
- * Verifies that removing a bubble while expanded does not result in reordering of groups
- * or any of the remaining bubbles.
+ * Verifies that removing a bubble while expanded does not result in reordering of bubbles.
*
- * @see #test_collapsed_addBubble_sortAndGrouping()
- * @see #test_expanded_addBubble_sortAndGrouping_existingGroup()
+ * @see #test_collapsed_addBubble()
*/
@Test
- public void test_expanded_removeBubble_sortAndGrouping() {
+ public void test_expanded_removeBubble() {
// Setup
sendUpdatedEntryAtTime(mEntryA1, 1000);
sendUpdatedEntryAtTime(mEntryB1, 2000);
sendUpdatedEntryAtTime(mEntryA2, 3000);
- sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, B1, A2, A1]
+ sendUpdatedEntryAtTime(mEntryB2, 4000); // [B2, A2, B1, A1]
changeExpandedStateAtTime(true, 5000L);
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleB1, mBubbleA2, mBubbleA1);
+ assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleA1);
}
/**
@@ -789,8 +633,7 @@
* 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()
+ * @see #test_collapsed_addBubble()
*/
@Test
public void test_expanded_removeBubble_selectionChanges_whenSelectedRemoved() {
@@ -800,17 +643,17 @@
sendUpdatedEntryAtTime(mEntryA2, 3000);
sendUpdatedEntryAtTime(mEntryB2, 4000);
changeExpandedStateAtTime(true, 5000L);
- mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
+ mBubbleData.setSelectedBubble(mBubbleA2); // [B2, A2^, B1, A1]
mBubbleData.setListener(mListener);
// Test
mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
- assertSelectionChangedTo(mBubbleA1);
-
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
- verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB1);
+
+ mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+ verifyUpdateReceived();
+ assertSelectionChangedTo(mBubbleA1);
}
@Test
@@ -838,7 +681,6 @@
* 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, this new order is kept as is.
*/
@@ -849,13 +691,13 @@
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]
+ changeExpandedStateAtTime(true, 5000L); // [B2=4000, A2=3000, B1=2000, A1=1000]
+ sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, A2=3000, B1=6000, A1=1000]
setCurrentTime(7000);
mBubbleData.setSelectedBubble(mBubbleA2);
mBubbleData.setListener(mListener);
assertThat(mBubbleData.getBubbles()).isEqualTo(
- ImmutableList.of(mBubbleB2, mBubbleB1, mBubbleA2, mBubbleA1));
+ ImmutableList.of(mBubbleB2, mBubbleA2, mBubbleB1, mBubbleA1));
// Test
@@ -863,18 +705,17 @@
// 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: [A2, A1, B1, B2]
+ // In this case, the expected re-expand state will be: [A2^, B1, B2, A1]
//
// collapse -> selected bubble (A2) moves first.
changeExpandedStateAtTime(false, 8000L);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
+ assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleB2, mBubbleA1);
}
/**
* 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.
+ * order becomes invalidated, the stack is resorted and will reflect that when next expanded.
*/
@Test
public void test_expansionChanges_withUpdatesWhileCollapsed() {
@@ -883,10 +724,10 @@
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]
+ changeExpandedStateAtTime(true, 5000L); // [B2=4000, A2=3000, B1=2000, A1=1000]
+ sendUpdatedEntryAtTime(mEntryB1, 6000); // [B2=4000, A2=3000, B1=6000, A1=1000]
setCurrentTime(7000);
- mBubbleData.setSelectedBubble(mBubbleA2); // [B2, B1, ^A2, A1]
+ mBubbleData.setSelectedBubble(mBubbleA2); // [B2, A2^, B1, A1]
mBubbleData.setListener(mListener);
// Test
@@ -895,7 +736,7 @@
// 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]
+ // In this case, the expected re-expand state will be: [A2^, B1, B2, A1]
//
// That state is restored as long as no changes occur (add/remove/update) while in
// the collapsed state.
@@ -903,11 +744,12 @@
// collapse -> selected bubble (A2) moves first.
changeExpandedStateAtTime(false, 8000L);
verifyUpdateReceived();
- assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB1, mBubbleB2);
+ assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleB2, mBubbleA1);
// An update occurs, which causes sorting, and this invalidates the previously saved order.
- sendUpdatedEntryAtTime(mEntryA2, 9000);
+ sendUpdatedEntryAtTime(mEntryA1, 9000);
verifyUpdateReceived();
+ assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB1, mBubbleB2);
// No order changes when expanding because the new sorted order remains.
changeExpandedStateAtTime(true, 10000L);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 73b8760..b18d67b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -46,6 +46,7 @@
import android.os.Handler;
import android.os.PowerManager;
import android.service.dreams.IDreamManager;
+import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -62,6 +63,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -640,6 +642,9 @@
assertTrue(mBubbleController.hasBubbles());
mRow.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
+ NotificationListenerService.Ranking ranking = new RankingBuilder(
+ mRow.getEntry().getRanking()).setCanBubble(false).build();
+ mRow.getEntry().setRanking(ranking);
mEntryListener.onEntryUpdated(mRow.getEntry());
assertFalse(mBubbleController.hasBubbles());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 8db57cd..487452b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -65,6 +65,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -119,6 +120,7 @@
@Mock GlobalActionsPanelPlugin mWalletPlugin;
@Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
+ @Mock private CurrentUserContextTracker mCurrentUserContextTracker;
private TestableLooper mTestableLooper;
@@ -129,6 +131,7 @@
allowTestableLooperAsMainThread();
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
+ when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext);
mGlobalActionsDialog = new GlobalActionsDialog(mContext,
mWindowManagerFuncs,
mAudioManager,
@@ -161,7 +164,8 @@
mUiEventLogger,
mRingerModeTracker,
mSysUiState,
- mHandler
+ mHandler,
+ mCurrentUserContextTracker
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 4d30500..b71a62c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -31,30 +31,24 @@
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
-
-import androidx.constraintlayout.motion.widget.MotionLayout
-import androidx.constraintlayout.motion.widget.MotionScene
-import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
-
import com.google.common.truth.Truth.assertThat
-
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
-import java.util.ArrayList
-
private const val KEY = "TEST_KEY"
private const val APP = "APP"
private const val BG_COLOR = Color.RED
@@ -78,8 +72,8 @@
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var holder: PlayerViewHolder
- @Mock private lateinit var motion: MotionLayout
- private lateinit var background: TextView
+ @Mock private lateinit var view: TransitionLayout
+ @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager
private lateinit var appIcon: ImageView
private lateinit var appName: TextView
private lateinit var albumView: ImageView
@@ -107,21 +101,15 @@
bgExecutor = FakeExecutor(FakeSystemClock())
activityStarter = mock(ActivityStarter::class.java)
+ mediaHostStatesManager = mock(MediaHostStatesManager::class.java)
- player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter)
+ player = MediaControlPanel(context, fgExecutor, bgExecutor, activityStarter,
+ mediaHostStatesManager)
// Mock out a view holder for the player to attach to.
holder = mock(PlayerViewHolder::class.java)
- motion = mock(MotionLayout::class.java)
- val trans: ArrayList<MotionScene.Transition> = ArrayList()
- trans.add(mock(MotionScene.Transition::class.java))
- whenever(motion.definedTransitions).thenReturn(trans)
- val constraintSet = mock(ConstraintSet::class.java)
- whenever(motion.getConstraintSet(R.id.expanded)).thenReturn(constraintSet)
- whenever(motion.getConstraintSet(R.id.collapsed)).thenReturn(constraintSet)
- whenever(holder.player).thenReturn(motion)
- background = TextView(context)
- whenever(holder.background).thenReturn(background)
+ view = mock(TransitionLayout::class.java)
+ whenever(holder.player).thenReturn(view)
appIcon = ImageView(context)
whenever(holder.appIcon).thenReturn(appIcon)
appName = TextView(context)
@@ -205,7 +193,9 @@
val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device)
player.bind(state)
- assertThat(background.getBackgroundTintList()).isEqualTo(ColorStateList.valueOf(BG_COLOR))
+ val list = ArgumentCaptor.forClass(ColorStateList::class.java)
+ verify(view).setBackgroundTintList(list.capture())
+ assertThat(list.value).isEqualTo(ColorStateList.valueOf(BG_COLOR))
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 7b80a6e..c0aef8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.Notification
+import android.graphics.drawable.Drawable
import android.media.MediaMetadata
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
@@ -73,6 +74,7 @@
private lateinit var fakeExecutor: FakeExecutor
@Mock private lateinit var listener: MediaDeviceManager.Listener
@Mock private lateinit var device: MediaDevice
+ @Mock private lateinit var icon: Drawable
@Mock private lateinit var route: RoutingSessionInfo
private lateinit var session: MediaSession
private lateinit var metadataBuilder: MediaMetadata.Builder
@@ -89,6 +91,7 @@
// Configure mocks.
whenever(device.name).thenReturn(DEVICE_NAME)
+ whenever(device.iconWithoutBackground).thenReturn(icon)
whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm)
whenever(lmm.getCurrentConnectedDevice()).thenReturn(device)
whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route)
@@ -157,6 +160,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
+ assertThat(data.icon).isEqualTo(icon)
}
@Test
@@ -170,6 +174,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
+ assertThat(data.icon).isEqualTo(icon)
}
@Test
@@ -183,6 +188,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isTrue()
assertThat(data.name).isEqualTo(DEVICE_NAME)
+ assertThat(data.icon).isEqualTo(icon)
}
@Test
@@ -204,6 +210,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
+ assertThat(data.icon).isNull()
}
@Test
@@ -221,6 +228,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
+ assertThat(data.icon).isNull()
}
@Test
@@ -238,6 +246,7 @@
val data = captureDeviceData(KEY)
assertThat(data.enabled).isFalse()
assertThat(data.name).isNull()
+ assertThat(data.icon).isNull()
}
fun captureCallback(): LocalMediaManager.DeviceCallback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
new file mode 100644
index 0000000..c9e6f55
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaHierarchyManagerTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 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.media
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.animation.UniqueObjectHostView
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaHierarchyManagerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var lockHost: MediaHost
+ @Mock
+ private lateinit var qsHost: MediaHost
+ @Mock
+ private lateinit var qqsHost: MediaHost
+ @Mock
+ private lateinit var bypassController: KeyguardBypassController
+ @Mock
+ private lateinit var mediaFrame: ViewGroup
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock
+ private lateinit var notificationLockscreenUserManager: NotificationLockscreenUserManager
+ @Mock
+ private lateinit var mediaViewManager: MediaViewManager
+ @Mock
+ private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+ @Captor
+ private lateinit var wakefullnessObserver: ArgumentCaptor<(WakefulnessLifecycle.Observer)>
+ @JvmField
+ @Rule
+ val mockito = MockitoJUnit.rule()
+ private lateinit var mediaHiearchyManager: MediaHierarchyManager
+
+ @Before
+ fun setup() {
+ `when`(mediaViewManager.mediaFrame).thenReturn(mediaFrame)
+ mediaHiearchyManager = MediaHierarchyManager(
+ context,
+ statusBarStateController,
+ keyguardStateController,
+ bypassController,
+ mediaViewManager,
+ notificationLockscreenUserManager,
+ wakefulnessLifecycle)
+ verify(wakefulnessLifecycle).addObserver(wakefullnessObserver.capture())
+ setupHost(lockHost, MediaHierarchyManager.LOCATION_LOCKSCREEN)
+ setupHost(qsHost, MediaHierarchyManager.LOCATION_QS)
+ setupHost(qqsHost, MediaHierarchyManager.LOCATION_QQS)
+ `when`(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ // We'll use the viewmanager to verify a few calls below, let's reset this.
+ clearInvocations(mediaViewManager)
+
+ }
+
+ private fun setupHost(host: MediaHost, location: Int) {
+ `when`(host.location).thenReturn(location)
+ `when`(host.currentBounds).thenReturn(Rect())
+ `when`(host.hostView).thenReturn(UniqueObjectHostView(context))
+ mediaHiearchyManager.register(host)
+ }
+
+ @Test
+ fun testHostViewSetOnRegister() {
+ val host = mediaHiearchyManager.register(lockHost)
+ verify(lockHost).hostView = eq(host)
+ }
+
+ @Test
+ fun testBlockedWhenScreenTurningOff() {
+ // Let's set it onto QS:
+ mediaHiearchyManager.qsExpansion = 1.0f
+ verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ val observer = wakefullnessObserver.value
+ assertNotNull("lifecycle observer wasn't registered", observer)
+ observer.onStartedGoingToSleep()
+ clearInvocations(mediaViewManager)
+ mediaHiearchyManager.qsExpansion = 0.0f
+ verify(mediaViewManager, times(0)).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ }
+
+ @Test
+ fun testAllowedWhenNotTurningOff() {
+ // Let's set it onto QS:
+ mediaHiearchyManager.qsExpansion = 1.0f
+ verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ val observer = wakefullnessObserver.value
+ assertNotNull("lifecycle observer wasn't registered", observer)
+ clearInvocations(mediaViewManager)
+ mediaHiearchyManager.qsExpansion = 0.0f
+ verify(mediaViewManager).onDesiredLocationChanged(ArgumentMatchers.anyInt(),
+ any(MediaHostState::class.java), anyBoolean(), anyLong(), anyLong())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
index 7678525..d6849bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/PlayerViewHolderTest.kt
@@ -57,6 +57,6 @@
@Test
fun backgroundIsIlluminationDrawable() {
val holder = PlayerViewHolder.create(inflater, parent)
- assertThat(holder.background.background as IlluminationDrawable).isNotNull()
+ assertThat(holder.player.background as IlluminationDrawable).isNotNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
index 425bf88..f404f04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipBoundsHandlerTest.java
@@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
import android.content.ComponentName;
import android.graphics.Rect;
@@ -32,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.wm.DisplayController;
import org.junit.Before;
import org.junit.Test;
@@ -63,7 +65,8 @@
@Before
public void setUp() throws Exception {
initializeMockResources();
- mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext));
+ mPipBoundsHandler = new PipBoundsHandler(mContext, new PipSnapAlgorithm(mContext),
+ mock(DisplayController.class));
mTestComponentName1 = new ComponentName(mContext, "component1");
mTestComponentName2 = new ComponentName(mContext, "component2");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 2c4304d..d3b3399 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -22,7 +22,6 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -37,6 +36,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
@@ -79,13 +79,12 @@
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
ScreenshotNotificationSmartActionsProvider smartActionsProvider = mock(
ScreenshotNotificationSmartActionsProvider.class);
- when(smartActionsProvider.getActions(any(), any(), any(), any(),
- eq(false))).thenThrow(
- RuntimeException.class);
+ when(smartActionsProvider.getActions(any(), any(), any(), any(), any()))
+ .thenThrow(RuntimeException.class);
CompletableFuture<List<Notification.Action>> smartActionsFuture =
ScreenshotSmartActions.getSmartActionsFuture(
"", Uri.parse("content://authority/data"), bitmap, smartActionsProvider,
- true, false);
+ true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
assertEquals(Collections.emptyList(), smartActions);
@@ -125,9 +124,8 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
ScreenshotSmartActions.getSmartActionsFuture(
"", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider,
- true, true);
- verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(),
- eq(false));
+ true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+ verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(), any());
assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
assertEquals(Collections.emptyList(), smartActions);
@@ -140,9 +138,8 @@
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
ScreenshotSmartActions.getSmartActionsFuture(
"", Uri.parse("content://autority/data"), bitmap, mSmartActionsProvider, true,
- true);
- verify(mSmartActionsProvider, times(1))
- .getActions(any(), any(), any(), any(), eq(true));
+ UserHandle.getUserHandleForUid(UserHandle.myUserId()));
+ verify(mSmartActionsProvider, times(1)).getActions(any(), any(), any(), any(), any());
}
// Tests for a hardware bitmap, a completed future is returned.
@@ -157,7 +154,7 @@
CompletableFuture<List<Notification.Action>> smartActionsFuture =
ScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
actionsProvider,
- true, true);
+ true, UserHandle.getUserHandleForUid(UserHandle.myUserId()));
assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
assertEquals(smartActions.size(), 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 5cbfcc1..e254cd2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -117,21 +117,12 @@
/**
* Sets up the state such that any requests to
- * {@link NotificationInterruptStateProviderImpl#canAlertAwakeCommon(NotificationEntry)} will
- * pass as long its provided NotificationEntry fulfills launch fullscreen check.
- */
- private void ensureStateForAlertAwakeCommon() {
- when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
- }
-
- /**
- * Sets up the state such that any requests to
* {@link NotificationInterruptStateProviderImpl#shouldHeadsUp(NotificationEntry)} will
* pass as long its provided NotificationEntry fulfills importance & DND checks.
*/
private void ensureStateForHeadsUpWhenAwake() throws RemoteException {
ensureStateForAlertCommon();
- ensureStateForAlertAwakeCommon();
+ when(mHeadsUpManager.isSnoozed(any())).thenReturn(false);
when(mStatusBarStateController.isDozing()).thenReturn(false);
when(mDreamManager.isDreaming()).thenReturn(false);
@@ -157,7 +148,6 @@
*/
private void ensureStateForBubbleUp() {
ensureStateForAlertCommon();
- ensureStateForAlertAwakeCommon();
}
@Test
@@ -392,7 +382,6 @@
@Test
public void testShouldNotHeadsUp_snoozedPackage() {
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
- ensureStateForAlertAwakeCommon();
when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true);
@@ -402,7 +391,6 @@
@Test
public void testShouldNotHeadsUp_justLaunchedFullscreen() {
- ensureStateForAlertAwakeCommon();
// On screen alerts don't happen when that package has just launched fullscreen.
NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 53a1773..acdb2c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -282,7 +282,7 @@
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+ verify(mBubbleController).expandStackAndSelectBubble(eq(mBubbleNotificationRow.getEntry()));
// This is called regardless, and simply short circuits when there is nothing to do.
verify(mShadeController, atLeastOnce()).collapsePanel();
@@ -313,7 +313,7 @@
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+ verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
verify(mShadeController, atLeastOnce()).collapsePanel();
@@ -343,7 +343,7 @@
mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow);
// Then
- verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey()));
+ verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry());
verify(mShadeController, atLeastOnce()).collapsePanel();
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index e5c0540..45d53a1 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -68,8 +68,7 @@
public abstract class SystemService {
/** @hide */
- // TODO(b/133242016) STOPSHIP: change to false before R ships
- protected static final boolean DEBUG_USER = true;
+ protected static final boolean DEBUG_USER = false;
/*
* The earliest boot phase the system send to system services on boot.
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index cca6046..ec12aeb 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -4914,8 +4914,13 @@
// TODO: remove this toast after feature development is done
void showWhileInUseDebugToastLocked(int uid, int op, int mode) {
- for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) {
- ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i);
+ final UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(uid);
+ if (uidRec == null) {
+ return;
+ }
+
+ for (int i = uidRec.procRecords.size() - 1; i >= 0; i--) {
+ ProcessRecord pr = uidRec.procRecords.valueAt(i);
if (pr.uid != uid) {
continue;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 671733b..7ef527c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11112,6 +11112,14 @@
}
pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid);
pw.print(": "); pw.println(uidRec);
+ pw.print(" curProcState="); pw.print(uidRec.mCurProcState);
+ pw.print(" curCapability=");
+ ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability);
+ pw.println();
+ for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) {
+ pw.print(" proc=");
+ pw.println(uidRec.procRecords.valueAt(j));
+ }
}
return printed;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f7a158a..a81590c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -324,8 +324,21 @@
boolean success = applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
if (uidRec != null) {
- updateAppUidRecLocked(app);
- // If this proc state is changed, need to update its uid record here
+ // After uidRec.reset() above, for UidRecord that has multiple processes (ProcessRecord)
+ // , We need to apply all ProcessRecord into UidRecord.
+ final ArraySet<ProcessRecord> procRecords = app.uidRecord.procRecords;
+ for (int i = procRecords.size() - 1; i >= 0; i--) {
+ final ProcessRecord pr = procRecords.valueAt(i);
+ if (!pr.killedByAm && pr.thread != null) {
+ if (pr.isolated && pr.numberOfRunningServices() <= 0
+ && pr.isolatedEntryPoint == null) {
+ // No op.
+ } else {
+ // Keeping this process, update its uid.
+ updateAppUidRecLocked(pr);
+ }
+ }
+ }
if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
&& (uidRec.setProcState != uidRec.getCurProcState()
|| uidRec.setCapability != uidRec.curCapability
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 108fb7d..9f2a77c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2808,6 +2808,7 @@
uidRec.curCapability);
}
proc.uidRecord = uidRec;
+ uidRec.procRecords.add(proc);
// Reset render thread tid if it was already set, so new process can set it again.
proc.renderThreadTid = 0;
@@ -2901,6 +2902,7 @@
}
if (old != null && old.uidRecord != null) {
old.uidRecord.numProcs--;
+ old.uidRecord.procRecords.remove(old);
if (old.uidRecord.numProcs == 0) {
// No more processes using this uid, tell clients it is gone.
if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java
index acf8b2e..c84ccb2 100644
--- a/services/core/java/com/android/server/am/UidRecord.java
+++ b/services/core/java/com/android/server/am/UidRecord.java
@@ -21,6 +21,7 @@
import android.content.pm.PackageManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.util.ArraySet;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
@@ -32,7 +33,7 @@
*/
public final class UidRecord {
final int uid;
- private int mCurProcState;
+ int mCurProcState;
int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
int curCapability;
int setCapability;
@@ -44,6 +45,7 @@
boolean idle;
boolean setIdle;
int numProcs;
+ ArraySet<ProcessRecord> procRecords = new ArraySet<>();
/**
* Sequence number associated with the {@link #mCurProcState}. This is incremented using
diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java
index d7d413c..0fa339f 100644
--- a/services/core/java/com/android/server/notification/BubbleExtractor.java
+++ b/services/core/java/com/android/server/notification/BubbleExtractor.java
@@ -75,9 +75,19 @@
mConfig.getBubblePreference(
record.getSbn().getPackageName(), record.getSbn().getUid());
NotificationChannel recordChannel = record.getChannel();
+ boolean canPresentAsBubble = canPresentAsBubble(record)
+ && !mActivityManager.isLowRamDevice()
+ && record.isConversation()
+ && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0;
- if (!mConfig.bubblesEnabled() || bubblePreference == BUBBLE_PREFERENCE_NONE) {
+ if (!mConfig.bubblesEnabled()
+ || bubblePreference == BUBBLE_PREFERENCE_NONE
+ || !canPresentAsBubble) {
record.setAllowBubble(false);
+ if (!canPresentAsBubble) {
+ // clear out bubble metadata since it can't be used
+ record.getNotification().setBubbleMetadata(null);
+ }
} else if (recordChannel == null) {
// the app is allowed but there's no channel to check
record.setAllowBubble(true);
@@ -86,14 +96,15 @@
} else if (bubblePreference == BUBBLE_PREFERENCE_SELECTED) {
record.setAllowBubble(recordChannel.canBubble());
}
+ if (DBG) {
+ Slog.d(TAG, "record: " + record.getKey()
+ + " appPref: " + bubblePreference
+ + " canBubble: " + record.canBubble()
+ + " canPresentAsBubble: " + canPresentAsBubble
+ + " flagRemoved: " + record.isFlagBubbleRemoved());
+ }
- final boolean fulfillsPolicy = record.canBubble()
- && record.isConversation()
- && !mActivityManager.isLowRamDevice()
- && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0;
- final boolean applyFlag = fulfillsPolicy
- && canPresentAsBubble(record)
- && !record.isFlagBubbleRemoved();
+ final boolean applyFlag = record.canBubble() && !record.isFlagBubbleRemoved();
if (applyFlag) {
record.getNotification().flags |= FLAG_BUBBLE;
} else {
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index e79d33f..0fa522c 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -28,6 +28,7 @@
import android.os.Binder;
import android.os.Handler;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -111,6 +112,15 @@
}
if (!foundShortcut) {
bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId));
+ shortcutBubbles.remove(shortcutId);
+ if (shortcutBubbles.isEmpty()) {
+ mActiveShortcutBubbles.remove(packageName);
+ if (mLauncherAppsCallbackRegistered
+ && mActiveShortcutBubbles.isEmpty()) {
+ mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
+ mLauncherAppsCallbackRegistered = false;
+ }
+ }
}
}
}
@@ -209,15 +219,16 @@
* @param removedNotification true if this notification is being removed
* @param handler handler to register the callback with
*/
- void maybeListenForShortcutChangesForBubbles(NotificationRecord r, boolean removedNotification,
+ void maybeListenForShortcutChangesForBubbles(NotificationRecord r,
+ boolean removedNotification,
Handler handler) {
final String shortcutId = r.getNotification().getBubbleMetadata() != null
? r.getNotification().getBubbleMetadata().getShortcutId()
: null;
- if (shortcutId == null) {
- return;
- }
- if (r.getNotification().isBubbleNotification() && !removedNotification) {
+ if (!removedNotification
+ && !TextUtils.isEmpty(shortcutId)
+ && r.getShortcutInfo() != null
+ && r.getShortcutInfo().getId().equals(shortcutId)) {
// Must track shortcut based bubbles in case the shortcut is removed
HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
r.getSbn().getPackageName());
@@ -235,10 +246,21 @@
HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get(
r.getSbn().getPackageName());
if (packageBubbles != null) {
- packageBubbles.remove(shortcutId);
- }
- if (packageBubbles != null && packageBubbles.isEmpty()) {
- mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
+ if (!TextUtils.isEmpty(shortcutId)) {
+ packageBubbles.remove(shortcutId);
+ } else {
+ // Check if there was a matching entry
+ for (String pkgShortcutId : packageBubbles.keySet()) {
+ String entryKey = packageBubbles.get(pkgShortcutId);
+ if (r.getKey().equals(entryKey)) {
+ // No longer has shortcut id so remove it
+ packageBubbles.remove(pkgShortcutId);
+ }
+ }
+ }
+ if (packageBubbles.isEmpty()) {
+ mActiveShortcutBubbles.remove(r.getSbn().getPackageName());
+ }
}
if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) {
mLauncherAppsService.unregisterCallback(mLauncherAppsCallback);
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 910ed44..19fa920 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -30,6 +30,7 @@
import com.android.server.FgThread;
+import java.io.File;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@@ -124,9 +125,13 @@
}
}
- String getIdmapPath(String overlayPath, int userId) throws TimeoutException, RemoteException {
+ boolean idmapExists(String overlayPath, int userId) {
try (Connection c = connect()) {
- return mService.getIdmapPath(overlayPath, userId);
+ return new File(mService.getIdmapPath(overlayPath, userId)).isFile();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": "
+ + e.getMessage());
+ return false;
}
}
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index 90c85ad..735d669 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -27,10 +27,10 @@
import android.os.Build.VERSION_CODES;
import android.os.OverlayablePolicy;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.util.Slog;
-import java.io.File;
+import com.android.internal.util.ArrayUtils;
+
import java.io.IOException;
/**
@@ -55,12 +55,16 @@
VENDOR_IS_Q_OR_LATER = isQOrLater;
}
- private final OverlayableInfoCallback mOverlayableCallback;
private final IdmapDaemon mIdmapDaemon;
+ private final OverlayableInfoCallback mOverlayableCallback;
+ private final String mOverlayableConfigurator;
+ private final String[] mOverlayableConfiguratorTargets;
- IdmapManager(final OverlayableInfoCallback verifyCallback) {
+ IdmapManager(final IdmapDaemon idmapDaemon, final OverlayableInfoCallback verifyCallback) {
mOverlayableCallback = verifyCallback;
- mIdmapDaemon = IdmapDaemon.getInstance();
+ mIdmapDaemon = idmapDaemon;
+ mOverlayableConfigurator = verifyCallback.getOverlayableConfigurator();
+ mOverlayableConfiguratorTargets = verifyCallback.getOverlayableConfiguratorTargets() ;
}
/**
@@ -103,23 +107,11 @@
}
boolean idmapExists(@NonNull final OverlayInfo oi) {
- return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile();
+ return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId);
}
boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId))
- .isFile();
- }
-
- private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath,
- final int userId) {
- try {
- return mIdmapDaemon.getIdmapPath(overlayPackagePath, userId);
- } catch (Exception e) {
- Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": "
- + e.getMessage());
- return "";
- }
+ return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId);
}
/**
@@ -198,9 +190,17 @@
String targetOverlayableName = overlayPackage.targetOverlayableName;
if (targetOverlayableName != null) {
try {
+ if (!mOverlayableConfigurator.isEmpty()
+ && ArrayUtils.contains(mOverlayableConfiguratorTargets,
+ targetPackage.packageName)
+ && mOverlayableCallback.signaturesMatching(mOverlayableConfigurator,
+ overlayPackage.packageName, userId)) {
+ return true;
+ }
+
OverlayableInfo overlayableInfo = mOverlayableCallback.getOverlayableForTarget(
targetPackage.packageName, targetOverlayableName, userId);
- if (overlayableInfo != null) {
+ if (overlayableInfo != null && overlayableInfo.actor != null) {
String actorPackageName = OverlayActorEnforcer.getPackageNameForActor(
overlayableInfo.actor, mOverlayableCallback.getNamedActors()).first;
if (mOverlayableCallback.signaturesMatching(actorPackageName,
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index ef6655d..2bc3499 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -50,8 +50,8 @@
/**
* @return nullable actor result with {@link ActorState} failure status
*/
- static Pair<String, ActorState> getPackageNameForActor(String actorUriString,
- Map<String, Map<String, String>> namedActors) {
+ static Pair<String, ActorState> getPackageNameForActor(@NonNull String actorUriString,
+ @NonNull Map<String, Map<String, String>> namedActors) {
Uri actorUri = Uri.parse(actorUriString);
String actorScheme = actorUri.getScheme();
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 6a8e465..086ab81 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -45,6 +45,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
import android.content.res.ApkAssets;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
@@ -62,6 +63,7 @@
import android.util.Slog;
import android.util.SparseArray;
+import com.android.internal.R;
import com.android.internal.content.om.OverlayConfig;
import com.android.server.FgThread;
import com.android.server.IoThread;
@@ -246,7 +248,7 @@
new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays");
mPackageManager = new PackageManagerHelperImpl(context);
mUserManager = UserManagerService.getInstance();
- IdmapManager im = new IdmapManager(mPackageManager);
+ IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager);
mSettings = new OverlayManagerSettings();
mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
@@ -1119,6 +1121,17 @@
}
@Override
+ public String getOverlayableConfigurator() {
+ return Resources.getSystem().getString(R.string.config_overlayableConfigurator);
+ }
+
+ @Override
+ public String[] getOverlayableConfiguratorTargets() {
+ return Resources.getSystem().getStringArray(
+ R.array.config_overlayableConfiguratorTargets);
+ }
+
+ @Override
public List<PackageInfo> getOverlayPackages(final int userId) {
final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId);
for (final PackageInfo info : overlays) {
diff --git a/services/core/java/com/android/server/om/OverlayableInfoCallback.java b/services/core/java/com/android/server/om/OverlayableInfoCallback.java
index 5066ecd..41c341a 100644
--- a/services/core/java/com/android/server/om/OverlayableInfoCallback.java
+++ b/services/core/java/com/android/server/om/OverlayableInfoCallback.java
@@ -80,4 +80,24 @@
* in the system returns {@link PackageManager#SIGNATURE_MATCH}
*/
boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId);
+
+ /**
+ * Retrieves the package name that is recognized as an actor for the packages specified by
+ * {@link #getOverlayableConfiguratorTargets()}.
+ */
+ @NonNull
+ default String getOverlayableConfigurator() {
+ return "";
+ }
+
+ /**
+ * Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an
+ * actor for its overlayable declarations. Overlays targeting one of the specified targets that
+ * are signed with the same signature as the overlayable configurator will be granted the
+ * "actor" policy.
+ */
+ @NonNull
+ default String[] getOverlayableConfiguratorTargets() {
+ return new String[0];
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f8278de..7ab05c4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -105,8 +105,10 @@
import android.os.RevocableFileDescriptor;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.incremental.IStorageHealthListener;
import android.os.incremental.IncrementalFileStorages;
import android.os.incremental.IncrementalManager;
+import android.os.incremental.StorageHealthCheckParams;
import android.os.storage.StorageManager;
import android.provider.Settings.Secure;
import android.stats.devicepolicy.DevicePolicyEnums;
@@ -231,6 +233,10 @@
private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
+ private static final int INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS = 2000;
+ private static final int INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS = 7000;
+ private static final int INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS = 60000;
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -1568,7 +1574,7 @@
dispatchSessionFinished(error, detailMessage, null);
}
- private void onDataLoaderUnrecoverable() {
+ private void onStorageUnhealthy() {
if (TextUtils.isEmpty(mPackageName)) {
// The package has not been installed.
return;
@@ -2745,7 +2751,7 @@
final DataLoaderParams params = this.params.dataLoaderParams;
final boolean manualStartAndDestroy = !isIncrementalInstallation();
- final IDataLoaderStatusListener listener = new IDataLoaderStatusListener.Stub() {
+ final IDataLoaderStatusListener statusListener = new IDataLoaderStatusListener.Stub() {
@Override
public void onStatusChanged(int dataLoaderId, int status) {
switch (status) {
@@ -2757,7 +2763,7 @@
if (mDestroyed || mDataLoaderFinished) {
switch (status) {
case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE:
- onDataLoaderUnrecoverable();
+ onStorageUnhealthy();
return;
}
return;
@@ -2840,9 +2846,49 @@
};
if (!manualStartAndDestroy) {
+ final StorageHealthCheckParams healthCheckParams = new StorageHealthCheckParams();
+ healthCheckParams.blockedTimeoutMs = INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS;
+ healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS;
+ healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS;
+
+ final boolean systemDataLoader =
+ params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE;
+ final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() {
+ @Override
+ public void onHealthStatus(int storageId, int status) {
+ if (mDestroyed || mDataLoaderFinished) {
+ // App's installed.
+ switch (status) {
+ case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
+ onStorageUnhealthy();
+ return;
+ }
+ return;
+ }
+
+ switch (status) {
+ case IStorageHealthListener.HEALTH_STATUS_OK:
+ break;
+ case IStorageHealthListener.HEALTH_STATUS_READS_PENDING:
+ case IStorageHealthListener.HEALTH_STATUS_BLOCKED:
+ if (systemDataLoader) {
+ // It's OK for ADB data loader to wait for pages.
+ break;
+ }
+ // fallthrough
+ case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY:
+ // Even ADB installation can't wait for missing pages for too long.
+ mDataLoaderFinished = true;
+ dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Image is missing pages required for installation.");
+ break;
+ }
+ }
+ };
+
try {
mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir,
- params, listener, addedFiles);
+ params, statusListener, healthCheckParams, healthListener, addedFiles);
return false;
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(),
@@ -2850,8 +2896,7 @@
}
}
- if (!dataLoaderManager.bindToDataLoader(
- sessionId, params.getData(), listener)) {
+ if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) {
throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
"Failed to initialize data loader");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index fbe8100..aa7a1ad 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -369,6 +369,7 @@
import com.android.server.policy.PermissionPolicyInternal;
import com.android.server.security.VerityUtils;
import com.android.server.storage.DeviceStorageMonitorInternal;
+import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -4406,6 +4407,11 @@
if (getInstantAppPackageName(callingUid) != null) {
throw new SecurityException("Instant applications don't have access to this method");
}
+ if (!mUserManager.exists(userId)) {
+ throw new SecurityException("User doesn't exist");
+ }
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, false, false, "checkPackageStartable");
final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId);
synchronized (mLock) {
final PackageSetting ps = mSettings.mPackages.get(packageName);
@@ -5778,9 +5784,15 @@
@Override
public ChangedPackages getChangedPackages(int sequenceNumber, int userId) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ final int callingUid = Binder.getCallingUid();
+ if (getInstantAppPackageName(callingUid) != null) {
return null;
}
+ if (!mUserManager.exists(userId)) {
+ return null;
+ }
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, false, false, "getChangedPackages");
synchronized (mLock) {
if (sequenceNumber >= mChangedPackagesSequenceNumber) {
return null;
@@ -8800,9 +8812,23 @@
private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) {
if (!mUserManager.exists(userId)) return null;
- flags = updateFlagsForComponent(flags, userId);
final int callingUid = Binder.getCallingUid();
+ flags = updateFlagsForComponent(flags, userId);
final ProviderInfo providerInfo = mComponentResolver.queryProvider(name, flags, userId);
+ boolean checkedGrants = false;
+ if (providerInfo != null) {
+ // Looking for cross-user grants before enforcing the typical cross-users permissions
+ if (userId != UserHandle.getUserId(callingUid)) {
+ final UriGrantsManagerInternal mUgmInternal =
+ LocalServices.getService(UriGrantsManagerInternal.class);
+ checkedGrants =
+ mUgmInternal.checkAuthorityGrants(callingUid, providerInfo, userId, true);
+ }
+ }
+ if (!checkedGrants) {
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, false, false, "resolveContentProvider");
+ }
if (providerInfo == null) {
return null;
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index e3b1152c..323ac7b 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.tv;
+import static android.media.AudioManager.DEVICE_NONE;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
@@ -2047,6 +2048,36 @@
return clientPid;
}
+ /**
+ * Add a hardware device in the TvInputHardwareManager for CTS testing
+ * purpose.
+ *
+ * @param device id of the adding hardware device.
+ */
+ @Override
+ public void addHardwareDevice(int deviceId) {
+ TvInputHardwareInfo info = new TvInputHardwareInfo.Builder()
+ .deviceId(deviceId)
+ .type(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI)
+ .audioType(DEVICE_NONE)
+ .audioAddress("0")
+ .hdmiPortId(0)
+ .build();
+ mTvInputHardwareManager.onDeviceAvailable(info, null);
+ return;
+ }
+
+ /**
+ * Remove a hardware device in the TvInputHardwareManager for CTS testing
+ * purpose.
+ *
+ * @param device id of the removing hardware device.
+ */
+ @Override
+ public void removeHardwareDevice(int deviceId) {
+ mTvInputHardwareManager.onDeviceUnavailable(deviceId);
+ }
+
private int getClientPidLocked(String sessionId)
throws IllegalStateException {
if (mSessionIdToSessionStateMap.get(sessionId) == null) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index eb85db6..ad1ee1c 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -498,6 +498,8 @@
*/
private ActivityRecord mFixedRotationLaunchingApp;
+ private FixedRotationAnimationController mFixedRotationAnimationController;
+
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
@@ -1106,12 +1108,14 @@
}
void removeShellRoot(int windowType) {
- ShellRoot root = mShellRoots.get(windowType);
- if (root == null) {
- return;
+ synchronized(mWmService.mGlobalLock) {
+ ShellRoot root = mShellRoots.get(windowType);
+ if (root == null) {
+ return;
+ }
+ root.clear();
+ mShellRoots.remove(windowType);
}
- root.clear();
- mShellRoots.remove(windowType);
}
void setRemoteInsetsController(IDisplayWindowInsetsController controller) {
@@ -1485,6 +1489,11 @@
return mFixedRotationLaunchingApp;
}
+ @VisibleForTesting
+ @Nullable FixedRotationAnimationController getFixedRotationAnimationController() {
+ return mFixedRotationAnimationController;
+ }
+
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) {
setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED);
}
@@ -1492,8 +1501,13 @@
void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) {
if (mFixedRotationLaunchingApp == null && r != null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation);
+ if (mFixedRotationAnimationController == null) {
+ mFixedRotationAnimationController = new FixedRotationAnimationController(this);
+ mFixedRotationAnimationController.hide();
+ }
} else if (mFixedRotationLaunchingApp != null && r == null) {
mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this);
+ finishFixedRotationAnimationIfPossible();
}
mFixedRotationLaunchingApp = r;
}
@@ -1582,6 +1596,15 @@
}
}
+ /** Re-show the previously hidden windows if all seamless rotated windows are done. */
+ void finishFixedRotationAnimationIfPossible() {
+ final FixedRotationAnimationController controller = mFixedRotationAnimationController;
+ if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) {
+ controller.show();
+ mFixedRotationAnimationController = null;
+ }
+ }
+
/**
* Update rotation of the display.
*
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 702df2a..96f2363 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -560,6 +560,7 @@
}, true /* traverseTopToBottom */);
mSeamlessRotationCount = 0;
mRotatingSeamlessly = false;
+ mDisplayContent.finishFixedRotationAnimationIfPossible();
}
private void prepareSeamlessRotation() {
@@ -573,6 +574,10 @@
return mRotatingSeamlessly;
}
+ boolean hasSeamlessRotatingWindow() {
+ return mSeamlessRotationCount > 0;
+ }
+
@VisibleForTesting
boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) {
// Display doesn't need to be frozen because application has been started in correct
@@ -646,6 +651,7 @@
"Performing post-rotate rotation after seamless rotation");
// Finish seamless rotation.
mRotatingSeamlessly = false;
+ mDisplayContent.finishFixedRotationAnimationIfPossible();
updateRotationAndSendNewConfigIfChanged();
}
diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
new file mode 100644
index 0000000..cc02e99
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.AnimationSpecProto.WINDOW;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
+import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION;
+
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import com.android.internal.R;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Controller to fade out and in system ui when applying a fixed rotation transform to a window
+ * token.
+ *
+ * The system bars will be fade out when the fixed rotation transform starts and will be fade in
+ * once all surfaces have been rotated.
+ */
+public class FixedRotationAnimationController {
+
+ private final Context mContext;
+ private final WindowState mStatusBar;
+ private final WindowState mNavigationBar;
+ private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2);
+ private final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>();
+
+ public FixedRotationAnimationController(DisplayContent displayContent) {
+ mContext = displayContent.mWmService.mContext;
+ final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+ mStatusBar = displayPolicy.getStatusBar();
+ // Do not animate movable navigation bar (e.g. non-gesture mode).
+ mNavigationBar = !displayPolicy.navigationBarCanMove()
+ ? displayPolicy.getNavigationBar()
+ : null;
+ }
+
+ /** Applies show animation on the previously hidden window tokens. */
+ void show() {
+ for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) {
+ final WindowToken windowToken = mAnimatedWindowToken.get(i);
+ fadeWindowToken(true /* show */, windowToken);
+ }
+ }
+
+ /** Applies hide animation on the window tokens which may be seamlessly rotated later. */
+ void hide() {
+ if (mNavigationBar != null) {
+ fadeWindowToken(false /* show */, mNavigationBar.mToken);
+ }
+ if (mStatusBar != null) {
+ fadeWindowToken(false /* show */, mStatusBar.mToken);
+ }
+ }
+
+ private void fadeWindowToken(boolean show, WindowToken windowToken) {
+ if (windowToken == null || windowToken.getParent() == null) {
+ return;
+ }
+
+ final Animation animation = AnimationUtils.loadAnimation(mContext,
+ show ? R.anim.fade_in : R.anim.fade_out);
+ final LocalAnimationAdapter.AnimationSpec windowAnimationSpec =
+ createAnimationSpec(animation);
+
+ final FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter(
+ windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken);
+
+ // We deferred the end of the animation when hiding the token, so we need to end it now that
+ // it's shown again.
+ final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> {
+ final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken);
+ if (runnable != null) {
+ runnable.run();
+ }
+ } : null;
+ windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter,
+ show /* hidden */, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback);
+ mAnimatedWindowToken.add(windowToken);
+ }
+
+ private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) {
+ return new LocalAnimationAdapter.AnimationSpec() {
+
+ final Transformation mTransformation = new Transformation();
+
+ @Override
+ public boolean getShowWallpaper() {
+ return true;
+ }
+
+ @Override
+ public long getDuration() {
+ return animation.getDuration();
+ }
+
+ @Override
+ public void apply(SurfaceControl.Transaction t, SurfaceControl leash,
+ long currentPlayTime) {
+ mTransformation.clear();
+ animation.getTransformation(currentPlayTime, mTransformation);
+ t.setAlpha(leash, mTransformation.getAlpha());
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println(animation);
+ }
+
+ @Override
+ public void dumpDebugInner(ProtoOutputStream proto) {
+ final long token = proto.start(WINDOW);
+ proto.write(ANIMATION, animation.toString());
+ proto.end(token);
+ }
+ };
+ }
+
+ private class FixedRotationAnimationAdapter extends LocalAnimationAdapter {
+ private final boolean mShow;
+ private final WindowToken mToken;
+
+ FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec,
+ SurfaceAnimationRunner surfaceAnimationRunner, boolean show,
+ WindowToken token) {
+ super(windowAnimationSpec, surfaceAnimationRunner);
+ mShow = show;
+ mToken = token;
+ }
+
+ @Override
+ public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) {
+ // We defer the end of the hide animation to ensure the tokens stay hidden until
+ // we show them again.
+ if (!mShow) {
+ mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback);
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 99f710b..7a38bb6 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -156,4 +156,3 @@
}
}
}
-
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 42342a6..0143eb1 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -489,6 +489,12 @@
static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5;
/**
+ * Animation when a fixed rotation transform is applied to a window token.
+ * @hide
+ */
+ static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6;
+
+ /**
* Bitmask to include all animation types. This is NOT an {@link AnimationType}
* @hide
*/
@@ -505,7 +511,8 @@
ANIMATION_TYPE_DIMMER,
ANIMATION_TYPE_RECENTS,
ANIMATION_TYPE_WINDOW_ANIMATION,
- ANIMATION_TYPE_INSETS_CONTROL
+ ANIMATION_TYPE_INSETS_CONTROL,
+ ANIMATION_TYPE_FIXED_TRANSFORM
})
@Retention(RetentionPolicy.SOURCE)
@interface AnimationType {}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index e9aff88..5fa4afd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -334,6 +334,8 @@
private boolean mDragResizing;
private boolean mDragResizingChangeReported = true;
private int mResizeMode;
+ private boolean mResizeForBlastSyncReported;
+
/**
* Special mode that is intended only for the rounded corner overlay: during rotation
* transition, we un-rotate the window token such that the window appears as it did before the
@@ -1370,11 +1372,14 @@
// variables, because mFrameSizeChanged only tracks the width and height changing.
updateLastFrames();
+ // Add a window that is using blastSync to the resizing list if it hasn't been reported
+ // already. This because the window is waiting on a finishDrawing from the client.
if (didFrameInsetsChange
|| winAnimator.mSurfaceResized
|| configChanged
|| dragResizingChanged
- || mReportOrientationChanged) {
+ || mReportOrientationChanged
+ || requestResizeForBlastSync()) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b "
+ "dragResizingChanged=%b reportOrientationChanged=%b",
@@ -3483,6 +3488,7 @@
mReportOrientationChanged = false;
mDragResizingChangeReported = true;
mWinAnimator.mSurfaceResized = false;
+ mResizeForBlastSyncReported = true;
mWindowFrames.resetInsetsChanged();
final Rect frame = mWindowFrames.mCompatFrame;
@@ -5500,7 +5506,9 @@
}
long getFrameNumber() {
- return mFrameNumber;
+ // Return the frame number in which changes requested in this layout will be rendered or
+ // -1 if we do not expect the frame to be rendered.
+ return getFrameLw().isEmpty() ? -1 : mFrameNumber;
}
void setFrameNumber(long frameNumber) {
@@ -5733,6 +5741,7 @@
if (!willSync) {
return false;
}
+ mResizeForBlastSyncReported = false;
mLocalSyncId = mBLASTSyncEngine.startSyncSet(this);
addChildrenToSyncSet(mLocalSyncId);
@@ -5777,4 +5786,8 @@
mWaitingListener = null;
return mWinAnimator.finishDrawingLocked(null);
}
+
+ private boolean requestResizeForBlastSync() {
+ return useBLASTSync() && !mResizeForBlastSyncReported;
+ }
}
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 8476674..6018b9e 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -118,14 +118,18 @@
}
binder::Status BinderIncrementalService::createStorage(
- const std::string& path, const content::pm::DataLoaderParamsParcel& params,
- const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
- int32_t createMode, int32_t* _aidl_return) {
+ const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params,
+ int32_t createMode,
+ const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
+ const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
+ const ::android::sp<::android::os::incremental::IStorageHealthListener>& healthListener,
+ int32_t* _aidl_return) {
*_aidl_return =
mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params),
- listener,
- android::incremental::IncrementalService::CreateOptions(
- createMode));
+ android::incremental::IncrementalService::CreateOptions(createMode),
+ statusListener,
+ const_cast<StorageHealthCheckParams&&>(healthCheckParams),
+ healthListener);
return ok();
}
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 5a7d5da..af11363 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -41,8 +41,11 @@
binder::Status openStorage(const std::string& path, int32_t* _aidl_return) final;
binder::Status createStorage(
const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params,
- const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
- int32_t createMode, int32_t* _aidl_return) final;
+ int32_t createMode,
+ const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener,
+ const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams,
+ const ::android::sp<IStorageHealthListener>& healthListener,
+ int32_t* _aidl_return) final;
binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId,
int32_t createMode, int32_t* _aidl_return) final;
binder::Status makeBindMount(int32_t storageId, const std::string& sourcePath,
@@ -55,8 +58,7 @@
binder::Status makeDirectories(int32_t storageId, const std::string& path,
int32_t* _aidl_return) final;
binder::Status makeFile(int32_t storageId, const std::string& path,
- const ::android::os::incremental::IncrementalNewFileParams& params,
- int32_t* _aidl_return) final;
+ const IncrementalNewFileParams& params, int32_t* _aidl_return) final;
binder::Status makeFileFromRange(int32_t storageId, const std::string& targetPath,
const std::string& sourcePath, int64_t start, int64_t end,
int32_t* _aidl_return) final;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index f0dca77..b03d1ea 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -410,9 +410,12 @@
}
}
-StorageId IncrementalService::createStorage(
- std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
- const DataLoaderStatusListener& dataLoaderStatusListener, CreateOptions options) {
+StorageId IncrementalService::createStorage(std::string_view mountPoint,
+ content::pm::DataLoaderParamsParcel&& dataLoaderParams,
+ CreateOptions options,
+ const DataLoaderStatusListener& statusListener,
+ StorageHealthCheckParams&& healthCheckParams,
+ const StorageHealthListener& healthListener) {
LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options);
if (!path::isAbsolute(mountPoint)) {
LOG(ERROR) << "path is not absolute: " << mountPoint;
@@ -545,8 +548,8 @@
// Done here as well, all data structures are in good state.
secondCleanupOnFailure.release();
- auto dataLoaderStub =
- prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener);
+ auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener,
+ std::move(healthCheckParams), &healthListener);
CHECK(dataLoaderStub);
mountIt->second = std::move(ifs);
@@ -1254,7 +1257,7 @@
dataLoaderParams.arguments = loader.arguments();
}
- prepareDataLoader(*ifs, std::move(dataLoaderParams), nullptr);
+ prepareDataLoader(*ifs, std::move(dataLoaderParams));
CHECK(ifs->dataLoaderStub);
std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
@@ -1338,14 +1341,18 @@
IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
IncFsMount& ifs, DataLoaderParamsParcel&& params,
- const DataLoaderStatusListener* externalListener) {
+ const DataLoaderStatusListener* statusListener,
+ StorageHealthCheckParams&& healthCheckParams, const StorageHealthListener* healthListener) {
std::unique_lock l(ifs.lock);
- prepareDataLoaderLocked(ifs, std::move(params), externalListener);
+ prepareDataLoaderLocked(ifs, std::move(params), statusListener, std::move(healthCheckParams),
+ healthListener);
return ifs.dataLoaderStub;
}
void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
- const DataLoaderStatusListener* externalListener) {
+ const DataLoaderStatusListener* statusListener,
+ StorageHealthCheckParams&& healthCheckParams,
+ const StorageHealthListener* healthListener) {
if (ifs.dataLoaderStub) {
LOG(INFO) << "Skipped data loader preparation because it already exists";
return;
@@ -1360,7 +1367,8 @@
ifs.dataLoaderStub =
new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel),
- externalListener, path::join(ifs.root, constants().mount));
+ statusListener, std::move(healthCheckParams), healthListener,
+ path::join(ifs.root, constants().mount));
}
template <class Duration>
@@ -1680,19 +1688,24 @@
IncrementalService::DataLoaderStub::DataLoaderStub(IncrementalService& service, MountId id,
DataLoaderParamsParcel&& params,
FileSystemControlParcel&& control,
- const DataLoaderStatusListener* externalListener,
+ const DataLoaderStatusListener* statusListener,
+ StorageHealthCheckParams&& healthCheckParams,
+ const StorageHealthListener* healthListener,
std::string&& healthPath)
: mService(service),
mId(id),
mParams(std::move(params)),
mControl(std::move(control)),
- mListener(externalListener ? *externalListener : DataLoaderStatusListener()),
+ mStatusListener(statusListener ? *statusListener : DataLoaderStatusListener()),
+ mHealthListener(healthListener ? *healthListener : StorageHealthListener()),
mHealthPath(std::move(healthPath)) {
+ // TODO(b/153874006): enable external health listener.
+ mHealthListener = {};
healthStatusOk();
}
IncrementalService::DataLoaderStub::~DataLoaderStub() {
- if (mId != kInvalidStorageId) {
+ if (isValid()) {
cleanupResources();
}
}
@@ -1710,13 +1723,14 @@
mStatusCondition.wait_until(lock, now + 60s, [this] {
return mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
});
- mListener = {};
+ mStatusListener = {};
+ mHealthListener = {};
mId = kInvalidStorageId;
}
sp<content::pm::IDataLoader> IncrementalService::DataLoaderStub::getDataLoader() {
sp<IDataLoader> dataloader;
- auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader);
+ auto status = mService.mDataLoaderManager->getDataLoader(id(), &dataloader);
if (!status.isOk()) {
LOG(ERROR) << "Failed to get dataloader: " << status.toString8();
return {};
@@ -1752,15 +1766,15 @@
auto oldStatus = mTargetStatus;
mTargetStatus = status;
mTargetStatusTs = Clock::now();
- LOG(DEBUG) << "Target status update for DataLoader " << mId << ": " << oldStatus << " -> "
+ LOG(DEBUG) << "Target status update for DataLoader " << id() << ": " << oldStatus << " -> "
<< status << " (current " << mCurrentStatus << ")";
}
bool IncrementalService::DataLoaderStub::bind() {
bool result = false;
- auto status = mService.mDataLoaderManager->bindToDataLoader(mId, mParams, this, &result);
+ auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result);
if (!status.isOk() || !result) {
- LOG(ERROR) << "Failed to bind a data loader for mount " << mId;
+ LOG(ERROR) << "Failed to bind a data loader for mount " << id();
return false;
}
return true;
@@ -1771,9 +1785,9 @@
if (!dataloader) {
return false;
}
- auto status = dataloader->create(mId, mParams, mControl, this);
+ auto status = dataloader->create(id(), mParams, mControl, this);
if (!status.isOk()) {
- LOG(ERROR) << "Failed to start DataLoader: " << status.toString8();
+ LOG(ERROR) << "Failed to create DataLoader: " << status.toString8();
return false;
}
return true;
@@ -1784,7 +1798,7 @@
if (!dataloader) {
return false;
}
- auto status = dataloader->start(mId);
+ auto status = dataloader->start(id());
if (!status.isOk()) {
LOG(ERROR) << "Failed to start DataLoader: " << status.toString8();
return false;
@@ -1793,7 +1807,7 @@
}
bool IncrementalService::DataLoaderStub::destroy() {
- return mService.mDataLoaderManager->unbindFromDataLoader(mId).isOk();
+ return mService.mDataLoaderManager->unbindFromDataLoader(id()).isOk();
}
bool IncrementalService::DataLoaderStub::fsmStep() {
@@ -1852,8 +1866,8 @@
return binder::Status::
fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub");
}
- if (mId != mountId) {
- LOG(ERROR) << "Mount ID mismatch: expected " << mId << ", but got: " << mountId;
+ if (id() != mountId) {
+ LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId;
return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch.");
}
@@ -1869,7 +1883,7 @@
mCurrentStatus = newStatus;
targetStatus = mTargetStatus;
- listener = mListener;
+ listener = mStatusListener;
if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) {
// For unavailable, unbind from DataLoader to ensure proper re-commit.
@@ -1877,7 +1891,7 @@
}
}
- LOG(DEBUG) << "Current status update for DataLoader " << mId << ": " << oldStatus << " -> "
+ LOG(DEBUG) << "Current status update for DataLoader " << id() << ": " << oldStatus << " -> "
<< newStatus << " (target " << targetStatus << ")";
if (listener) {
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index f3fde2a..bde4ef6 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -21,6 +21,8 @@
#include <android/content/pm/FileSystemControlParcel.h>
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/incremental/BnIncrementalServiceConnector.h>
+#include <android/os/incremental/BnStorageHealthListener.h>
+#include <android/os/incremental/StorageHealthCheckParams.h>
#include <binder/IAppOpsCallback.h>
#include <utils/String16.h>
#include <utils/StrongPointer.h>
@@ -56,10 +58,15 @@
using Clock = std::chrono::steady_clock;
using TimePoint = std::chrono::time_point<Clock>;
using Seconds = std::chrono::seconds;
+using BootClockTsUs = uint64_t;
using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener;
using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>;
+using StorageHealthCheckParams = ::android::os::incremental::StorageHealthCheckParams;
+using IStorageHealthListener = ::android::os::incremental::IStorageHealthListener;
+using StorageHealthListener = ::android::sp<IStorageHealthListener>;
+
class IncrementalService final {
public:
explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir);
@@ -72,6 +79,8 @@
static constexpr StorageId kInvalidStorageId = -1;
static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max();
+ static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max();
+
enum CreateOptions {
TemporaryBind = 1,
PermanentBind = 2,
@@ -97,8 +106,9 @@
StorageId createStorage(std::string_view mountPoint,
content::pm::DataLoaderParamsParcel&& dataLoaderParams,
- const DataLoaderStatusListener& dataLoaderStatusListener,
- CreateOptions options = CreateOptions::Default);
+ CreateOptions options, const DataLoaderStatusListener& statusListener,
+ StorageHealthCheckParams&& healthCheckParams,
+ const StorageHealthListener& healthListener);
StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
CreateOptions options = CreateOptions::Default);
StorageId openStorage(std::string_view path);
@@ -161,7 +171,9 @@
DataLoaderStub(IncrementalService& service, MountId id,
content::pm::DataLoaderParamsParcel&& params,
content::pm::FileSystemControlParcel&& control,
- const DataLoaderStatusListener* externalListener, std::string&& healthPath);
+ const DataLoaderStatusListener* statusListener,
+ StorageHealthCheckParams&& healthCheckParams,
+ const StorageHealthListener* healthListener, std::string&& healthPath);
~DataLoaderStub();
// Cleans up the internal state and invalidates DataLoaderStub. Any subsequent calls will
// result in an error.
@@ -212,7 +224,8 @@
MountId mId = kInvalidStorageId;
content::pm::DataLoaderParamsParcel mParams;
content::pm::FileSystemControlParcel mControl;
- DataLoaderStatusListener mListener;
+ DataLoaderStatusListener mStatusListener;
+ StorageHealthListener mHealthListener;
std::condition_variable mStatusCondition;
int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
@@ -291,9 +304,13 @@
DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
content::pm::DataLoaderParamsParcel&& params,
- const DataLoaderStatusListener* externalListener = nullptr);
+ const DataLoaderStatusListener* statusListener = nullptr,
+ StorageHealthCheckParams&& healthCheckParams = {},
+ const StorageHealthListener* healthListener = nullptr);
void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
- const DataLoaderStatusListener* externalListener = nullptr);
+ const DataLoaderStatusListener* statusListener = nullptr,
+ StorageHealthCheckParams&& healthCheckParams = {},
+ const StorageHealthListener* healthListener = nullptr);
BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
StorageId findStorageId(std::string_view path) const;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 08fb486..a76aa62 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -175,6 +175,10 @@
ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
}
+ WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final {
+ return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer);
+ }
};
RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env)
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index abbf2f4..a935ab9 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -69,6 +69,7 @@
using Control = incfs::Control;
using FileId = incfs::FileId;
using ErrorCode = incfs::ErrorCode;
+ using WaitResult = incfs::WaitResult;
using ExistingMountCallback =
std::function<void(std::string_view root, std::string_view backingDir,
@@ -90,6 +91,9 @@
virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
+ virtual WaitResult waitForPendingReads(
+ const Control& control, std::chrono::milliseconds timeout,
+ std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0;
};
class AppOpsManagerWrapper {
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index 2e4625c..2948b6a 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -284,6 +284,9 @@
MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
+ MOCK_CONST_METHOD3(waitForPendingReads,
+ WaitResult(const Control& control, std::chrono::milliseconds timeout,
+ std::vector<incfs::ReadInfo>* pendingReadsBuffer));
MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); }
@@ -292,12 +295,23 @@
void openMountSuccess() {
ON_CALL(*this, openMount(_)).WillByDefault(Invoke(this, &MockIncFs::openMountForHealth));
}
+ void waitForPendingReadsSuccess() {
+ ON_CALL(*this, waitForPendingReads(_, _, _))
+ .WillByDefault(Invoke(this, &MockIncFs::waitForPendingReadsForHealth));
+ }
static constexpr auto kPendingReadsFd = 42;
Control openMountForHealth(std::string_view) {
return UniqueControl(IncFs_CreateControl(-1, kPendingReadsFd, -1));
}
+ WaitResult waitForPendingReadsForHealth(
+ const Control& control, std::chrono::milliseconds timeout,
+ std::vector<incfs::ReadInfo>* pendingReadsBuffer) const {
+ pendingReadsBuffer->push_back({.bootClockTsUs = 0});
+ return android::incfs::WaitResult::HaveData;
+ }
+
RawMetadata getMountInfoMetadata(const Control& control, std::string_view path) {
metadata::Mount m;
m.mutable_storage()->set_id(100);
@@ -499,9 +513,9 @@
mVold->mountIncFsFails();
EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -510,9 +524,9 @@
EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0);
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -523,9 +537,9 @@
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -537,9 +551,9 @@
EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_));
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -555,9 +569,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(0);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_LT(storageId, 0);
}
@@ -574,9 +588,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
mIncrementalService->deleteStorage(storageId);
}
@@ -594,9 +608,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
// Simulated crash/other connection breakage.
mDataLoaderManager->setDataLoaderStatusDestroyed();
@@ -616,9 +630,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusCreated();
ASSERT_TRUE(mIncrementalService->startLoading(storageId));
@@ -639,9 +653,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_TRUE(mIncrementalService->startLoading(storageId));
mDataLoaderManager->setDataLoaderStatusCreated();
@@ -661,9 +675,9 @@
EXPECT_CALL(*mDataLoader, destroy(_)).Times(1);
EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusUnavailable();
}
@@ -672,6 +686,7 @@
mVold->mountIncFsSuccess();
mIncFs->makeFileSuccess();
mIncFs->openMountSuccess();
+ mIncFs->waitForPendingReadsSuccess();
mVold->bindMountSuccess();
mDataLoader->initializeCreateOkNoStatus();
mDataLoaderManager->bindToDataLoaderSuccess();
@@ -685,9 +700,9 @@
EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1);
EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
mDataLoaderManager->setDataLoaderStatusUnavailable();
ASSERT_NE(nullptr, mLooper->mCallback);
@@ -712,9 +727,9 @@
// Not expecting callback removal.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
}
@@ -739,9 +754,9 @@
// After callback is called, disable read logs and remove callback.
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(1);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_GE(mDataLoader->setStorageParams(true), 0);
ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get());
@@ -762,9 +777,9 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_LT(mDataLoader->setStorageParams(true), 0);
}
@@ -785,9 +800,9 @@
EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0);
EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0);
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
ASSERT_GE(storageId, 0);
ASSERT_LT(mDataLoader->setStorageParams(true), 0);
}
@@ -799,9 +814,9 @@
mDataLoaderManager->bindToDataLoaderSuccess();
mDataLoaderManager->getDataLoaderSuccess();
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
std::string dir_path("test");
// Expecting incfs to call makeDir on a path like:
@@ -823,9 +838,9 @@
mDataLoaderManager->bindToDataLoaderSuccess();
mDataLoaderManager->getDataLoaderSuccess();
TemporaryDir tempDir;
- int storageId =
- mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
- IncrementalService::CreateOptions::CreateNew);
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
auto first = "first"sv;
auto second = "second"sv;
auto third = "third"sv;
diff --git a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
index d338b58..ade01dc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java
@@ -19,7 +19,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
+import static com.android.server.blob.BlobStoreConfig.DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -93,6 +93,7 @@
doReturn(true).when(mBlobsDir).exists();
doReturn(new File[0]).when(mBlobsDir).listFiles();
doReturn(true).when(() -> BlobStoreConfig.hasLeaseWaitTimeElapsed(anyLong()));
+ doCallRealMethod().when(() -> BlobStoreConfig.hasSessionExpired(anyLong()));
mContext = InstrumentationRegistry.getTargetContext();
mHandler = new TestHandler(Looper.getMainLooper());
@@ -236,7 +237,7 @@
public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception {
// Setup sessions
final File sessionFile1 = mock(File.class);
- doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000)
+ doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS + 1000)
.when(sessionFile1).lastModified();
final long sessionId1 = 342;
final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
@@ -256,7 +257,7 @@
mUserSessions.append(sessionId2, session2);
final File sessionFile3 = mock(File.class);
- doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000)
+ doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS - 2000)
.when(sessionFile3).lastModified();
final long sessionId3 = 9484;
final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index f35eecf..b7692f9 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -44,9 +44,9 @@
@Test
public void testUpdateOverlaysForUser() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- installTargetPackage("some.other.target", USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
+ addSystemPackage(target(TARGET), USER);
+ addSystemPackage(target("some.other.target"), USER);;
+ addSystemPackage(overlay(OVERLAY, TARGET), USER);
// do nothing, expect no change
final List<String> a = impl.updateOverlaysForUser(USER);
@@ -54,8 +54,7 @@
assertTrue(a.contains(TARGET));
// upgrade overlay, keep target
- beginUpgradeOverlayPackage(OVERLAY, USER);
- endUpgradeOverlayPackage(OVERLAY, TARGET, USER);
+ addSystemPackage(overlay(OVERLAY, TARGET), USER);
final List<String> b = impl.updateOverlaysForUser(USER);
assertEquals(1, b.size());
@@ -67,7 +66,7 @@
assertTrue(c.contains(TARGET));
// upgrade overlay, switch to new target
- addOverlayPackage(OVERLAY, "some.other.target", USER, true, false, 0);
+ addSystemPackage(overlay(OVERLAY, "some.other.target"), USER);
final List<String> d = impl.updateOverlaysForUser(USER);
assertEquals(2, d.size());
assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target")));
@@ -81,23 +80,24 @@
@Test
public void testImmutableEnabledChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
- addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertFalse(o1.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertTrue(o2.isEnabled());
assertFalse(o2.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -108,23 +108,24 @@
@Test
public void testMutableEnabledChangeHasNoEffect() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
- addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertFalse(o1.isEnabled());
assertTrue(o1.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o2);
assertFalse(o2.isEnabled());
assertTrue(o2.isMutable);
- addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
@@ -135,15 +136,16 @@
@Test
public void testMutableEnabledToImmutableEnabled() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> {
- addOverlayPackage(OVERLAY, TARGET, USER, mutable, enabled, 0);
+ configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */);
impl.updateOverlaysForUser(USER);
- final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
- assertNotNull(o1);
- assertEquals(enabled, o1.isEnabled());
- assertEquals(mutable, o1.isMutable);
+ final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER);
+ assertNotNull(o);
+ assertEquals(enabled, o.isEnabled());
+ assertEquals(mutable, o.isMutable);
};
// Immutable/enabled -> mutable/enabled
@@ -178,70 +180,76 @@
@Test
public void testMutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0);
- addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 1);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */);
+ configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
+ assertFalse(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
+ assertFalse(o2.isEnabled());
// Overlay priority changing between reboots should not affect enable state of mutable
- // overlays
+ // overlays.
impl.setEnabled(OVERLAY, true, USER);
// Reorder the overlays
- addOverlayPackage(OVERLAY, TARGET, USER, true, true, 1);
- addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 0);
+ configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */);
+ configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
+ assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
- assertTrue(o1.isEnabled());
+ assertFalse(o4.isEnabled());
}
@Test
public void testImmutablePriorityChange() {
final OverlayManagerServiceImpl impl = getImpl();
- installTargetPackage(TARGET, USER);
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0);
- addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 1);
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */);
+ configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o1);
assertEquals(0, o1.priority);
+ assertTrue(o1.isEnabled());
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o2);
assertEquals(1, o2.priority);
-
- // Overlay priority changing between reboots should not affect enable state of mutable
- // overlays
- impl.setEnabled(OVERLAY, true, USER);
+ assertTrue(o2.isEnabled());
// Reorder the overlays
- addOverlayPackage(OVERLAY, TARGET, USER, false, true, 1);
- addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 0);
+ configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 1 /* priority */);
+ configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */);
impl.updateOverlaysForUser(USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER);
assertNotNull(o3);
assertEquals(1, o3.priority);
+ assertTrue(o3.isEnabled());
final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER);
assertNotNull(o4);
assertEquals(0, o4.priority);
- assertTrue(o1.isEnabled());
+ assertTrue(o4.isEnabled());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index cd73432..b25af05 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertTrue;
import android.content.om.OverlayInfo;
+import android.os.OverlayablePolicy;
import androidx.test.runner.AndroidJUnit4;
@@ -49,11 +50,9 @@
private static final String OVERLAY3 = OVERLAY + "3";
private static final int USER3 = USER2 + 1;
- // tests: basics
-
@Test
- public void testGetOverlayInfo() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
+ public void testGetOverlayInfo() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER);
@@ -64,10 +63,10 @@
}
@Test
- public void testGetOverlayInfosForTarget() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET, USER2);
+ public void testGetOverlayInfosForTarget() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET), USER2);
final OverlayManagerServiceImpl impl = getImpl();
final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -89,11 +88,11 @@
}
@Test
- public void testGetOverlayInfosForUser() throws Exception {
- installTargetPackage(TARGET, USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET2, USER);
+ public void testGetOverlayInfosForUser() {
+ installNewPackage(target(TARGET), USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET2), USER);
final OverlayManagerServiceImpl impl = getImpl();
final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -116,91 +115,86 @@
}
@Test
- public void testPriority() throws Exception {
- installOverlayPackage(OVERLAY, TARGET, USER);
- installOverlayPackage(OVERLAY2, TARGET, USER);
- installOverlayPackage(OVERLAY3, TARGET, USER);
+ public void testPriority() {
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
+ installNewPackage(overlay(OVERLAY2, TARGET), USER);
+ installNewPackage(overlay(OVERLAY3, TARGET), USER);
final OverlayManagerServiceImpl impl = getImpl();
final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER);
final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER);
final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER);
- assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setLowestPriority(OVERLAY3, USER));
- assertOverlayInfoList(TARGET, USER, o3, o1, o2);
+ assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
assertTrue(impl.setHighestPriority(OVERLAY3, USER));
- assertOverlayInfoList(TARGET, USER, o1, o2, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER));
- assertOverlayInfoList(TARGET, USER, o2, o1, o3);
+ assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
}
@Test
- public void testOverlayInfoStateTransitions() throws Exception {
+ public void testOverlayInfoStateTransitions() {
final OverlayManagerServiceImpl impl = getImpl();
assertNull(impl.getOverlayInfo(OVERLAY, USER));
- installOverlayPackage(OVERLAY, TARGET, USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
- installTargetPackage(TARGET, USER);
+ final DummyDeviceState.PackageBuilder target = target(TARGET);
+ installNewPackage(target, USER);
assertState(STATE_DISABLED, OVERLAY, USER);
impl.setEnabled(OVERLAY, true, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
// target upgrades do not change the state of the overlay
- beginUpgradeTargetPackage(TARGET, USER);
+ upgradePackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
- endUpgradeTargetPackage(TARGET, USER);
- assertState(STATE_ENABLED, OVERLAY, USER);
-
- uninstallTargetPackage(TARGET, USER);
+ uninstallPackage(TARGET, USER);
assertState(STATE_MISSING_TARGET, OVERLAY, USER);
- installTargetPackage(TARGET, USER);
+ installNewPackage(target, USER);
assertState(STATE_ENABLED, OVERLAY, USER);
}
@Test
- public void testOnOverlayPackageUpgraded() throws Exception {
- final OverlayManagerServiceImpl impl = getImpl();
+ public void testOnOverlayPackageUpgraded() {
final DummyListener listener = getListener();
- installTargetPackage(TARGET, USER);
- installOverlayPackage(OVERLAY, TARGET, USER);
- impl.onOverlayPackageReplacing(OVERLAY, USER);
+ final DummyDeviceState.PackageBuilder target = target(TARGET);
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
+ installNewPackage(target, USER);
+ installNewPackage(overlay, USER);
listener.count = 0;
- impl.onOverlayPackageReplaced(OVERLAY, USER);
- assertEquals(1, listener.count);
-
- // upgrade to a version where the overlay has changed its target
- beginUpgradeOverlayPackage(OVERLAY, USER);
- listener.count = 0;
- endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
- // expect once for the old target package, once for the new target package
+ upgradePackage(overlay, USER);
assertEquals(2, listener.count);
- beginUpgradeOverlayPackage(OVERLAY, USER);
+ // upgrade to a version where the overlay has changed its target
+ // expect once for the old target package, once for the new target package
listener.count = 0;
- endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER);
- assertEquals(1, listener.count);
+ final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
+ upgradePackage(overlay2, USER);
+ assertEquals(3, listener.count);
+
+ listener.count = 0;
+ upgradePackage(overlay2, USER);
+ assertEquals(2, listener.count);
}
- // tests: listener interface
-
@Test
- public void testListener() throws Exception {
+ public void testListener() {
final OverlayManagerServiceImpl impl = getImpl();
final DummyListener listener = getListener();
- installOverlayPackage(OVERLAY, TARGET, USER);
+ installNewPackage(overlay(OVERLAY, TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
- installTargetPackage(TARGET, USER);
+ installNewPackage(target(TARGET), USER);
assertEquals(1, listener.count);
listener.count = 0;
@@ -211,4 +205,49 @@
impl.setEnabled(OVERLAY, true, USER);
assertEquals(0, listener.count);
}
+
+ @Test
+ public void testConfigurator() {
+ final DummyPackageManagerHelper packageManager = getPackageManager();
+ packageManager.overlayableConfigurator = "actor";
+ packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
+ reinitializeImpl();
+
+ installNewPackage(target("actor").setCertificate("one"), USER);
+ installNewPackage(target(TARGET)
+ .addOverlayable("TestResources")
+ .setCertificate("two"), USER);
+
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
+ .setCertificate("one");
+ installNewPackage(overlay, USER);
+
+ final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
+ final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
+ assertNotNull(idmap);
+ assertEquals(OverlayablePolicy.ACTOR_SIGNATURE,
+ idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
+ }
+
+ @Test
+ public void testConfiguratorDifferentSignatures() {
+ final DummyPackageManagerHelper packageManager = getPackageManager();
+ packageManager.overlayableConfigurator = "actor";
+ packageManager.overlayableConfiguratorTargets = new String[]{TARGET};
+ reinitializeImpl();
+
+ installNewPackage(target("actor").setCertificate("one"), USER);
+ installNewPackage(target(TARGET)
+ .addOverlayable("TestResources")
+ .setCertificate("two"), USER);
+
+ final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources")
+ .setCertificate("two");
+ installNewPackage(overlay, USER);
+
+ final DummyIdmapDaemon idmapDaemon = getIdmapDaemon();
+ final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath);
+ assertNotNull(idmap);
+ assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 9eda718..ec6a481 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -17,6 +17,7 @@
package com.android.server.om;
import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -26,17 +27,19 @@
import android.content.om.OverlayableInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.util.ArrayMap;
import android.util.ArraySet;
import androidx.annotation.Nullable;
import com.android.internal.content.om.OverlayConfig;
+import org.junit.Assert;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -47,29 +50,48 @@
private OverlayManagerServiceImpl mImpl;
private DummyDeviceState mState;
private DummyListener mListener;
+ private DummyPackageManagerHelper mPackageManager;
+ private DummyIdmapDaemon mIdmapDaemon;
+ private OverlayConfig mOverlayConfig;
@Before
public void setUp() {
mState = new DummyDeviceState();
mListener = new DummyListener();
- final DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState);
+ mPackageManager = new DummyPackageManagerHelper(mState);
+ mIdmapDaemon = new DummyIdmapDaemon(mState);
+ mOverlayConfig = mock(OverlayConfig.class);
+ when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY);
+ when(mOverlayConfig.isEnabled(any())).thenReturn(false);
+ when(mOverlayConfig.isMutable(any())).thenReturn(true);
+ reinitializeImpl();
+ }
- mImpl = new OverlayManagerServiceImpl(pmh,
- new DummyIdmapManager(mState, pmh),
+ void reinitializeImpl() {
+ mImpl = new OverlayManagerServiceImpl(mPackageManager,
+ new IdmapManager(mIdmapDaemon, mPackageManager),
new OverlayManagerSettings(),
- mState.mOverlayConfig,
+ mOverlayConfig,
new String[0],
mListener);
}
- public OverlayManagerServiceImpl getImpl() {
+ OverlayManagerServiceImpl getImpl() {
return mImpl;
}
- public DummyListener getListener() {
+ DummyListener getListener() {
return mListener;
}
+ DummyPackageManagerHelper getPackageManager() {
+ return mPackageManager;
+ }
+
+ DummyIdmapDaemon getIdmapDaemon() {
+ return mIdmapDaemon;
+ }
+
void assertState(@State int expected, final String overlayPackageName, int userId) {
final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId);
if (info == null) {
@@ -81,7 +103,7 @@
assertEquals(msg, expected, info.state);
}
- void assertOverlayInfoList(final String targetPackageName, int userId,
+ void assertOverlayInfoForTarget(final String targetPackageName, int userId,
OverlayInfo... overlayInfos) {
final List<OverlayInfo> expected =
mImpl.getOverlayInfosForTarget(targetPackageName, userId);
@@ -89,198 +111,202 @@
assertEquals(expected, actual);
}
- /**
- * Creates an overlay configured through {@link OverlayConfig}.
- *
- * @throws IllegalStateException if the package is already installed
- */
- void addOverlayPackage(String packageName, String targetPackageName, int userId,
- boolean mutable, boolean enabled, int priority) {
- mState.addOverlay(packageName, targetPackageName, userId, mutable, enabled, priority);
+ DummyDeviceState.PackageBuilder target(String packageName) {
+ return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */,
+ null /* targetOverlayableName */);
+ }
+
+ DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) {
+ return overlay(packageName, targetPackageName, null /* targetOverlayableName */);
+ }
+
+ DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName,
+ String targetOverlayableName) {
+ return new DummyDeviceState.PackageBuilder(packageName, targetPackageName,
+ targetOverlayableName);
+ }
+
+ void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ mState.add(pkg, userId);
+ }
+
+ void configureSystemOverlay(String packageName, boolean mutable, boolean enabled,
+ int priority) {
+ when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
+ when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
+ when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
}
/**
- * Adds the target package to the device.
+ * Adds the package to the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
*
- * @throws IllegalStateException if the package is not currently installed
+ * @throws IllegalStateException if the package is currently installed
*/
- void installTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) != null) {
- throw new IllegalStateException("package already installed");
+ void installNewPackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ if (mState.select(pkg.packageName, userId) != null) {
+ throw new IllegalStateException("package " + pkg.packageName + " already installed");
}
- mState.addTarget(packageName, userId);
- mImpl.onTargetPackageAdded(packageName, userId);
+ mState.add(pkg, userId);
+ if (pkg.targetPackage == null) {
+ mImpl.onTargetPackageAdded(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageAdded(pkg.packageName, userId);
+ }
}
/**
- * Begins upgrading the target package.
+ * Begins upgrading the package.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void beginUpgradeTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
- }
- mImpl.onTargetPackageReplacing(packageName, userId);
- }
-
- /**
- * Ends upgrading the target package.
- *
- * This corresponds to when the OMS receives the
+ * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the
* {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
* {@link android.content.Intent#EXTRA_REPLACING} extra.
*
* @throws IllegalStateException if the package is not currently installed
*/
- void endUpgradeTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
+ void upgradePackage(DummyDeviceState.PackageBuilder pkg, int userId) {
+ final DummyDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
+ if (replacedPackage == null) {
+ throw new IllegalStateException("package " + pkg.packageName + " not installed");
}
- mState.addTarget(packageName, userId);
- mImpl.onTargetPackageReplaced(packageName, userId);
+ if (replacedPackage.targetPackageName != null) {
+ mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
+ }
+
+ mState.add(pkg, userId);
+ if (pkg.targetPackage == null) {
+ mImpl.onTargetPackageReplaced(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
+ }
}
/**
- * Removes the target package from the device.
+ * Removes the package from the device.
*
* This corresponds to when the OMS receives the
* {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast.
*
* @throws IllegalStateException if the package is not currently installed
*/
- void uninstallTargetPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed");
+ void uninstallPackage(String packageName, int userId) {
+ final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null) {
+ throw new IllegalStateException("package " + packageName+ " not installed");
}
- mState.remove(packageName, userId);
- mImpl.onTargetPackageRemoved(packageName, userId);
+ mState.remove(pkg.packageName);
+ if (pkg.targetPackageName == null) {
+ mImpl.onTargetPackageRemoved(pkg.packageName, userId);
+ } else {
+ mImpl.onOverlayPackageRemoved(pkg.packageName, userId);
+ }
}
- /**
- * Adds the overlay package to the device.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast.
- *
- * @throws IllegalStateException if the package is already installed
- */
- void installOverlayPackage(String packageName, String targetPackageName, int userId) {
- if (mState.select(packageName, userId) != null) {
- throw new IllegalStateException("package already installed");
- }
- mState.addOverlay(packageName, targetPackageName, userId);
- mImpl.onOverlayPackageAdded(packageName, userId);
- }
+ /** Represents the state of packages installed on a fake device. */
+ static class DummyDeviceState {
+ private ArrayMap<String, Package> mPackages = new ArrayMap<>();
- /**
- * Begins upgrading the overlay package.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void beginUpgradeOverlayPackage(String packageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed, cannot upgrade");
- }
+ void add(PackageBuilder pkgBuilder, int userId) {
+ final Package pkg = pkgBuilder.build();
+ final Package previousPkg = select(pkg.packageName, userId);
+ mPackages.put(pkg.packageName, pkg);
- mImpl.onOverlayPackageReplacing(packageName, userId);
- }
-
- /**
- * Ends upgrading the overlay package, potentially changing its target package.
- *
- * This corresponds to when the OMS receives the
- * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
- * {@link android.content.Intent#EXTRA_REPLACING} extra.
- *
- * @throws IllegalStateException if the package is not currently installed
- */
- void endUpgradeOverlayPackage(String packageName, String targetPackageName, int userId) {
- if (mState.select(packageName, userId) == null) {
- throw new IllegalStateException("package not installed, cannot upgrade");
- }
-
- mState.addOverlay(packageName, targetPackageName, userId);
- mImpl.onOverlayPackageReplaced(packageName, userId);
- }
-
- private static final class DummyDeviceState {
- private List<Package> mPackages = new ArrayList<>();
- private OverlayConfig mOverlayConfig = mock(OverlayConfig.class);
-
- /** Adds a non-overlay to the device. */
- public void addTarget(String packageName, int userId) {
- remove(packageName, userId);
- mPackages.add(new Package(packageName, userId, null, false, false, 0));
- }
-
- /** Adds an overlay to the device. */
- public void addOverlay(String packageName, String targetPackageName, int userId) {
- addOverlay(packageName, targetPackageName, userId, true, false, OverlayConfig.DEFAULT_PRIORITY);
- }
-
- /** Adds a configured overlay to the device. */
- public void addOverlay(String packageName, String targetPackageName, int userId,
- boolean mutable, boolean enabled, int priority) {
- remove(packageName, userId);
- mPackages.add(new Package(packageName, userId, targetPackageName, mutable, enabled,
- priority));
- when(mOverlayConfig.getPriority(packageName)).thenReturn(priority);
- when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled);
- when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable);
- }
-
- /** Remove a package from the device. */
- public void remove(String packageName, int userId) {
- final Iterator<Package> iter = mPackages.iterator();
- while (iter.hasNext()) {
- final Package pkg = iter.next();
- if (pkg.packageName.equals(packageName) && pkg.userId == userId) {
- iter.remove();
- return;
- }
+ pkg.installedUserIds.add(userId);
+ if (previousPkg != null) {
+ pkg.installedUserIds.addAll(previousPkg.installedUserIds);
}
}
- /** Retrieves all packages on device for a particular user. */
- public List<Package> select(int userId) {
- return mPackages.stream().filter(p -> p.userId == userId).collect(Collectors.toList());
+ void remove(String packageName) {
+ mPackages.remove(packageName);
}
- /** Retrieves the package with the specified package name for a particular user. */
- public Package select(String packageName, int userId) {
- return mPackages.stream().filter(
- p -> p.packageName.equals(packageName) && p.userId == userId)
- .findFirst().orElse(null);
+ void uninstall(String packageName, int userId) {
+ final Package pkg = mPackages.get(packageName);
+ if (pkg != null) {
+ pkg.installedUserIds.remove(userId);
+ }
}
- private static final class Package {
- public final String packageName;
- public final int userId;
- public final String targetPackageName;
- public final boolean mutable;
- public final boolean enabled;
- public final int priority;
+ List<Package> select(int userId) {
+ return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId))
+ .collect(Collectors.toList());
+ }
- private Package(String packageName, int userId, String targetPackageName,
- boolean mutable, boolean enabled, int priority) {
+ Package select(String packageName, int userId) {
+ final Package pkg = mPackages.get(packageName);
+ return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null;
+ }
+
+ private Package selectFromPath(String path) {
+ return mPackages.values().stream()
+ .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null);
+ }
+
+ static final class PackageBuilder {
+ private String packageName;
+ private String targetPackage;
+ private String certificate = "[default]";
+ private int version = 0;
+ private ArrayList<String> overlayableNames = new ArrayList<>();
+ private String targetOverlayableName;
+
+ private PackageBuilder(String packageName, String targetPackage,
+ String targetOverlayableName) {
this.packageName = packageName;
- this.userId = userId;
+ this.targetPackage = targetPackage;
+ this.targetOverlayableName = targetOverlayableName;
+ }
+
+ PackageBuilder setCertificate(String certificate) {
+ this.certificate = certificate;
+ return this;
+ }
+
+ PackageBuilder addOverlayable(String overlayableName) {
+ overlayableNames.add(overlayableName);
+ return this;
+ }
+
+ PackageBuilder setVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
+ Package build() {
+ final String apkPath = String.format("%s/%s/base.apk",
+ targetPackage == null ? "/system/app/:" : "/vendor/overlay/:",
+ packageName);
+ final Package newPackage = new Package(packageName, targetPackage,
+ targetOverlayableName, version, apkPath, certificate);
+ newPackage.overlayableNames.addAll(overlayableNames);
+ return newPackage;
+ }
+ }
+
+ static final class Package {
+ final String packageName;
+ final String targetPackageName;
+ final String targetOverlayableName;
+ final int versionCode;
+ final String apkPath;
+ final String certificate;
+ final ArrayList<String> overlayableNames = new ArrayList<>();
+ private final ArraySet<Integer> installedUserIds = new ArraySet<>();
+
+ private Package(String packageName, String targetPackageName,
+ String targetOverlayableName, int versionCode, String apkPath,
+ String certificate) {
+ this.packageName = packageName;
this.targetPackageName = targetPackageName;
- this.mutable = mutable;
- this.enabled = enabled;
- this.priority = priority;
+ this.targetOverlayableName = targetOverlayableName;
+ this.versionCode = versionCode;
+ this.apkPath = apkPath;
+ this.certificate = certificate;
}
}
}
@@ -288,6 +314,8 @@
static final class DummyPackageManagerHelper implements PackageManagerHelper,
OverlayableInfoCallback {
private final DummyDeviceState mState;
+ String[] overlayableConfiguratorTargets = new String[0];
+ String overlayableConfigurator = "";
private DummyPackageManagerHelper(DummyDeviceState state) {
mState = state;
@@ -300,13 +328,12 @@
return null;
}
final ApplicationInfo ai = new ApplicationInfo();
- ai.sourceDir = String.format("%s/%s/base.apk",
- pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/",
- pkg.packageName);
+ ai.sourceDir = pkg.apkPath;
PackageInfo pi = new PackageInfo();
pi.applicationInfo = ai;
pi.packageName = pkg.packageName;
pi.overlayTarget = pkg.targetPackageName;
+ pi.targetOverlayableName = pkg.targetOverlayableName;
pi.overlayCategory = "dummy-category-" + pkg.targetPackageName;
return pi;
}
@@ -314,14 +341,16 @@
@Override
public boolean signaturesMatching(@NonNull String packageName1,
@NonNull String packageName2, int userId) {
- return false;
+ final DummyDeviceState.Package pkg1 = mState.select(packageName1, userId);
+ final DummyDeviceState.Package pkg2 = mState.select(packageName2, userId);
+ return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate);
}
@Override
public List<PackageInfo> getOverlayPackages(int userId) {
return mState.select(userId).stream()
.filter(p -> p.targetPackageName != null)
- .map(p -> getPackageInfo(p.packageName, p.userId))
+ .map(p -> getPackageInfo(p.packageName, userId))
.collect(Collectors.toList());
}
@@ -329,7 +358,11 @@
@Override
public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
@NonNull String targetOverlayableName, int userId) {
- throw new UnsupportedOperationException();
+ final DummyDeviceState.Package pkg = mState.select(packageName, userId);
+ if (pkg == null || !pkg.overlayableNames.contains(targetOverlayableName)) {
+ return null;
+ }
+ return new OverlayableInfo(targetOverlayableName, null /* actor */);
}
@Nullable
@@ -341,69 +374,98 @@
@NonNull
@Override
public Map<String, Map<String, String>> getNamedActors() {
- throw new UnsupportedOperationException();
+ return Collections.emptyMap();
}
@Override
public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) {
- throw new UnsupportedOperationException();
+ final DummyDeviceState.Package pkg = mState.select(targetPackageName, userId);
+ return pkg != null && pkg.overlayableNames.contains(targetPackageName);
}
@Override
public void enforcePermission(String permission, String message) throws SecurityException {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public String[] getOverlayableConfiguratorTargets() {
+ return overlayableConfiguratorTargets;
+ }
+
+ @Override
+ public String getOverlayableConfigurator() {
+ return overlayableConfigurator;
+ }
}
- static class DummyIdmapManager extends IdmapManager {
+ static class DummyIdmapDaemon extends IdmapDaemon {
private final DummyDeviceState mState;
- private Set<String> mIdmapFiles = new ArraySet<>();
+ private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>();
- private DummyIdmapManager(DummyDeviceState state,
- DummyPackageManagerHelper packageManagerHelper) {
- super(packageManagerHelper);
- mState = state;
+ DummyIdmapDaemon(DummyDeviceState state) {
+ this.mState = state;
+ }
+
+ private int getCrc(@NonNull final String path) {
+ final DummyDeviceState.Package pkg = mState.selectFromPath(path);
+ Assert.assertNotNull(pkg);
+ return pkg.versionCode;
}
@Override
- boolean createIdmap(@NonNull final PackageInfo targetPackage,
- @NonNull final PackageInfo overlayPackage, int userId) {
- final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId);
- if (t == null) {
+ String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
+ int userId) {
+ mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath),
+ getCrc(overlayPath), targetPath, policies, enforce));
+ return overlayPath;
+ }
+
+ @Override
+ boolean removeIdmap(String overlayPath, int userId) {
+ return mIdmapFiles.remove(overlayPath) != null;
+ }
+
+ @Override
+ boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce,
+ int userId) {
+ final IdmapHeader idmap = mIdmapFiles.get(overlayPath);
+ if (idmap == null) {
return false;
}
- final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId);
- if (o == null) {
- return false;
+ return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath);
+ }
+
+ @Override
+ boolean idmapExists(String overlayPath, int userId) {
+ return mIdmapFiles.containsKey(overlayPath);
+ }
+
+ IdmapHeader getIdmap(String overlayPath) {
+ return mIdmapFiles.get(overlayPath);
+ }
+
+ static class IdmapHeader {
+ private final int targetCrc;
+ private final int overlayCrc;
+ final String targetPath;
+ final int policies;
+ final boolean enforceOverlayable;
+
+ private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies,
+ boolean enforceOverlayable) {
+ this.targetCrc = targetCrc;
+ this.overlayCrc = overlayCrc;
+ this.targetPath = targetPath;
+ this.policies = policies;
+ this.enforceOverlayable = enforceOverlayable;
}
- final String key = createKey(overlayPackage.packageName, userId);
- return mIdmapFiles.add(key);
- }
- @Override
- boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) {
- final String key = createKey(oi.packageName, oi.userId);
- if (!mIdmapFiles.contains(key)) {
- return false;
+ private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc,
+ String expectedTargetPath) {
+ return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc
+ && expectedTargetPath.equals(targetPath);
}
- mIdmapFiles.remove(key);
- return true;
- }
-
- @Override
- boolean idmapExists(@NonNull final OverlayInfo oi) {
- final String key = createKey(oi.packageName, oi.userId);
- return mIdmapFiles.contains(key);
- }
-
- @Override
- boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) {
- final String key = createKey(overlayPackage.packageName, userId);
- return mIdmapFiles.contains(key);
- }
-
- private String createKey(@NonNull final String packageName, final int userId) {
- return String.format("%s:%d", packageName, userId);
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
index 38b71b7..13457f0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java
@@ -26,6 +26,8 @@
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -166,6 +168,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
ALLOW_BUBBLE_OFF /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -178,6 +182,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
DEFAULT_ALLOW_BUBBLE /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -190,6 +196,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
ALLOW_BUBBLE_ON /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -202,6 +210,8 @@
setUpBubblesEnabled(false /* feature */,
BUBBLE_PREFERENCE_ALL /* app */,
ALLOW_BUBBLE_ON /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -215,6 +225,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_NONE /* app */,
ALLOW_BUBBLE_ON /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -228,6 +240,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_NONE /* app */,
DEFAULT_ALLOW_BUBBLE /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -241,6 +255,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
DEFAULT_ALLOW_BUBBLE /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -254,6 +270,8 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
ALLOW_BUBBLE_OFF /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -267,6 +285,9 @@
setUpBubblesEnabled(true /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
ALLOW_BUBBLE_ON /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
+
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -279,6 +300,9 @@
setUpBubblesEnabled(false /* feature */,
BUBBLE_PREFERENCE_SELECTED /* app */,
ALLOW_BUBBLE_ON /* channel */);
+ when(mActivityManager.isLowRamDevice()).thenReturn(false);
+ setUpShortcutBubble(true /* isValid */);
+
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
@@ -305,6 +329,7 @@
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
+ assertNotNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -320,6 +345,7 @@
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
+ assertNotNull(r.getNotification().getBubbleMetadata());
assertTrue(r.getNotification().isBubbleNotification());
}
@@ -335,6 +361,7 @@
mBubbleExtractor.process(r);
assertTrue(r.canBubble());
+ assertNotNull(r.getNotification().getBubbleMetadata());
assertTrue(r.getNotification().isBubbleNotification());
}
@@ -350,7 +377,8 @@
r.setShortcutInfo(null);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -366,7 +394,8 @@
r.setShortcutInfo(null);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -381,7 +410,8 @@
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -395,7 +425,8 @@
NotificationRecord r = getNotificationRecord(false /* bubble */);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -414,7 +445,8 @@
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -429,7 +461,8 @@
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -445,7 +478,8 @@
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
@@ -462,7 +496,8 @@
NotificationRecord r = getNotificationRecord(true /* bubble */);
mBubbleExtractor.process(r);
- assertTrue(r.canBubble());
+ assertFalse(r.canBubble());
+ assertNull(r.getNotification().getBubbleMetadata());
assertFalse(r.getNotification().isBubbleNotification());
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ced7804..2bea491 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6135,8 +6135,6 @@
"tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0);
NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
-
-
// Test: Send the bubble notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
@@ -6168,12 +6166,12 @@
verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue());
// We're no longer a bubble
- Notification notif2 = mService.getNotificationRecord(
- nr.getSbn().getKey()).getNotification();
- assertFalse(notif2.isBubbleNotification());
+ NotificationRecord notif2 = mService.getNotificationRecord(
+ nr.getSbn().getKey());
+ assertNull(notif2.getShortcutInfo());
+ assertFalse(notif2.getNotification().isBubbleNotification());
}
-
@Test
public void testNotificationBubbles_shortcut_stopListeningWhenNotifRemoved()
throws RemoteException {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index eb2d9fe..c700a09 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -48,6 +48,7 @@
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
@SmallTest
@@ -73,6 +74,8 @@
StatusBarNotification mSbn;
@Mock
Notification.BubbleMetadata mBubbleMetadata;
+ @Mock
+ ShortcutInfo mShortcutInfo;
ShortcutHelper mShortcutHelper;
@@ -86,13 +89,13 @@
when(mNr.getSbn()).thenReturn(mSbn);
when(mSbn.getPackageName()).thenReturn(PKG);
when(mNr.getNotification()).thenReturn(mNotif);
+ when(mNr.getShortcutInfo()).thenReturn(mShortcutInfo);
+ when(mShortcutInfo.getId()).thenReturn(SHORTCUT_ID);
when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata);
when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID);
}
private LauncherApps.Callback addShortcutBubbleAndVerifyListener() {
- when(mNotif.isBubbleNotification()).thenReturn(true);
-
mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
false /* removed */,
null /* handler */);
@@ -124,12 +127,12 @@
}
@Test
- public void testBubbleNoLongerBubble_listenerRemoved() {
+ public void testBubbleNoLongerHasBubbleMetadata_listenerRemoved() {
// First set it up to listen
addShortcutBubbleAndVerifyListener();
// Then make it not a bubble
- when(mNotif.isBubbleNotification()).thenReturn(false);
+ when(mNotif.getBubbleMetadata()).thenReturn(null);
mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
false /* removed */,
null /* handler */);
@@ -138,6 +141,45 @@
}
@Test
+ public void testBubbleNoLongerHasShortcutId_listenerRemoved() {
+ // First set it up to listen
+ addShortcutBubbleAndVerifyListener();
+
+ // Clear out shortcutId
+ when(mBubbleMetadata.getShortcutId()).thenReturn(null);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+ false /* removed */,
+ null /* handler */);
+
+ verify(mLauncherApps, times(1)).unregisterCallback(any());
+ }
+
+ @Test
+ public void testNotifNoLongerHasShortcut_listenerRemoved() {
+ // First set it up to listen
+ addShortcutBubbleAndVerifyListener();
+
+ // Clear out shortcutId
+ when(mNr.getShortcutInfo()).thenReturn(null);
+ mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr,
+ false /* removed */,
+ null /* handler */);
+
+ verify(mLauncherApps, times(1)).unregisterCallback(any());
+ }
+
+ @Test
+ public void testOnShortcutsChanged_listenerRemoved() {
+ // First set it up to listen
+ LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
+
+ // App shortcuts are removed:
+ callback.onShortcutsChanged(PKG, Collections.emptyList(), mock(UserHandle.class));
+
+ verify(mLauncherApps, times(1)).unregisterCallback(any());
+ }
+
+ @Test
public void testListenerNotifiedOnShortcutRemoved() {
LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener();
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 4e82ceb..d063f10 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -1060,6 +1061,11 @@
@Test
public void testApplyTopFixedRotationTransform() {
mWm.mIsFixedRotationTransformEnabled = true;
+ final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
+ // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation.
+ doReturn(false).when(displayPolicy).navigationBarCanMove();
+ displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs);
+ displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
final Configuration config90 = new Configuration();
mDisplayContent.computeScreenConfiguration(config90, ROTATION_90);
@@ -1080,6 +1086,12 @@
ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */,
false /* forceUpdate */));
+ assertNotNull(mDisplayContent.getFixedRotationAnimationController());
+ assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+ ANIMATION_TYPE_FIXED_TRANSFORM));
+ assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS,
+ ANIMATION_TYPE_FIXED_TRANSFORM));
+
final Rect outFrame = new Rect();
final Rect outInsets = new Rect();
final Rect outStableInsets = new Rect();
@@ -1132,6 +1144,7 @@
assertFalse(app.hasFixedRotationTransform());
assertFalse(app2.hasFixedRotationTransform());
assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation);
+ assertNull(mDisplayContent.getFixedRotationAnimationController());
}
@Test
@@ -1310,7 +1323,7 @@
}
private static int getRotatedOrientation(DisplayContent dc) {
- return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE
+ return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight
? SCREEN_ORIENTATION_PORTRAIT
: SCREEN_ORIENTATION_LANDSCAPE;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 8ce5daa..e9ed20b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -603,6 +603,29 @@
}
@Test
+ public void testRequestResizeForBlastSync() {
+ final WindowState win = mChildAppWindowAbove;
+ makeWindowVisible(win, win.getParentWindow());
+ win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
+ win.reportResized();
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).doesNotContain(win);
+
+ // Check that the window is in resizing if using blast sync.
+ win.reportResized();
+ win.prepareForSync(mock(BLASTSyncEngine.TransactionReadyListener.class), 1);
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).contains(win);
+
+ // Don't re-add the window again if it's been reported to the client and still waiting on
+ // the client draw for blast sync.
+ win.reportResized();
+ mWm.mResizingWindows.remove(win);
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).doesNotContain(win);
+ }
+
+ @Test
public void testGetTransformationMatrix() {
final int PARENT_WINDOW_OFFSET = 1;
final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f5ed64e..fadebaa 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12716,7 +12716,6 @@
@Nullable String mvnoMatchData) {
try {
if (!mccmnc.equals(getSimOperator())) {
- Log.d(TAG, "The mccmnc does not match");
return false;
}
ITelephony service = getITelephony();
diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
index 7d750b7..1a58f17 100644
--- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
+++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java
@@ -115,7 +115,7 @@
private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 3000; // 3s between launching apps
private static final int PROFILE_SAVE_SLEEP_TIMEOUT = 1000; // Allow 1s for the profile to save
private static final int IORAP_TRACE_DURATION_TIMEOUT = 7000; // Allow 7s for trace to complete.
- private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 3; // min 3 launches to merge traces.
+ private static final int IORAP_TRIAL_LAUNCH_ITERATIONS = 5; // min 5 launches to merge traces.
private static final int IORAP_COMPILE_CMD_TIMEOUT = 60; // in seconds: 1 minutes
private static final int IORAP_COMPILE_MIN_TRACES = 1; // configure iorapd to need 1 trace.
private static final int IORAP_COMPILE_RETRIES = 3; // retry compiler 3 times if it fails.
diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp
index 829c883..5c7c68b 100644
--- a/tests/BlobStoreTestUtils/Android.bp
+++ b/tests/BlobStoreTestUtils/Android.bp
@@ -15,6 +15,9 @@
java_library {
name: "BlobStoreTestUtils",
srcs: ["src/**/*.java"],
- static_libs: ["truth-prebuilt"],
+ static_libs: [
+ "truth-prebuilt",
+ "androidx.test.uiautomator_uiautomator",
+ ],
sdk_version: "test_current",
}
\ No newline at end of file
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index 6927e86..7cf58e1 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -18,12 +18,16 @@
import static com.google.common.truth.Truth.assertThat;
+import android.app.Instrumentation;
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
import android.app.blob.LeaseInfo;
import android.content.Context;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import androidx.test.uiautomator.UiDevice;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -32,6 +36,8 @@
import java.io.OutputStream;
public class Utils {
+ public static final String TAG = "BlobStoreTest";
+
public static final int BUFFER_SIZE_BYTES = 16 * 1024;
public static final long KB_IN_BYTES = 1000;
@@ -68,7 +74,8 @@
public static void assertLeasedBlobs(BlobStoreManager blobStoreManager,
BlobHandle... expectedBlobHandles) throws IOException {
- assertThat(blobStoreManager.getLeasedBlobs()).containsExactly(expectedBlobHandles);
+ assertThat(blobStoreManager.getLeasedBlobs()).containsExactly(
+ (Object[]) expectedBlobHandles);
}
public static void assertNoLeasedBlobs(BlobStoreManager blobStoreManager)
@@ -141,4 +148,16 @@
assertThat(leaseInfo.getDescriptionResId()).isEqualTo(descriptionResId);
assertThat(leaseInfo.getDescription()).isEqualTo(description);
}
+
+ public static void triggerIdleMaintenance(Instrumentation instrumentation) throws IOException {
+ runShellCmd(instrumentation, "cmd blob_store idle-maintenance");
+ }
+
+ private static String runShellCmd(Instrumentation instrumentation,
+ String cmd) throws IOException {
+ final UiDevice uiDevice = UiDevice.getInstance(instrumentation);
+ final String result = uiDevice.executeShellCommand(cmd);
+ Log.i(TAG, "Output of '" + cmd + "': '" + result + "'");
+ return result;
+ }
}