diff options
299 files changed, 5017 insertions, 3346 deletions
diff --git a/Android.bp b/Android.bp index ee381a42d728..01a43b6a8de7 100644 --- a/Android.bp +++ b/Android.bp @@ -930,6 +930,8 @@ filegroup { 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 90df19a9491a..a81342a5160f 100644 --- a/ApiDocs.bp +++ b/ApiDocs.bp @@ -66,7 +66,7 @@ stubs_defaults { ":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 6e6efe50309a..c0197c4b2acf 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -69,7 +69,7 @@ stubs_defaults { 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 b4a7cd4de2ae..9a711d31b6a9 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 @@ package com.android.server.blob; 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 @@ class BlobStoreConfig { * 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 @@ class BlobStoreConfig { } 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 @@ class BlobStoreConfig { 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 @@ class BlobStoreConfig { } /** + * 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 @@ class BlobStoreConfig { 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 460e776b9ff6..4b0f719b13be 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 @@ public class BlobStoreIdleJobService extends JobService { 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 9376198b8eaf..7a27b2c795fb 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_CURRENT; 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 @@ public class BlobStoreManagerService extends SystemService { 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 fdb078e00d92..000000000000 --- 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 a6e55228596b..000000000000 --- 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 3dc1b9fa5dd0..000000000000 --- 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 Binary files differdeleted file mode 100644 index 8f47741ed3b8..000000000000 --- a/apex/sdkextensions/com.android.sdkext.avbpubkey +++ /dev/null diff --git a/apex/sdkextensions/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem deleted file mode 100644 index 816460183aa3..000000000000 --- 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 Binary files differdeleted file mode 100644 index ccc0bf438cd1..000000000000 --- a/apex/sdkextensions/com.android.sdkext.pk8 +++ /dev/null diff --git a/apex/sdkextensions/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem deleted file mode 100644 index 45d2ade354d4..000000000000 --- 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 41eae09b1351..000000000000 --- 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 900193a6bd0d..000000000000 --- 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 18f021ccadff..000000000000 --- 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 d15b93552ff4..000000000000 --- 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 b8aad7d8204f..000000000000 --- 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 d802177e249b..000000000000 --- 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 d802177e249b..000000000000 --- 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 d802177e249b..000000000000 --- 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 d802177e249b..000000000000 --- 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 bbff4c5ae6f4..000000000000 --- 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 d802177e249b..000000000000 --- 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 6c25f2849cea..000000000000 --- 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 34c1697c01fd..000000000000 --- 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 5af478ba7fe6..000000000000 --- 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 deeb29e155c0..000000000000 --- 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 d15b93552ff4..000000000000 --- 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 f2f5b321fafe..000000000000 --- 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 1b4a2b0c6e60..000000000000 --- 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 5dc7bdb9a940..a163dea35755 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1728,6 +1728,15 @@ package android.media.audiopolicy { } +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 f30ed17c392f..3dbe41395024 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -292,6 +292,7 @@ cc_test { 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 @@ cc_test { multilib: { lib64: { suffix: "64", - } + }, + lib32: { + suffix: "32", + }, }, cflags: [ diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index c6e9e01cc0d7..62e156767cb4 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -6713,11 +6713,11 @@ message NetworkDnsEventReported { * 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 @@ message DataStallEvent { 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 3d0eeb840064..8587e1452543 100644 --- a/cmds/statsd/src/guardrail/StatsdStats.h +++ b/cmds/statsd/src/guardrail/StatsdStats.h @@ -160,7 +160,7 @@ public: 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 e86fdf06e836..673f668cda82 100644 --- a/cmds/statsd/src/metrics/MetricProducer.h +++ b/cmds/statsd/src/metrics/MetricProducer.h @@ -82,7 +82,9 @@ enum BucketDropReason { 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 f03ce4550bc4..dbec24bf3f6c 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -108,7 +108,7 @@ ValueMetricProducer::ValueMetricProducer( 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::onDumpReportLocked(const int64_t dumpTimeNs, 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 @@ void ValueMetricProducer::invalidateCurrentBucket(const int64_t dropTimeNs, 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 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, 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 @@ void ValueMetricProducer::flushCurrentBucketLocked(const int64_t& eventTimeNs, 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 @@ void ValueMetricProducer::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) // 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::initCurrentSlicedBucket(int64_t nextBucketStartTimeNs) 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 751fef2bf2b1..bb4a66164860 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -144,6 +144,10 @@ private: 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 @@ private: // 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 1121392f1db0..6bfa26761b2f 100644 --- a/cmds/statsd/src/stats_log.proto +++ b/cmds/statsd/src/stats_log.proto @@ -212,6 +212,8 @@ message StatsLogReport { 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 2e6043df0e64..72decf2c7bd0 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -278,7 +278,7 @@ message GaugeMetric { 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 @@ message ValueMetric { 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 000000000000..8f9bb1cb6b2a --- /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 474aa2234837..14246cab0d96 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -1115,13 +1115,21 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) { 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 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit) { 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 @@ TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketI 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 @@ TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenMultipleBucketsSki // 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(ValueMetricProducerTest_BucketDrop, TestBucketDropWhenBucketTooSmall) { } /* + * 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 8e43ca3c6739..cffa59c06a53 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -604,11 +604,8 @@ public final class ActivityThread extends ClientTransactionHandler { 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 @@ public final class ActivityThread extends ClientTransactionHandler { // 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 @@ public final class ActivityThread extends ClientTransactionHandler { } } 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 @@ public final class ActivityThread extends ClientTransactionHandler { * @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 @@ public final class ActivityThread extends ClientTransactionHandler { * {@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 @@ public final class ActivityThread extends ClientTransactionHandler { * 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 @@ public final class ActivityThread extends ClientTransactionHandler { // 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 @@ public final class ActivityThread extends ClientTransactionHandler { activity.dispatchMovedToDisplay(displayId, configToReport); } - if (shouldChangeConfig) { + if (shouldReportChange) { activity.mCalled = false; activity.onConfigurationChanged(configToReport); if (!activity.mCalled) { @@ -5792,7 +5778,7 @@ public final class ActivityThread extends ClientTransactionHandler { // 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 @@ public final class ActivityThread extends ClientTransactionHandler { } } - @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 @@ public final class ActivityThread extends ClientTransactionHandler { * @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 @@ public final class ActivityThread extends ClientTransactionHandler { + ", 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 2f488cdc3158..c577d0e896b0 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2032,8 +2032,16 @@ public abstract class PackageManager { /** * 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 ebf4cc5c938e..bf105ced65b9 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2216,6 +2216,20 @@ public class UserManager { } }; + // 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 @@ public class UserManager { /** {@hide} */ public void disableIsUserUnlockedCache() { mIsUserUnlockedCache.disableLocal(); + mIsUserUnlockingOrUnlockedCache.disableLocal(); } /** {@hide} */ @@ -2263,11 +2278,7 @@ public class UserManager { @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 25cb0400a38d..4c5757098025 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -19,6 +19,8 @@ package android.os.incremental; 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 @@ interface IIncrementalService { * 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 000000000000..9f93ede5c9fc --- /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 958c7fb4fc8d..863d86ef88c9 100644 --- a/core/java/android/os/incremental/IncrementalFileStorages.java +++ b/core/java/android/os/incremental/IncrementalFileStorages.java @@ -65,7 +65,9 @@ public final class IncrementalFileStorages { 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 @@ public final class IncrementalFileStorages { 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 @@ public final class IncrementalFileStorages { 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 @@ public final class IncrementalFileStorages { 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 916edfae679f..c7f50c951d70 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -110,11 +110,15 @@ public final class IncrementalManager { */ @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 000000000000..6839317a9ad5 --- /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 e0bc764025a5..1b19e1290121 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -14363,6 +14363,21 @@ public final class Settings { * @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 d20ffb3a6ec1..9b5b8824b0e6 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -136,6 +136,7 @@ public class WindowlessWindowManager implements IWindowSession { 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 @@ public class WindowlessWindowManager implements IWindowSession { 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 class WindowlessWindowManager implements IWindowSession { 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 f041ad91b559..8b0dd8ae18ce 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.MeasureSpec; 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 @@ public class ChooserActivity extends ResolverActivity implements @Override public void onListRebuilt(ResolverListAdapter listAdapter) { setupScrollListener(); + maybeSetupGlobalLayoutListener(); ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter; if (chooserListAdapter.getUserHandle() @@ -2858,6 +2860,28 @@ public class ChooserActivity extends ResolverActivity implements }); } + 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 762895b6320f..023197baef12 100644 --- a/core/proto/android/providers/settings/global.proto +++ b/core/proto/android/providers/settings/global.proto @@ -695,6 +695,8 @@ message GlobalSettingsProto { } 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 @@ message GlobalSettingsProto { // 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 a205fcf9520b..2dc3996e3d7b 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 758a4f7baffc..6ce25d45b16c 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 bdb6bcc1f19f..8d73f8a94dae 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -409,6 +409,8 @@ applications that come with the platform <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 4aff3e544efa..b6c6cd0b5c1c 100644 --- a/libs/hwui/jni/android_graphics_Canvas.cpp +++ b/libs/hwui/jni/android_graphics_Canvas.cpp @@ -113,7 +113,7 @@ static void restoreUnclippedLayer(CRITICAL_JNI_PARAMS_COMMA jlong canvasHandle, 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 8a262969614e..9cffceb308c8 100644 --- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp +++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp @@ -102,7 +102,7 @@ static void setAntiAlias(JNIEnv*, jobject, jlong treePtr, jboolean aa) { /** * 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 df5e9cd44ed0..72995efb1c21 100644 --- a/libs/hwui/jni/android_util_PathParser.cpp +++ b/libs/hwui/jni/android_util_PathParser.cpp @@ -39,18 +39,18 @@ static void parseStringForPath(JNIEnv* env, jobject, jlong skPathHandle, jstring } } -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 long createPathDataFromStringPath(JNIEnv* env, jobject, jstring inputStr, } } -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 @@ static void deletePathData(JNIEnv*, jobject, jlong pathDataHandle) { 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 508a46f492dd..1fbb67260895 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -111,4 +111,8 @@ interface ITvInputManager { // 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 e701055c2894..98a01a4cb449 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; 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 @@ public final class TvInputManager { 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 a458b16c551a..8bf688dba10b 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -447,7 +447,7 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { 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 @@ public class Tuner implements AutoCloseable { } /* 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 cc932da4a9f6..f0015b723edb 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 @@ public class Filter implements AutoCloseable { } /** @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 fb8c276ecc8d..7e721406a300 100644 --- a/media/jni/android_media_tv_Tuner.cpp +++ b/media/jni/android_media_tv_Tuner.cpp @@ -2975,7 +2975,7 @@ static jint copyData(JNIEnv *env, std::unique_ptr<MQ>& mq, EventFlag* flag, jbyt 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 @@ static jint copyData(JNIEnv *env, std::unique_ptr<MQ>& mq, EventFlag* flag, jbyt 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 1dd02919a093..c0482e1b3e04 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 1418bf8604bf..2a715d0c3494 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 837252b6d716..ca4e76ee104b 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 a5867638b183..788376494032 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 0e84d517759a..d20ab49a22e6 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 ed0b4853994d..cb321cdc6c4d 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 69ec78eab593..ede4696a96c3 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 @@ package com.android.systemui.car.navigationbar; 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 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; * 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 class AssitantButton extends CarNavigationButton { 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 @@ public class AssitantButton extends CarNavigationButton { 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 000000000000..5c83c025bc20 --- /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 5c6472ecb4ef..4c720abb4c74 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 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks 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 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks @UiBackground Executor uiBgExecutor, IStatusBarService barService, Lazy<KeyguardStateController> keyguardStateControllerLazy, - ButtonSelectionStateController buttonSelectionStateController, Lazy<PhoneStatusBarPolicy> iconPolicyLazy, Lazy<StatusBarIconController> iconControllerLazy ) { @@ -156,7 +154,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks mUiBgExecutor = uiBgExecutor; mBarService = barService; mKeyguardStateControllerLazy = keyguardStateControllerLazy; - mButtonSelectionStateController = buttonSelectionStateController; mIconPolicyLazy = iconPolicyLazy; mIconControllerLazy = iconControllerLazy; @@ -280,10 +277,9 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks * 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 288e5cf13c2e..ca780ae645c9 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 @@ public class CarNavigationBarController { 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 class CarNavigationBarController { 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 @@ public class CarNavigationBarController { 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 @@ public class CarNavigationBarController { 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 5f4ac2dcb141..5e113d6366a1 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.view.View; 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { 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 @@ public class CarNavigationButton extends LinearLayout { } /** + * 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 @@ public class CarNavigationButton extends LinearLayout { }; } - /** * Initializes view-related aspects of the button. */ @@ -256,28 +292,27 @@ public class CarNavigationButton extends LinearLayout { 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 000000000000..25ec2c179c8c --- /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 f0e02164a24b..a8e83d6d7717 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 b0ca8dc4cd34..94edc4b5e245 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 576928cda089..44f834040391 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 000000000000..a57736bb3502 --- /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 911f624d1fb3..e84e42c77245 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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @Mock private ButtonSelectionStateController mButtonSelectionStateController; @Mock + private ButtonRoleHolderController mButtonRoleHolderController; + @Mock private HvacController mHvacController; @Before @@ -66,10 +68,15 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { 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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { } @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 @@ public class CarNavigationBarControllerTest extends SysuiTestCase { @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 04e0a7324adf..0caa86f0eab2 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 @@ public class CarNavigationBarTest extends SysuiTestCase { @Mock private ButtonSelectionStateListener mButtonSelectionStateListener; @Mock + private ButtonRoleHolderController mButtonRoleHolderController; + @Mock private IStatusBarService mBarService; @Mock private KeyguardStateController mKeyguardStateController; @@ -137,8 +139,8 @@ public class CarNavigationBarTest extends SysuiTestCase { 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 11f2fa48783f..54282d39998b 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 @@ public class CarNavigationButtonTest extends SysuiTestCase { } @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 66787c2a8663..35e2295ced33 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 ead0ebfa73bd..25cea4db553c 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 c1f4fd95813f..bd94c4065a1f 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 b5d642a9ae8f..5572994f0d20 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 aecaf704b02f..39ab0783e444 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 092bd42e60ce..78fd0bf3b100 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 d610973d891c..30a9e0ee81ec 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 5c3fe75857dc..c8dd82f626c0 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 27d370714a0d..02ca679fca27 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 a9d9e7025236..785460f0ba2f 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 5ed0f1b2d3ca..50c03745fb6d 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 d4d37353fb82..9fb63ea13798 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 89dbf2581746..dc4f873a7595 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 c2030b162e56..bee2d7995d55 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 bfc183e40a53..b1d07acc3d8a 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 9d869ba30717..e51456af9b14 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 77658243864e..34ff568bb315 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 6a76bebc78ee..fb5e9d760b04 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 010b85b86df3..a6a89dd2578d 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 ad2a56152452..65f1043a7b8d 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 1ceb738914f2..6864db195ce1 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 d3f1c56cd1d7..5e13c1b07256 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 5ab01c4b51b4..1ec71c71abb6 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 26e50421cad8..0a32ca51f769 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 b8fc50dc028b..bb069b55c7dd 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 8dc58ef6f32f..a27f2c4078e0 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 9f69a2c0fa20..cf1e5eedfd33 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 dc8e28bb9df2..89b7f679612e 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 f8878396d867..f57a302e43f3 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 d7d269406f84..e8699dc05115 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 a5ef6b557e4b..3f2b41421ad5 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 04ca2f69cc61..dd1f9994c916 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 ce60eb50031d..5dad63d03ae4 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 bef644b60a3b..02b853384b98 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 ed796326c708..af6a8706ed71 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 3f08a6ac83e2..080c1a9116f5 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 2ebc3ee32c38..23de5b23f7be 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 91e548830ee9..821c5663ffec 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 143f5f90a457..f93ab25930b9 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 1df141ca9d9b..e41877966e19 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 86e165027652..6b273a36e0f0 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 925f4a706f70..d035362b41d8 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 3bf1996591f5..e188f3d6ec7a 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 153c9956dc84..075032cdf261 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 f6e7f35d9211..c37811fc0b44 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 d55f1aff6f96..1f5908c1c7cf 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 c5267d6b5b0a..6ee9777f3887 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 0a01f8480d0c..29647df89f8b 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 075c748f1bb7..99bf1e8fc8e8 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 cdf32a7b39ce..bdecf640e9b1 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 2bd3b450238f..1519380832f9 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 de5bed63b70a..a2f862ea5854 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 bfe295a4066a..159050e2a3df 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 a82271981fca..7e3005b76aea 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 ab9fbc364f43..19e250ba3acc 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 35d8cbab0b36..184f4cd5ec46 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 54ed131797a9..eebea5f3c70b 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 c6dc1d3376b9..142ef478111a 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 999e684f124d..0c6f488dbba5 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 c6dc1d3376b9..142ef478111a 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 ba2f36fc5c1d..036cf830e160 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 8ec387584c5a..6b3975406fbd 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 01c36349bb8f..9074c632bcbd 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 b5cbf435f356..cd3109fc9f6d 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 86cccb859e27..0e492c7d1e42 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 bd3353cfc2d3..4a80bcd10fe8 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 29f23b48d5e0..321645ebcf5e 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 a40100e83f51..cd28c86b986d 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 fedf1c85946c..2bc038e27cb1 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 345f0a7c560d..a28fdce2d653 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 c4fda01c19d2..b7bb6e8f5e37 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 a9032ebf4fd2..2f9ac46d1033 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 712c06bbea74..5064c1efd8cd 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 8818498bbcfd..454ca3b45861 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 f6568e3dbb5e..7e969f7bac4b 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 f13f8fb40847..e3b28c7df66e 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 7b8f627ac0db..04707c53f9e5 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 35cecc1a7b9f..e7fbf467cdc1 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 c69ea2d4b668..bda7d8b05bb4 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 b0324d9a2f46..841de63a8397 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 0574c7ecf626..a6c1647ec9f1 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 b60553a052e4..c43758093537 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 af728887c917..1d4cfdc88ad5 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 @@ public class ApplicationsState { } } + /** + * 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 class ApplicationsState { public long size; public long internalSize; public long externalSize; + public String labelDescription; public boolean mounted; @@ -1616,6 +1631,24 @@ public class ApplicationsState { 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 959c42fe75c9..b83a9c4835e0 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 @@ public class InfoMediaManager extends MediaManager { } /** + * 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 f34903cf7f98..e89f628a2757 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 @@ public class LocalMediaManager implements BluetoothCallback { } /** + * 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 c51467123233..248eb5b96b92 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 @@ public class InfoMediaManagerTest { } @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 db0cb0668a2f..5bb550053a12 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 @@ public class ShadowRouter2Manager { private List<MediaRoute2Info> mAvailableRoutes; private List<MediaRoute2Info> mAllRoutes; + private List<MediaRoute2Info> mDeselectableRoutes; private List<RoutingSessionInfo> mActiveSessions; private List<RoutingSessionInfo> mRoutingSessions; @@ -70,6 +72,15 @@ public class ShadowRouter2Manager { 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 3d7559b2c1a6..aae72e55b549 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 @@ class SettingsProtoDumpUtil { 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 e537b768d1af..fa87b62bd73a 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -391,6 +391,7 @@ public class SettingsBackupTest { 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 b85c7714bf96..a0130f8e9b83 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 055b2beaaa65..985269b2bb75 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 c8cf7f503468..28491d6e7e1f 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -41,6 +41,7 @@ tracyzhou@google.com 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 a520719566ab..63a878f772f9 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 index bf062421ddb4..1a1fddbcfd03 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 09918e764140..d47ad7fcdfbd 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 52ace2bd409b..81020432daa7 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 000000000000..57e6f3635785 --- /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 000000000000..78973f3207d1 --- /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 f61b2b096d3c..000000000000 --- 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 f1b401e77fbc..ecd8b4585609 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 @@ class Bubble implements BubbleViewProvider { private NotificationEntry mEntry; private final String mKey; - private final String mGroupId; private long mLastUpdated; private long mLastAccessed; @@ -69,6 +68,8 @@ class Bubble implements BubbleViewProvider { /** 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { mEntry = e; mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); - mGroupId = groupId(e); mSuppressionListener = listener; } @@ -129,10 +122,6 @@ class Bubble implements BubbleViewProvider { return mEntry; } - public String getGroupId() { - return mGroupId; - } - public String getPackageName() { return mEntry.getSbn().getPackageName(); } @@ -297,20 +286,13 @@ class Bubble implements BubbleViewProvider { } /** - * @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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { 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 2587369cf0f5..5f157c104200 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } + boolean inLandscape() { + return mOrientation == Configuration.ORIENTATION_LANDSCAPE; + } + /** * Set a listener to be notified of bubble expand events. */ @@ -840,25 +845,34 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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); + 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) { - mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); + 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 */); } - } else if (bubble.getEntry().isBubble()){ - mBubbleData.setSelectedBubble(bubble); } + mBubbleData.setExpanded(true); } @@ -878,11 +892,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 65d5bebc625d..35647b0bb2f1 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.DEBUG_BUBBLE_DATA; 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.Collections; 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 @@ public class BubbleData { 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 @@ public class BubbleData { // 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 @@ public class BubbleData { } 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 @@ public class BubbleData { } 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 @@ public class BubbleData { 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 @@ public class BubbleData { } 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 @@ public class BubbleData { } 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 @@ public class BubbleData { 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 class BubbleData { 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 19733a5f895c..d98fee399470 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 @@ public class BubbleDebugConfig { && 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 769740032509..c4b4f4316d93 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 class BubbleExpandedView extends LinearLayout { 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 f4eb580d70ff..af6e66aeb989 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 @@ public class BubbleOverflow implements BubbleViewProvider { } 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 @@ public class BubbleOverflow implements BubbleViewProvider { 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 08ec789bba6a..8fec3385d491 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 @@ public class BubbleOverflowActivity extends Activity { } @Override public boolean canScrollVertically() { + if (mBubbleController.inLandscape()) { + return super.canScrollVertically(); + } return false; } } @@ -89,6 +92,14 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { 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 @@ public class BubbleOverflowActivity extends Activity { @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 c9f362b871e1..418cc505daa8 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.Rect; 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 @@ public class BubbleStackView extends FrameLayout private final SpringAnimation mExpandedViewYAnim; private final BubbleData mBubbleData; - private final Vibrator mVibrator; private final ValueAnimator mDesaturateAndDarkenAnimator; private final Paint mDesaturateAndDarkenPaint = new Paint(); @@ -701,8 +699,6 @@ public class BubbleStackView extends FrameLayout // 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 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow.setUpOverflow(mBubbleContainer, this); } else { mBubbleContainer.removeView(mBubbleOverflow.getBtn()); + mBubbleOverflow.updateDimensions(); mBubbleOverflow.updateIcon(mContext,this); overflowBtnIndex = mBubbleContainer.getChildCount(); } @@ -2333,7 +2330,7 @@ public class BubbleStackView extends FrameLayout 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 23bcb29923d8..8368b2c1ae86 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 @@ public class DependencyProvider { 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 3a8212cd733e..58aad8673172 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.KeyguardManager; 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 @@ public class SystemServicesModule { 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 65729372363a..95a9006c854a 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 android.os.Handler; 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 @@ public class DozeFactory { 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 ac289cb5f822..61c9a967cc4c 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.model.SysUiState; 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, @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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mControlsController = controlsController; mSysUiState = sysUiState; mMainHandler = handler; + mCurrentUserContextTracker = currentUserContextTracker; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -382,12 +385,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 2fe0d9f4711f..000000000000 --- 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 85e1c6b77be4..5f43e43c03c6 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 @@ class KeyguardMediaController @Inject constructor( } }) } + 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 a366725a4398..000000000000 --- 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 c7b93262b181..8e1e1b27cadf 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.ImageView; 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.settingslib.widget.AdaptiveIcon; 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { * @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 @@ public class MediaControlPanel { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } mSeekBarViewModel.onDestroy(); + mMediaViewController.onDestroy(); } private void loadDimens() { @@ -165,6 +164,15 @@ public class MediaControlPanel { } /** + * 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 @@ public class MediaControlPanel { /** 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 @@ public class MediaControlPanel { 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 @@ public class MediaControlPanel { mController.registerCallback(mSessionCallback); - mViewHolder.getBackground().setBackgroundTintList( + mViewHolder.getPlayer().setBackgroundTintList( ColorStateList.valueOf(mBackgroundColor)); // Click action @@ -356,7 +362,6 @@ public class MediaControlPanel { } }); boolean visibleInCompat = actionsWhenCollapsed.contains(i); - updateKeyFrameVisibility(actionId, visibleInCompat); setVisibleAndAlpha(collapsedSet, actionId, visibleInCompat); setVisibleAndAlpha(expandedSet, actionId, true /*visible */); } @@ -374,9 +379,9 @@ public class MediaControlPanel { // 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 @@ public class MediaControlPanel { } /** - * 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 @@ public class MediaControlPanel { } // 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 @@ public class MediaControlPanel { options.setVisibility(View.VISIBLE); return true; // consumed click }); + mMediaViewController.refreshState(); } private void setVisibleAndAlpha(ConstraintSet set, int actionId, boolean visible) { @@ -649,33 +631,4 @@ public class MediaControlPanel { * 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 0b04fd060766..552fea63a278 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 @@ class MediaDeviceManager @Inject constructor( 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 cce1d3efd6e5..775a1649702a 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.AnimatorListenerAdapter 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( * 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( } } + /** + * 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( * * @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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( // 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 @@ class MediaHierarchyManager @Inject constructor( // 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 @@ class MediaHierarchyManager @Inject constructor( @MediaLocation currentLocation: Int, @MediaLocation previousLocation: Int ): Boolean { + if (isCurrentlyInGuidedTransformation()) { + return false + } if (currentLocation == LOCATION_QQS && previousLocation == LOCATION_LOCKSCREEN && (statusBarStateController.leaveOpenOnKeyguardHide() || @@ -247,7 +330,7 @@ class MediaHierarchyManager @Inject constructor( // 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 @@ class MediaHierarchyManager @Inject constructor( // 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( 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 @@ class MediaHierarchyManager @Inject constructor( } } - /** - * 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 @@ class MediaHierarchyManager @Inject constructor( 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 240e44cb8db4..e904e935b0e0 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 @@ class MediaHost @Inject constructor( 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 @@ class MediaHost @Inject constructor( 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 @@ class MediaHost @Inject constructor( /** * 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 @@ class MediaHost @Inject constructor( 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 @@ class MediaHost @Inject constructor( visibleChangedListener?.invoke(visible) } - class MediaHostState @Inject constructor() : MediaState { - var measurementInput: MediaMeasurementInput? = null + class MediaHostStateHolder @Inject constructor() : 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 - override val boundsOnScreen: Rect = Rect() + 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 - override fun copy(): MediaState { - val mediaHostState = MediaHostState() + /** + * 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 } - return result + if (!Objects.equals(measurementInput, other.measurementInput)) { + return false + } + if (expansion != other.expansion) { + return false + } + if (showsOnlyActiveMedia != other.showsOnlyActiveMedia) { + return false + } + return true } - override fun getMeasuringInput(input: MeasurementInput): MediaMeasurementInput { - measurementInput = MediaMeasurementInput(input, expansion) - return measurementInput as MediaMeasurementInput + override fun hashCode(): Int { + var result = measurementInput?.hashCode() ?: 0 + result = 31 * result + expansion.hashCode() + result = 31 * result + showsOnlyActiveMedia.hashCode() + return result } } } -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 + + /** + * Is this host only showing active media or is it showing all of them including resumption? + */ 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 - } + + /** + * 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 000000000000..f90af2a01de0 --- /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 4bbf5eb9f0dc..000000000000 --- 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 000000000000..e82bb402407e --- /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 16302d1632b5..8ab30c75c7eb 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.dagger.qualifiers.Main 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 @@ class MediaViewManager @Inject constructor( @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 @@ class MediaViewManager @Inject constructor( override fun onMediaDataLoaded(key: String, data: MediaData) { updateView(key, data) updatePlayerVisibilities() + mediaCarousel.requiresRemeasuring = true } override fun onMediaDataRemoved(key: String) { @@ -137,6 +173,13 @@ class MediaViewManager @Inject constructor( } } }) + 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 @@ class MediaViewManager @Inject constructor( 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 @@ class MediaViewManager @Inject constructor( 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 @@ class MediaViewManager @Inject constructor( } } 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 @@ class MediaViewManager @Inject constructor( } /** - * 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 + this.desiredLocation = desiredLocation + this.desiredHostState = it + currentlyExpanded = it.expansion > 0 + for (mediaPlayer in mediaPlayers.values) { + if (animate) { + mediaPlayer.mediaViewController.animatePendingStateChange( + duration = duration, + delay = startDelay) } + mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation) } + updateCarouselSize() } } - fun setPlayerWidth(width: Int) { - if (width != playerWidth) { - playerWidth = width - playerWidthPlusPadding = playerWidth + context.resources.getDimensionPixelSize( + /** + * Update the size of the carousel, remeasuring it if necessary. + */ + 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) - for (mediaPlayer in mediaPlayers.values) { - mediaPlayer.setPlayerWidth(width) - } // 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) + newScroll += playerWidthPlusPadding - + (scrollIntoCurrentMedia - playerWidthPlusPadding) } else { newScroll += scrollIntoCurrentMedia } mediaCarousel.scrollX = newScroll - } - } - - /** - * 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 - */ - 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) - } - } - 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) - } + // 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 764dbe6d428f..60c576bd6c34 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.ImageButton 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 @@ class PlayerViewHolder private constructor(itemView: View) { 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 @@ class PlayerViewHolder private constructor(itemView: View) { * @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 b9b8a25c6d31..a10972e8721b 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.Size; 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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { * * @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 @@ public class PipBoundsHandler { 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 @@ public class PipBoundsHandler { mSnapAlgorithm.applySnapFraction(postChangeStackBounds, postChangeMovementBounds, snapFraction); + getInsetBounds(outInsetBounds); outBounds.set(postChangeStackBounds); t.setBounds(pinnedStackInfo.stackToken, outBounds); return true; @@ -425,15 +443,11 @@ public class PipBoundsHandler { * 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 64df2ffee1c6..02bf74577407 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio 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 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio @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 3396f7097ce0..a3185a2ad796 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 class PipMenuActivity extends Activity { 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 @@ public class PipMenuActivity extends Activity { mMenuContainerAnimator.start(); break; } + case MESSAGE_FADE_OUT_MENU: { + fadeOutMenu(); + break; + } } } }; @@ -409,6 +414,18 @@ public class PipMenuActivity extends Activity { } } + /** + * 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 bf2c3e9d0d6e..8b4d932619a9 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 class PipMenuActivityController { */ 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 @@ public class PipMenuActivityController { } } + 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 @@ public class PipMenuActivityController { 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 cc3ab29daed3..4b23e678b15d 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.InputEvent; 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 @@ public class PipResizeGestureHandler { 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 @@ public class PipResizeGestureHandler { mPipTaskOrganizer = pipTaskOrganizer; mMovementBoundsSupplier = movementBoundsSupplier; mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; + context.getDisplay().getRealSize(mMaxSize); reloadResources(); @@ -126,6 +130,7 @@ public class PipResizeGestureHandler { 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 @@ public class PipResizeGestureHandler { 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 @@ public class PipResizeGestureHandler { 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 c408caaccbfb..c274ee96b170 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 @@ public class PipTouchHandler { 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 b877e8745769..8f9e9e2eacd5 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 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca (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 cdde06b0b143..4f0b56e705de 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne } 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 2f06c4b1fed0..75507beba7ae 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 @@ public class QuickQSPanel extends QSPanel { 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 e88ce5a96f87..f0a81e9b0302 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.ContentResolver; 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { } } - 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 3edb33da9cd0..63f323ed2768 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.app.Notification; 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 @@ public class ScreenshotNotificationSmartActionsProvider { * @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 c228fe2c4334..442b373b31be 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.graphics.Bitmap; 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 @@ public class ScreenshotSmartActions { 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 @@ public class ScreenshotSmartActions { 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 @@ public class ScreenshotSmartActions { ? 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 b57b22fed853..8e6398f3630b 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 class CrossFadeHelper { } 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 dea1a07c0268..cb7da4fb9b56 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 @@ public abstract class CustomInterpolatorTransformation 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 27109d2acfa2..9a8cff0f8dc1 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 @@ public class TransformState { if (sameAs(otherState)) { ensureVisible(); } else { - CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); + CrossFadeHelper.fadeIn(mTransformedView, transformationAmount, true /* remap */); } transformViewFullyFrom(otherState, transformationAmount); } @@ -424,7 +424,7 @@ public class TransformState { 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 fefad531377f..71f6dac4e234 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter 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 92b597b01559..f1727ec91c72 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 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView 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 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView 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 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView 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 049cafa4ccde..26ccd721460e 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 @@ public abstract class ExpandableOutlineView extends ExpandableView { 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 @@ public abstract class ExpandableOutlineView extends ExpandableView { 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 0831c0b66797..7ed8350249ec 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 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { 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 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { 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 207144931c3b..bc2adac31d07 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 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout // 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 2d99ab1e999f..14aab9d62dfd 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.Context; 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 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp 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 fa7f282be74a..02e537d2879f 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 @@ public abstract class NotificationViewWrapper implements TransformableView { @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 ecab188a7481..b4220f1da715 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 @@ public class AmbientState { 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 @@ public class AmbientState { * 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 3ac322fec071..383f2a2b0e9f 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 b4f7b59349d2..2c3239a45012 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 android.util.MathUtils; 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 @@ import javax.inject.Singleton; @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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { } 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 @@ public class NotificationRoundnessManager implements OnHeadsUpChangedListener { } 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 bad36bf3de64..1131a65abe93 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 android.view.animation.Interpolator; 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 @@ public class NotificationSection { 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 @@ public class NotificationSection { 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 @@ public class NotificationSection { 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 @@ public class NotificationSection { } } 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 @@ public class NotificationSection { 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 e39a4a0c799f..ba7675f27cf4 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.PeopleHubViewAdapter 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 @@ class NotificationSectionsManager @Inject internal constructor( 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 @@ class NotificationSectionsManager @Inject internal constructor( } 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 @@ class NotificationSectionsManager @Inject internal constructor( */ 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 e33cc6027c4f..bcafd0eeb9a6 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd * @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd // 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } @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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd ? (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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd } 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 64e5f0a8184e..7bcfb466d7d9 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit 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 2be698b4e796..000000000000 --- 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 000000000000..c7edd51e220a --- /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 000000000000..701ff5ecf8a1 --- /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 000000000000..9ee141053861 --- /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 bf94c5d36ff7..5b6444d8feaf 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 @@ package com.android.systemui.util.animation 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 @@ import android.widget.FrameLayout 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 @@ class UniqueObjectHostView( 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 7729965b56c4..7c9ea6bd8e80 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 @@ public abstract class ConcurrencyModule { } /** + * @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 96e868d2a618..9b377ca3ec90 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.hardware.face.FaceManager; 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.shared.system.QuickStepContract; 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 @@ public class BubbleControllerTest extends SysuiTestCase { mRemoveInterceptor.onNotificationRemoveRequested( mRow.getEntry().getKey(), mRow.getEntry(), REASON_APP_CANCEL); - mBubbleController.expandStackAndSelectBubble(key); + mBubbleController.expandStackAndSelectBubble(mRow.getEntry()); assertTrue(mSysUiStateBubblesExpanded); } @@ -727,6 +729,9 @@ public class BubbleControllerTest extends SysuiTestCase { 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 66f119a082a6..eca78ec38a88 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.android.systemui.statusbar.NotificationEntryHelper.modifyRanki 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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,69 +310,60 @@ public class BubbleDataTest extends SysuiTestCase { } /** - * 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 - sendUpdatedEntryAtTime(mEntryA1, 1000); - verifyUpdateReceived(); - assertOrderNotChanged(); - - sendUpdatedEntryAtTime(mEntryB1, 2000); - verifyUpdateReceived(); - assertOrderChangedTo(mBubbleB1, mBubbleA1); - - sendUpdatedEntryAtTime(mEntryB2, 3000); + mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1); + assertOverflowChangedTo(ImmutableList.of(mBubbleA2)); - sendUpdatedEntryAtTime(mEntryA2, 4000); + // Test + mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleA2, mBubbleA1, mBubbleB2, mBubbleB1); + assertOverflowChangedTo(ImmutableList.of()); } + // COLLAPSED / ADD + /** - * 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. + * Verifies that new bubbles insert to the left when collapsed. * <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() + * Placement within the list is based on {@link Bubble#getLastActivity()}, descending + * order (with most recent first). */ @Test - public void test_collapsed_addBubble_sortAndGrouping_withOngoing() { + public void test_collapsed_addBubble() { // Setup mBubbleData.setListener(mListener); // Test - setOngoing(mEntryA1, true); sendUpdatedEntryAtTime(mEntryA1, 1000); verifyUpdateReceived(); assertOrderNotChanged(); sendUpdatedEntryAtTime(mEntryB1, 2000); verifyUpdateReceived(); - assertOrderNotChanged(); + assertOrderChangedTo(mBubbleB1, mBubbleA1); sendUpdatedEntryAtTime(mEntryB2, 3000); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleA1, mBubbleB2, mBubbleB1); + assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1); sendUpdatedEntryAtTime(mEntryA2, 4000); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleA1, mBubbleA2, mBubbleB2, mBubbleB1); + assertOrderChangedTo(mBubbleA2, mBubbleB2, mBubbleB1, mBubbleA1); } /** @@ -378,7 +371,6 @@ public class BubbleDataTest extends SysuiTestCase { * 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { } /** - * 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 @@ public class BubbleDataTest extends SysuiTestCase { // Test sendUpdatedEntryAtTime(mEntryC1, 4000); verifyUpdateReceived(); - assertOrderChangedTo(mBubbleB1, mBubbleC1, 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); + assertOrderChangedTo(mBubbleC1, mBubbleB1, mBubbleA2, mBubbleA1); } /** - * 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 @@ public class BubbleDataTest extends SysuiTestCase { * 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 @@ public class BubbleDataTest extends SysuiTestCase { * 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 @@ public class BubbleDataTest extends SysuiTestCase { 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); + assertSelectionChangedTo(mBubbleB1); - mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE); + mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE); verifyUpdateReceived(); - assertSelectionChangedTo(mBubbleB1); + assertSelectionChangedTo(mBubbleA1); } @Test @@ -838,7 +681,6 @@ public class BubbleDataTest extends SysuiTestCase { * 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 @@ public class BubbleDataTest extends SysuiTestCase { // 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 73b876019863..b18d67bf2726 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.hardware.face.FaceManager; 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.model.SysUiState; 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 8db57cdf5f71..487452b0d26a 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.model.SysUiState; 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin mWalletPlugin; @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; + @Mock private CurrentUserContextTracker mCurrentUserContextTracker; private TestableLooper mTestableLooper; @@ -129,6 +131,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase { allowTestableLooperAsMainThread(); when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData); + when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext); mGlobalActionsDialog = new GlobalActionsDialog(mContext, mWindowManagerFuncs, mAudioManager, @@ -161,7 +164,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase { 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 4d30500bad45..b71a62c9d1a9 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.ImageButton 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 @@ public class MediaControlPanelTest : SysuiTestCase() { @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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 @@ public class MediaControlPanelTest : SysuiTestCase() { 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 7b80a6ea94a0..c0aef8adc4af 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 @@ public class MediaDeviceManagerTest : SysuiTestCase() { 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 @@ public class MediaDeviceManagerTest : SysuiTestCase() { // 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 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.icon).isEqualTo(icon) } @Test @@ -170,6 +174,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.icon).isEqualTo(icon) } @Test @@ -183,6 +188,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.enabled).isTrue() assertThat(data.name).isEqualTo(DEVICE_NAME) + assertThat(data.icon).isEqualTo(icon) } @Test @@ -204,6 +210,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() assertThat(data.name).isNull() + assertThat(data.icon).isNull() } @Test @@ -221,6 +228,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { val data = captureDeviceData(KEY) assertThat(data.enabled).isFalse() assertThat(data.name).isNull() + assertThat(data.icon).isNull() } @Test @@ -238,6 +246,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { 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 000000000000..c9e6f55ff59a --- /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 767852582dc3..d6849bf2aa39 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 @@ class PlayerViewHolderTest : SysuiTestCase() { @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 425bf88ebec0..f404f0489e01 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 @@ package com.android.systemui.pip; 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 android.view.Gravity; 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 @@ public class PipBoundsHandlerTest extends SysuiTestCase { @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 2c4304d51720..d3b33992d017 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.assertNull; 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.net.Uri; 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 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { 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 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { 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 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { 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 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase { 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 5cbfcc1bcd06..e254cd2c82a7 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { /** * 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 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { */ private void ensureStateForBubbleUp() { ensureStateForAlertCommon(); - ensureStateForAlertAwakeCommon(); } @Test @@ -392,7 +382,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @Test public void testShouldNotHeadsUp_snoozedPackage() { NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT); - ensureStateForAlertAwakeCommon(); when(mHeadsUpManager.isSnoozed(entry.getSbn().getPackageName())).thenReturn(true); @@ -402,7 +391,6 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { @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 53a1773b1bd1..acdb2c59dc0c 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mNotificationActivityStarter.onNotificationClicked(sbn, mBubbleNotificationRow); // Then - verify(mBubbleController).expandStackAndSelectBubble(eq(sbn.getKey())); + verify(mBubbleController).expandStackAndSelectBubble(mBubbleNotificationRow.getEntry()); verify(mShadeController, atLeastOnce()).collapsePanel(); @@ -343,7 +343,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 e5c05408f628..45d53a14d1e9 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -68,8 +68,7 @@ import java.util.List; 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 cca604655f3d..ec12aebc37f6 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 @@ public final class ActiveServices { // 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 671733b7cb7a..7ef527cb7d84 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 @@ public class ActivityManagerService extends IActivityManager.Stub } 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 f7a158a885c6..a81590c8c022 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 @@ public final class OomAdjuster { 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 108fb7dff2cd..9f2a77c05d2b 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 @@ public final class ProcessList { 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 @@ public final class ProcessList { } 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 acf8b2e84821..c84ccb298bef 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.app.ActivityManager; 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 @@ import com.android.internal.annotations.GuardedBy; */ 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 @@ public final class UidRecord { 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 d7d413c2ffb0..0fa339f65cdb 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 @@ public class BubbleExtractor implements NotificationSignalExtractor { 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 @@ public class BubbleExtractor implements NotificationSignalExtractor { } 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 e79d33fa5f7a..0fa522cf51de 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.content.pm.ShortcutServiceInternal; 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 @@ public class ShortcutHelper { } 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 @@ public class ShortcutHelper { * @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 @@ public class ShortcutHelper { 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 910ed44df0f8..19fa9204935d 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 android.util.Slog; import com.android.server.FgThread; +import java.io.File; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -124,9 +125,13 @@ class IdmapDaemon { } } - 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 90c85ada9def..735d66983520 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.content.pm.PackageInfo; 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 @@ class IdmapManager { 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 @@ class IdmapManager { } 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 @@ class IdmapManager { 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 ef6655dd224e..2bc34998785b 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 @@ public class OverlayActorEnforcer { /** * @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 6a8e465529d7..086ab8183254 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.PackageInfo; 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.AtomicFile; 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 @@ public final class OverlayManagerService extends SystemService { 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 @@ public final class OverlayManagerService extends SystemService { } @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 5066ecdd6316..41c341adf1bc 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 @@ public interface OverlayableInfoCallback { * 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 f8278de1531d..7ab05c4f762c 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.RemoteException; 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (mDestroyed || mDataLoaderFinished) { switch (status) { case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: - onDataLoaderUnrecoverable(); + onStorageUnhealthy(); return; } return; @@ -2840,9 +2846,49 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - 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 fbe810025edb..aa7a1ad2b47a 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.pm.permission.PermissionsState; 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 @@ public class PackageManagerService extends IPackageManager.Stub 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 @@ public class PackageManagerService extends IPackageManager.Stub @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 @@ public class PackageManagerService extends IPackageManager.Stub 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 e3b1152cd7b7..323ac7b8806e 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 @@ public final class TvInputManagerService extends SystemService { 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 eb85db61754f..ad1ee1cc2843 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private ActivityRecord mFixedRotationLaunchingApp; + private FixedRotationAnimationController mFixedRotationAnimationController; + final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); @@ -1106,12 +1108,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mFixedRotationLaunchingApp; } + @VisibleForTesting + @Nullable FixedRotationAnimationController getFixedRotationAnimationController() { + return mFixedRotationAnimationController; + } + void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) { setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED); } @@ -1492,8 +1501,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo 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 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** 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 702df2a0fc63..96f236310d83 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 @@ public class DisplayRotation { }, true /* traverseTopToBottom */); mSeamlessRotationCount = 0; mRotatingSeamlessly = false; + mDisplayContent.finishFixedRotationAnimationIfPossible(); } private void prepareSeamlessRotation() { @@ -573,6 +574,10 @@ public class DisplayRotation { 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 @@ public class DisplayRotation { "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 000000000000..cc02e991c2ae --- /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 99f710bd8bd4..7a38bb65f73b 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 @@ public class ShellRoot { } } } - diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 42342a60ba16..0143eb1abe03 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 @@ class SurfaceAnimator { 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 @@ class SurfaceAnimator { 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 e9aff88d0f80..5fa4afd6dfdf 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mReportOrientationChanged = false; mDragResizingChangeReported = true; mWinAnimator.mSurfaceResized = false; + mResizeForBlastSyncReported = true; mWindowFrames.resetInsetsChanged(); final Rect frame = mWindowFrames.mCompatFrame; @@ -5500,7 +5506,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (!willSync) { return false; } + mResizeForBlastSyncReported = false; mLocalSyncId = mBLASTSyncEngine.startSyncSet(this); addChildrenToSyncSet(mLocalSyncId); @@ -5777,4 +5786,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 847667427593..6018b9e0b2e0 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -118,14 +118,18 @@ binder::Status BinderIncrementalService::openStorage(const std::string& path, } 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 5a7d5da56f18..af1136344246 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -41,8 +41,11 @@ public: 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 @@ public: 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 f0dca772adaa..b03d1eae8ca0 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -410,9 +410,12 @@ auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { } } -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 @@ StorageId IncrementalService::createStorage( // 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 @@ bool IncrementalService::mountExistingImage(std::string_view root) { 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 @@ void IncrementalService::runCmdLooper() { 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 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara 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 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { 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 @@ void IncrementalService::DataLoaderStub::cleanupResources() { 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 @@ void IncrementalService::DataLoaderStub::setTargetStatusLocked(int status) { 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 @@ bool IncrementalService::DataLoaderStub::create() { 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 @@ bool IncrementalService::DataLoaderStub::start() { 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::start() { } bool IncrementalService::DataLoaderStub::destroy() { - return mService.mDataLoaderManager->unbindFromDataLoader(mId).isOk(); + return mService.mDataLoaderManager->unbindFromDataLoader(id()).isOk(); } bool IncrementalService::DataLoaderStub::fsmStep() { @@ -1852,8 +1866,8 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount 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 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount 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 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount } } - 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 f3fde2a413e8..bde4ef68d2a7 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 RawMetadata = incfs::RawMetadata; 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 @@ private: 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 08fb486c8058..a76aa625ebc6 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -175,6 +175,10 @@ public: 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 abbf2f4c4424..a935ab99267e 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -69,6 +69,7 @@ public: 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 @@ public: 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 2e4625cf85a1..2948b6a0f293 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -284,6 +284,9 @@ public: 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 @@ public: 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 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { 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 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) 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 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { 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 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { 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 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { 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 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { 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 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { 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 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mIncFs->openMountSuccess(); + mIncFs->waitForPendingReadsSuccess(); mVold->bindMountSuccess(); mDataLoader->initializeCreateOkNoStatus(); mDataLoaderManager->bindToDataLoaderSuccess(); @@ -685,9 +700,9 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { // 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang // 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { 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 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { 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 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) { 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 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { 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 d338b587e059..ade01dc6afa6 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.doCallRealM 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 @@ public class BlobStoreManagerServiceTest { 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 class BlobStoreManagerServiceTest { 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 @@ public class BlobStoreManagerServiceTest { 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 f35eecf05f32..b7692f912e39 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI 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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @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 cd7343235750..b25af0543829 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.assertNull; import static org.junit.Assert.assertTrue; import android.content.om.OverlayInfo; +import android.os.OverlayablePolicy; import androidx.test.runner.AndroidJUnit4; @@ -49,11 +50,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes 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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @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); - assertState(STATE_ENABLED, OVERLAY, USER); - - endUpgradeTargetPackage(TARGET, USER); + upgradePackage(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); + upgradePackage(overlay, USER); + assertEquals(2, 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 - assertEquals(2, listener.count); + listener.count = 0; + final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target"); + upgradePackage(overlay2, USER); + assertEquals(3, listener.count); - beginUpgradeOverlayPackage(OVERLAY, USER); listener.count = 0; - endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER); - assertEquals(1, listener.count); + 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 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes 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 9eda718ed922..ec6a48182a25 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.OverlayInfo.State; 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 */); } - /** - * Adds the target 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 - */ - void installTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) != null) { - throw new IllegalStateException("package already installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageAdded(packageName, userId); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) { + return overlay(packageName, targetPackageName, null /* targetOverlayableName */); } - /** - * Begins upgrading the target 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); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName, + String targetOverlayableName) { + return new DummyDeviceState.PackageBuilder(packageName, targetPackageName, + targetOverlayableName); } - /** - * Ends upgrading the 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 endUpgradeTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageReplaced(packageName, userId); + void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) { + mState.add(pkg, userId); } - /** - * Removes the target 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"); - } - mState.remove(packageName, userId); - mImpl.onTargetPackageRemoved(packageName, 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 overlay 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 already installed + * @throws IllegalStateException if the package is currently installed */ - void installOverlayPackage(String packageName, String targetPackageName, 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.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageAdded(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageAdded(pkg.packageName, userId); } - mState.addOverlay(packageName, targetPackageName, userId); - mImpl.onOverlayPackageAdded(packageName, userId); } /** - * Begins upgrading the overlay 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 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 beginUpgradeOverlayPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed, cannot upgrade"); + 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"); + } + if (replacedPackage.targetPackageName != null) { + mImpl.onOverlayPackageReplacing(pkg.packageName, userId); } - mImpl.onOverlayPackageReplacing(packageName, userId); + mState.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageReplaced(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageReplaced(pkg.packageName, userId); + } } /** - * Ends upgrading the overlay package, potentially changing its target package. + * Removes the package from the device. * * 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. + * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast. * * @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"); + 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(pkg.packageName); + if (pkg.targetPackageName == null) { + mImpl.onTargetPackageRemoved(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageRemoved(pkg.packageName, userId); } - - 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; - } + /** Represents the state of packages installed on a fake device. */ + static class DummyDeviceState { + private ArrayMap<String, Package> mPackages = new ArrayMap<>(); + + void add(PackageBuilder pkgBuilder, int userId) { + final Package pkg = pkgBuilder.build(); + final Package previousPkg = select(pkg.packageName, userId); + mPackages.put(pkg.packageName, pkg); + + 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); + } + + void uninstall(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + if (pkg != null) { + pkg.installedUserIds.remove(userId); + } + } + + List<Package> select(int userId) { + return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId)) + .collect(Collectors.toList()); + } + + Package select(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null; } - /** 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); + private Package selectFromPath(String path) { + return mPackages.values().stream() + .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null); } - 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; + 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 Package(String packageName, int userId, String targetPackageName, - boolean mutable, boolean enabled, int priority) { + private PackageBuilder(String packageName, String targetPackage, + String targetOverlayableName) { + this.packageName = packageName; + 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.userId = userId; 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { 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 @@ class OverlayManagerServiceImplTestsBase { @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 @@ class OverlayManagerServiceImplTestsBase { @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 @@ class OverlayManagerServiceImplTestsBase { @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) { - return false; - } - final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId); - if (o == null) { - return false; - } - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.add(key); + 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 removeIdmap(@NonNull final OverlayInfo oi, final int userId) { - final String key = createKey(oi.packageName, oi.userId); - if (!mIdmapFiles.contains(key)) { + boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce, + int userId) { + final IdmapHeader idmap = mIdmapFiles.get(overlayPath); + if (idmap == null) { return false; } - mIdmapFiles.remove(key); - return true; + return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath); } @Override - boolean idmapExists(@NonNull final OverlayInfo oi) { - final String key = createKey(oi.packageName, oi.userId); - return mIdmapFiles.contains(key); + boolean idmapExists(String overlayPath, int userId) { + return mIdmapFiles.containsKey(overlayPath); } - @Override - boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) { - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.contains(key); + IdmapHeader getIdmap(String overlayPath) { + return mIdmapFiles.get(overlayPath); } - private String createKey(@NonNull final String packageName, final int userId) { - return String.format("%s:%d", packageName, userId); + 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; + } + + private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc, + String expectedTargetPath) { + return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc + && expectedTargetPath.equals(targetPath); + } } } 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 38b71b707196..13457f0a284c 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_RESIZEABLE; 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -320,6 +345,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertTrue(r.getNotification().isBubbleNotification()); } @@ -335,6 +361,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertTrue(r.getNotification().isBubbleNotification()); } @@ -350,7 +377,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { r.setShortcutInfo(null); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -366,7 +394,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { r.setShortcutInfo(null); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -381,7 +410,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -429,7 +461,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 @@ public class BubbleExtractorTest extends UiServiceTestCase { 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 ced780475fb7..2bea491949de 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 eb2d9fed197f..c700a090fa2e 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.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @SmallTest @@ -73,6 +74,8 @@ public class ShortcutHelperTest extends UiServiceTestCase { StatusBarNotification mSbn; @Mock Notification.BubbleMetadata mBubbleMetadata; + @Mock + ShortcutInfo mShortcutInfo; ShortcutHelper mShortcutHelper; @@ -86,13 +89,13 @@ public class ShortcutHelperTest extends UiServiceTestCase { 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,40 @@ public class ShortcutHelperTest extends UiServiceTestCase { } @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 */); + + verify(mLauncherApps, times(1)).unregisterCallback(any()); + } + + @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 */); @@ -138,6 +169,17 @@ public class ShortcutHelperTest extends UiServiceTestCase { } @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 4e82ceb882a8..d063f10d52e2 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.same; 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 @@ public class DisplayContentTests extends WindowTestsBase { @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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); + assertNull(mDisplayContent.getFixedRotationAnimationController()); } @Test @@ -1310,7 +1323,7 @@ public class DisplayContentTests extends WindowTestsBase { } 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 8ce5daa635f2..e9ed20bd9683 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 @@ public class WindowStateTests extends WindowTestsBase { } @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 f5ed64ebf3fb..fadebaa7bb8a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -12716,7 +12716,6 @@ public class TelephonyManager { @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 7d750b7bf690..1a58f17ef6a0 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 @@ public class AppLaunch extends InstrumentationTestCase { 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 829c883913e1..5c7c68b57ba1 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 6927e86213d8..7cf58e1682bf 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 @@ package com.android.utils.blob; 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.InputStream; 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 class Utils { 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 @@ public class Utils { 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; + } } |