diff options
87 files changed, 1666 insertions, 794 deletions
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index 7e8c90632fd9..1193ae9cf990 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -29,6 +29,8 @@ import static android.app.blob.XmlTags.TAG_COMMITTER; import static android.app.blob.XmlTags.TAG_LEASEE; import static android.os.Process.INVALID_UID; import static android.system.OsConstants.O_RDONLY; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_COMMIT_TIME; @@ -335,7 +337,9 @@ class BlobMetadata { } void forEachLeasee(Consumer<Leasee> consumer) { - mLeasees.forEach(consumer); + synchronized (mMetadataLock) { + mLeasees.forEach(consumer); + } } File getBlobFile() { @@ -460,54 +464,57 @@ class BlobMetadata { } void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { - fout.println("blobHandle:"); - fout.increaseIndent(); - mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); - fout.decreaseIndent(); - - fout.println("Committers:"); - fout.increaseIndent(); - if (mCommitters.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mCommitters.size(); i < count; ++i) { - final Committer committer = mCommitters.valueAt(i); - fout.println("committer " + committer.toString()); - fout.increaseIndent(); - committer.dump(fout); - fout.decreaseIndent(); + synchronized (mMetadataLock) { + fout.println("blobHandle:"); + fout.increaseIndent(); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); + fout.decreaseIndent(); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); + + fout.println("Committers:"); + fout.increaseIndent(); + if (mCommitters.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mCommitters.size(); i < count; ++i) { + final Committer committer = mCommitters.valueAt(i); + fout.println("committer " + committer.toString()); + fout.increaseIndent(); + committer.dump(fout); + fout.decreaseIndent(); + } } - } - fout.decreaseIndent(); + fout.decreaseIndent(); - fout.println("Leasees:"); - fout.increaseIndent(); - if (mLeasees.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mLeasees.size(); i < count; ++i) { - final Leasee leasee = mLeasees.valueAt(i); - fout.println("leasee " + leasee.toString()); - fout.increaseIndent(); - leasee.dump(mContext, fout); - fout.decreaseIndent(); + fout.println("Leasees:"); + fout.increaseIndent(); + if (mLeasees.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mLeasees.size(); i < count; ++i) { + final Leasee leasee = mLeasees.valueAt(i); + fout.println("leasee " + leasee.toString()); + fout.increaseIndent(); + leasee.dump(mContext, fout); + fout.decreaseIndent(); + } } - } - fout.decreaseIndent(); - - fout.println("Open fds:"); - fout.increaseIndent(); - if (mRevocableFds.isEmpty()) { - fout.println("<empty>"); - } else { - for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { - final String packageName = mRevocableFds.keyAt(i); - final ArraySet<RevocableFileDescriptor> packageFds = - mRevocableFds.valueAt(i); - fout.println(packageName + "#" + packageFds.size()); + fout.decreaseIndent(); + + fout.println("Open fds:"); + fout.increaseIndent(); + if (mRevocableFds.isEmpty()) { + fout.println("<empty>"); + } else { + for (int i = 0, count = mRevocableFds.size(); i < count; ++i) { + final String packageName = mRevocableFds.keyAt(i); + final ArraySet<RevocableFileDescriptor> packageFds = + mRevocableFds.valueAt(i); + fout.println(packageName + "#" + packageFds.size()); + } } + fout.decreaseIndent(); } - fout.decreaseIndent(); } void writeToXml(XmlSerializer out) throws IOException { 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 381efc10b416..78eab0b0a21e 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -1070,10 +1070,8 @@ public class BlobStoreManagerService extends SystemService { return shouldRemove; }); } - if (LOGV) { - Slog.v(TAG, "Completed idle maintenance; deleted " - + Arrays.toString(deletedBlobIds.toArray())); - } + Slog.d(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); writeBlobSessionsAsync(); } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 77ca4aa7a9e6..00983058841c 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -27,6 +27,8 @@ import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static android.text.format.Formatter.FLAG_IEC_UNITS; +import static android.text.format.Formatter.formatFileSize; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; @@ -533,6 +535,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { fout.println("ownerUid: " + mOwnerUid); fout.println("ownerPkg: " + mOwnerPackageName); fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs)); + fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); fout.println("blobHandle:"); fout.increaseIndent(); diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 062108757349..5ceea2aedb85 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -82,10 +82,10 @@ import android.os.BatteryStats; import android.os.Build; import android.os.Environment; import android.os.Handler; +import android.os.IDeviceIdleController; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.PowerWhitelistManager; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -93,7 +93,6 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings.Global; import android.telephony.TelephonyManager; -import android.util.ArrayMap; import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; @@ -205,6 +204,10 @@ public class AppStandbyController implements AppStandbyInternal { */ private static final long WAIT_FOR_ADMIN_DATA_TIMEOUT_MS = 10_000; + private static final int HEADLESS_APP_CHECK_FLAGS = + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS; + // To name the lock for stack traces static class Lock {} @@ -234,7 +237,7 @@ public class AppStandbyController implements AppStandbyInternal { * disabled). Presence in this map indicates that the app is a headless system app. */ @GuardedBy("mHeadlessSystemApps") - private final ArrayMap<String, Boolean> mHeadlessSystemApps = new ArrayMap<>(); + private final ArraySet<String> mHeadlessSystemApps = new ArraySet<>(); private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1); @@ -387,6 +390,7 @@ public class AppStandbyController implements AppStandbyInternal { DeviceStateReceiver deviceStateReceiver = new DeviceStateReceiver(); IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING); deviceStates.addAction(BatteryManager.ACTION_DISCHARGING); + deviceStates.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); mContext.registerReceiver(deviceStateReceiver, deviceStates); synchronized (mAppIdleLock) { @@ -442,6 +446,9 @@ public class AppStandbyController implements AppStandbyInternal { mSystemServicesReady = true; + // Offload to handler thread to avoid boot time impact. + mHandler.post(mInjector::updatePowerWhitelistCache); + boolean userFileExists; synchronized (mAppIdleLock) { userFileExists = mAppIdleHistory.userFileExists(UserHandle.USER_SYSTEM); @@ -1080,15 +1087,11 @@ public class AppStandbyController implements AppStandbyInternal { return STANDBY_BUCKET_EXEMPTED; } if (mSystemServicesReady) { - try { - // We allow all whitelisted apps, including those that don't want to be whitelisted - // for idle mode, because app idle (aka app standby) is really not as big an issue - // for controlling who participates vs. doze mode. - if (mInjector.isNonIdleWhitelisted(packageName)) { - return STANDBY_BUCKET_EXEMPTED; - } - } catch (RemoteException re) { - throw re.rethrowFromSystemServer(); + // We allow all whitelisted apps, including those that don't want to be whitelisted + // for idle mode, because app idle (aka app standby) is really not as big an issue + // for controlling who participates vs. doze mode. + if (mInjector.isNonIdleWhitelisted(packageName)) { + return STANDBY_BUCKET_EXEMPTED; } if (isActiveDeviceAdmin(packageName, userId)) { @@ -1123,7 +1126,7 @@ public class AppStandbyController implements AppStandbyInternal { private boolean isHeadlessSystemApp(String packageName) { synchronized (mHeadlessSystemApps) { - return mHeadlessSystemApps.containsKey(packageName); + return mHeadlessSystemApps.contains(packageName); } } @@ -1695,9 +1698,8 @@ public class AppStandbyController implements AppStandbyInternal { return; } try { - PackageInfo pi = mPackageManager.getPackageInfoAsUser(packageName, - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - userId); + PackageInfo pi = mPackageManager.getPackageInfoAsUser( + packageName, HEADLESS_APP_CHECK_FLAGS, userId); evaluateSystemAppException(pi); } catch (PackageManager.NameNotFoundException e) { synchronized (mHeadlessSystemApps) { @@ -1709,15 +1711,16 @@ public class AppStandbyController implements AppStandbyInternal { /** Returns true if the exception status changed. */ private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { if (pkgInfo == null || pkgInfo.applicationInfo == null - || !pkgInfo.applicationInfo.isSystemApp()) { + || (!pkgInfo.applicationInfo.isSystemApp() + && !pkgInfo.applicationInfo.isUpdatedSystemApp())) { return false; } synchronized (mHeadlessSystemApps) { if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { // Headless system app. - return mHeadlessSystemApps.put(pkgInfo.packageName, true) == null; + return mHeadlessSystemApps.add(pkgInfo.packageName); } else { - return mHeadlessSystemApps.remove(pkgInfo.packageName) != null; + return mHeadlessSystemApps.remove(pkgInfo.packageName); } } } @@ -1754,12 +1757,11 @@ public class AppStandbyController implements AppStandbyInternal { } } - /** Call on a system update to temporarily reset system app buckets. */ + /** Call on system boot to get the initial set of headless system apps. */ private void loadHeadlessSystemAppCache() { Slog.d(TAG, "Loading headless system app cache. appIdleEnabled=" + mAppIdleEnabled); final List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser( - PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS, - UserHandle.USER_SYSTEM); + HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { PackageInfo pkgInfo = packages.get(i); @@ -1807,8 +1809,6 @@ public class AppStandbyController implements AppStandbyInternal { + "): " + mCarrierPrivilegedApps); } - final long now = System.currentTimeMillis(); - pw.println(); pw.println("Settings:"); @@ -1868,12 +1868,14 @@ public class AppStandbyController implements AppStandbyInternal { synchronized (mHeadlessSystemApps) { for (int i = mHeadlessSystemApps.size() - 1; i >= 0; --i) { pw.print(" "); - pw.print(mHeadlessSystemApps.keyAt(i)); + pw.print(mHeadlessSystemApps.valueAt(i)); pw.println(","); } } pw.println("]"); pw.println(); + + mInjector.dump(pw); } /** @@ -1890,7 +1892,7 @@ public class AppStandbyController implements AppStandbyInternal { private PackageManagerInternal mPackageManagerInternal; private DisplayManager mDisplayManager; private PowerManager mPowerManager; - private PowerWhitelistManager mPowerWhitelistManager; + private IDeviceIdleController mDeviceIdleController; private CrossProfileAppsInternal mCrossProfileAppsInternal; int mBootPhase; /** @@ -1898,6 +1900,11 @@ public class AppStandbyController implements AppStandbyInternal { * automatically placed in the RESTRICTED bucket. */ long mAutoRestrictedBucketDelayMs = ONE_DAY; + /** + * Cached set of apps that are power whitelisted, including those not whitelisted from idle. + */ + @GuardedBy("mPowerWhitelistedApps") + private final ArraySet<String> mPowerWhitelistedApps = new ArraySet<>(); Injector(Context context, Looper looper) { mContext = context; @@ -1914,7 +1921,8 @@ public class AppStandbyController implements AppStandbyInternal { void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { - mPowerWhitelistManager = mContext.getSystemService(PowerWhitelistManager.class); + mDeviceIdleController = IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mBatteryStats = IBatteryStats.Stub.asInterface( ServiceManager.getService(BatteryStats.SERVICE_NAME)); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); @@ -1965,8 +1973,34 @@ public class AppStandbyController implements AppStandbyInternal { return mBatteryManager.isCharging(); } - boolean isNonIdleWhitelisted(String packageName) throws RemoteException { - return mPowerWhitelistManager.isWhitelisted(packageName, false); + boolean isNonIdleWhitelisted(String packageName) { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return false; + } + synchronized (mPowerWhitelistedApps) { + return mPowerWhitelistedApps.contains(packageName); + } + } + + private void updatePowerWhitelistCache() { + if (mBootPhase < PHASE_SYSTEM_SERVICES_READY) { + return; + } + try { + // Don't call out to DeviceIdleController with the lock held. + final String[] whitelistedPkgs = + mDeviceIdleController.getFullPowerWhitelistExceptIdle(); + synchronized (mPowerWhitelistedApps) { + mPowerWhitelistedApps.clear(); + final int len = whitelistedPkgs.length; + for (int i = 0; i < len; ++i) { + mPowerWhitelistedApps.add(whitelistedPkgs[i]); + } + } + } catch (RemoteException e) { + // Should not happen. + Slog.wtf(TAG, "Failed to get power whitelist", e); + } } boolean isRestrictedBucketEnabled() { @@ -2053,6 +2087,19 @@ public class AppStandbyController implements AppStandbyInternal { } return mCrossProfileAppsInternal.getTargetUserProfiles(pkg, userId); } + + void dump(PrintWriter pw) { + pw.println("mPowerWhitelistedApps=["); + synchronized (mPowerWhitelistedApps) { + for (int i = mPowerWhitelistedApps.size() - 1; i >= 0; --i) { + pw.print(" "); + pw.print(mPowerWhitelistedApps.valueAt(i)); + pw.println(","); + } + } + pw.println("]"); + pw.println(); + } } class AppStandbyHandler extends Handler { @@ -2138,6 +2185,11 @@ public class AppStandbyController implements AppStandbyInternal { case BatteryManager.ACTION_DISCHARGING: setChargingState(false); break; + case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: + if (mSystemServicesReady) { + mHandler.post(mInjector::updatePowerWhitelistCache); + } + break; } } } diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index 15a2f22e0fea..8a0f66040711 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -88,27 +88,4 @@ java_sdk_library { "com.android.os.statsd", "test_com.android.os.statsd", ], -} - -android_test { - name: "FrameworkStatsdTest", - platform_apis: true, - srcs: [ - // TODO(b/147705194): Use framework-statsd as a lib dependency instead. - ":framework-statsd-sources", - "test/**/*.java", - ], - manifest: "test/AndroidManifest.xml", - static_libs: [ - "androidx.test.rules", - "truth-prebuilt", - ], - libs: [ - "android.test.runner.stubs", - "android.test.base.stubs", - ], - test_suites: [ - "device-tests", - ], -} - +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/Android.bp b/apex/statsd/framework/test/Android.bp new file mode 100644 index 000000000000..b113d595b57c --- /dev/null +++ b/apex/statsd/framework/test/Android.bp @@ -0,0 +1,36 @@ +// 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. + +android_test { + name: "FrameworkStatsdTest", + platform_apis: true, + srcs: [ + // TODO(b/147705194): Use framework-statsd as a lib dependency instead. + ":framework-statsd-sources", + "**/*.java", + ], + manifest: "AndroidManifest.xml", + static_libs: [ + "androidx.test.rules", + "truth-prebuilt", + ], + libs: [ + "android.test.runner.stubs", + "android.test.base.stubs", + ], + test_suites: [ + "device-tests", + "mts", + ], +}
\ No newline at end of file diff --git a/apex/statsd/framework/test/AndroidTest.xml b/apex/statsd/framework/test/AndroidTest.xml new file mode 100644 index 000000000000..fb519150ecd5 --- /dev/null +++ b/apex/statsd/framework/test/AndroidTest.xml @@ -0,0 +1,34 @@ +<?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 Tests for Statsd."> + <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup"> + <option name="test-file-name" value="FrameworkStatsdTest.apk" /> + <option name="install-arg" value="-g" /> + </target_preparer> + + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="mts" /> + <option name="test-tag" value="FrameworkStatsdTest" /> + <test class="com.android.tradefed.testtype.AndroidJUnitTest" > + <option name="package" value="com.android.os.statsd.framework.test" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </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>
\ No newline at end of file diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 7a445a4cf8a8..042983be9ab7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -482,6 +482,7 @@ message Atom { BlobLeased blob_leased = 299 [(module) = "framework"]; BlobOpened blob_opened = 300 [(module) = "framework"]; ContactsProviderStatusReported contacts_provider_status_reported = 301; + KeystoreKeyEventReported keystore_key_event_reported = 302; // StatsdStats tracks platform atoms with ids upto 500. // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value. @@ -10926,6 +10927,114 @@ message MediametricsAudioDeviceConnectionReported { optional int32 connection_count = 6; } +/** + * Logs: i) creation of different types of cryptographic keys in the keystore, + * ii) operations performed using the keys, + * iii) attestation of the keys + * Logged from: system/security/keystore/key_event_log_handler.cpp + */ +message KeystoreKeyEventReported { + + enum Algorithm { + /** Asymmetric algorithms. */ + RSA = 1; + // 2 removed, do not reuse. + EC = 3; + /** Block cipher algorithms */ + AES = 32; + TRIPLE_DES = 33; + /** MAC algorithms */ + HMAC = 128; + }; + /** Algorithm associated with the key */ + optional Algorithm algorithm = 1; + + /** Size of the key */ + optional int32 key_size = 2; + + enum KeyOrigin { + /** Generated in keymaster. Should not exist outside the TEE. */ + GENERATED = 0; + /** Derived inside keymaster. Likely exists off-device. */ + DERIVED = 1; + /** Imported into keymaster. Existed as cleartext in Android. */ + IMPORTED = 2; + /** Keymaster did not record origin. */ + UNKNOWN = 3; + /** Securely imported into Keymaster. */ + SECURELY_IMPORTED = 4; + }; + /* Logs whether the key was generated, imported, securely imported, or derived.*/ + optional KeyOrigin key_origin = 3; + + enum HardwareAuthenticatorType { + NONE = 0; + PASSWORD = 1; + FINGERPRINT = 2; + // Additional entries must be powers of 2. + }; + /** + * What auth types does this key require? If none, + * then no auth required. + */ + optional HardwareAuthenticatorType user_auth_type = 4; + + /** + * If user authentication is required, is the requirement time based? If it + * is not time based then this field will not be used and the key is per + * operation. Per operation keys must be user authenticated on each usage. + */ + optional int32 user_auth_key_timeout_secs = 5; + + /** + * padding mode, digest, block_mode and purpose should ideally be repeated + * fields. However, since statsd does not support repeated fields in + * pushed atoms, they are represented using bitmaps. + */ + + /** Track which padding mode is being used.*/ + optional int32 padding_mode_bitmap = 6; + + /** Track which digest is being used. */ + optional int32 digest_bitmap = 7; + + /** Track what block mode is being used (for encryption). */ + optional int32 block_mode_bitmap = 8; + + /** Track what purpose is this key serving. */ + optional int32 purpose_bitmap = 9; + + enum EcCurve { + P_224 = 0; + P_256 = 1; + P_384 = 2; + P_521 = 3; + }; + /** Which ec curve was selected if elliptic curve cryptography is in use **/ + optional EcCurve ec_curve = 10; + + enum KeyBlobUsageRequirements { + STANDALONE = 0; + REQUIRES_FILE_SYSTEM = 1; + }; + /** Standalone or is a file system required */ + optional KeyBlobUsageRequirements key_blob_usage_reqs = 11; + + enum Type { + key_operation = 0; + key_creation = 1; + key_attestation = 2; + } + /** Key creation event, operation event or attestation event? */ + optional Type type = 12; + + /** Was the key creation, operation, or attestation successful? */ + optional bool was_successful = 13; + + /** Response code or error code */ + optional int32 error_code = 14; +} + // Blob Committer stats // Keep in sync between: // frameworks/base/core/proto/android/server/blobstoremanagerservice.proto diff --git a/core/java/android/companion/BluetoothDeviceFilterUtils.java b/core/java/android/companion/BluetoothDeviceFilterUtils.java index 24be45cb20fe..8e687413b7e1 100644 --- a/core/java/android/companion/BluetoothDeviceFilterUtils.java +++ b/core/java/android/companion/BluetoothDeviceFilterUtils.java @@ -51,13 +51,6 @@ public class BluetoothDeviceFilterUtils { return s == null ? null : Pattern.compile(s); } - static boolean matches(ScanFilter filter, BluetoothDevice device) { - boolean result = matchesAddress(filter.getDeviceAddress(), device) - && matchesServiceUuid(filter.getServiceUuid(), filter.getServiceUuidMask(), device); - if (DEBUG) debugLogMatchResult(result, device, filter); - return result; - } - static boolean matchesAddress(String deviceAddress, BluetoothDevice device) { final boolean result = deviceAddress == null || (device != null && deviceAddress.equals(device.getAddress())); diff --git a/core/java/android/companion/BluetoothLeDeviceFilter.java b/core/java/android/companion/BluetoothLeDeviceFilter.java index dccfb0346c9c..8c071fe99104 100644 --- a/core/java/android/companion/BluetoothLeDeviceFilter.java +++ b/core/java/android/companion/BluetoothLeDeviceFilter.java @@ -37,7 +37,6 @@ import android.util.Log; import com.android.internal.util.BitUtils; import com.android.internal.util.ObjectUtils; -import com.android.internal.util.Preconditions; import libcore.util.HexEncoding; @@ -166,21 +165,18 @@ public final class BluetoothLeDeviceFilter implements DeviceFilter<ScanResult> { /** @hide */ @Override - public boolean matches(ScanResult device) { - boolean result = matches(device.getDevice()) + public boolean matches(ScanResult scanResult) { + BluetoothDevice device = scanResult.getDevice(); + boolean result = getScanFilter().matches(scanResult) + && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device) && (mRawDataFilter == null - || BitUtils.maskedEquals(device.getScanRecord().getBytes(), + || BitUtils.maskedEquals(scanResult.getScanRecord().getBytes(), mRawDataFilter, mRawDataFilterMask)); if (DEBUG) Log.i(LOG_TAG, "matches(this = " + this + ", device = " + device + ") -> " + result); return result; } - private boolean matches(BluetoothDevice device) { - return BluetoothDeviceFilterUtils.matches(getScanFilter(), device) - && BluetoothDeviceFilterUtils.matchesName(getNamePattern(), device); - } - /** @hide */ @Override public int getMediumType() { diff --git a/core/java/android/os/Users.md b/core/java/android/os/Users.md index 3bbbe5452fd3..b019b0dc178b 100644 --- a/core/java/android/os/Users.md +++ b/core/java/android/os/Users.md @@ -18,54 +18,80 @@ ## Concepts -### User +### Users and profiles -A user of a device e.g. usually a human being. Each user has its own home screen. +#### User -#### User Profile +A user is a representation of a person using a device, with their own distinct application data +and some unique settings. Throughout this document, the word 'user' will be used in this technical +sense, i.e. for this virtual environment, whereas the word 'person' will be used to denote an actual +human interacting with the device. -A user can have multiple profiles. E.g. one for the private life and one for work. Each profile -has a different set of apps and accounts but they share one home screen. All profiles of a -profile group can be active at the same time. - -Each profile has a separate [`userId`](#int-userid). Unless needed user profiles are treated as -completely separate users. +Each user has a separate [`userId`](#int-userid). #### Profile Group -All user profiles that share a home screen. You can list the profiles of a user via -`UserManager#getEnabledProfiles` (you usually don't deal with disabled profiles) +Often, there is a 1-to-1 mapping of people who use a device to 'users'; e.g. there may be two users +on a device - the owner and a guest, each with their own separate home screen. -#### Foreground user vs background user +However, Android also supports multiple profiles for a single person, e.g. one for their private +life and one for work, both sharing a single home screen. +Each profile in a profile group is a distinct user, with a unique [`userId`](#int-userid), and have +a different set of apps and accounts, +but they share a single UI, single launcher, and single wallpaper. +All profiles of a profile group can be active at the same time. -Only a single user profile group can be in the foreground. This is the user profile the user -currently interacts with. +You can list the profiles of a user via `UserManager#getEnabledProfiles` (you usually don't deal +with disabled profiles) -#### Parent user (profile) +#### Parent user -The main profile of a profile group, usually the personal (as opposed to work) profile. Get this via -`UserManager#getProfileParent` (returns `null` if the user does not have profiles) +The main user of a profile group, to which the other profiles of the group 'belong'. +This is usually the personal (as opposed to work) profile. Get this via +`UserManager#getProfileParent` (returns `null` if the user does not have profiles). -#### Managed user (profile) +#### Profile (Managed profile) -The other profiles of a profile group. The name comes from the fact that these profiles are usually +A profile of the parent user, i.e. a profile belonging to the same profile group as a parent user, +with whom they share a single home screen. +Currently, the only type of profile supported in AOSP is a 'Managed Profile'. +The name comes from the fact that these profiles are usually managed by a device policy controller app. You can create a managed profile from within the device policy controller app on your phone. +Note that, as a member of the profile group, the parent user may sometimes also be considered a +'profile', but generally speaking, the word 'profile' denotes a user that is subordinate to a +parent. + +#### Foreground user vs background user + +Only a single user can be in the foreground. +This is the user with whom the person using the device is currently interacting, or, in the case +of profiles, the parent profile of this user. +All other running users are background users. +Some users may not be running at all, neither in the foreground nor the background. + #### Account -An account of a user profile with a (usually internet based) service. E.g. aname@gmail.com or -aname@yahoo.com. Each profile can have multiple accounts. A profile does not have to have a +An account of a user with a (usually internet based) service. E.g. aname@gmail.com or +aname@yahoo.com. Each user can have multiple accounts. A user does not have to have a account. +#### System User + +The user with [`userId`](#int-userid) 0 denotes the system user, which is always required to be +running. + +On most devices, the system user is also used by the primary person using the device; however, +on certain types of devices, the system user may be a stand-alone user, not intended for direct +human interaction. + ## Data types ### int userId -... usually marked as `@UserIdInt` - -The id of a user profile. List all users via `adb shell dumpsys user`. There is no data type for a -user, all you can do is using the user id of the parent profile as a proxy for the user. +The id of a user. List all users via `adb shell dumpsys user`. +In code, these are sometimes marked as `@UserIdInt`. ### int uid @@ -97,10 +123,10 @@ mechanism should be access controlled by permissions. A system service should deal with users being started and stopped by overriding `SystemService.onSwitchUser` and `SystemService.onStopUser`. -If users profiles become inactive the system should stop all apps of this profile from interacting +If a user become inactive the system should stop all apps of this user from interacting with other apps or the system. -Another important lifecycle event is `onUnlockUser`. Only for unlocked user profiles you can access +Another important lifecycle event is `onUnlockUser`. Only for an unlocked user can you access all data, e.g. which packages are installed. You only want to deal with user profiles that diff --git a/core/java/android/permission/Permissions.md b/core/java/android/permission/Permissions.md index 2bf08e2ff2d4..1ef3ad211cee 100644 --- a/core/java/android/permission/Permissions.md +++ b/core/java/android/permission/Permissions.md @@ -706,9 +706,9 @@ App-op permissions are user-switchable permissions that are not runtime permissi be used for permissions that are really only meant to be ever granted to a very small amount of apps. Traditionally granting these permissions is intentionally very heavy weight so that the user really needs to understand the use case. For example one use case is the -`INTERACT_ACROSS_PROFILES` permission that allows apps of different -[user profiles](../os/Users.md#user-profile) to interact. Of course this is breaking a very basic -security container and hence should only every be granted with a lot of care. +`INTERACT_ACROSS_PROFILES` permission that allows apps of different users within the same +[profile group](../os/Users.md#profile-group) to interact. Of course this is breaking a very basic +security container and hence should only ever be granted with a lot of care. **Warning:** Most app-op permissions follow this logic, but most of them also have exceptions and special behavior. Hence this section is a guideline, not a rule. diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java index c2234bad3803..95cc64ae8aab 100644 --- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java +++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java @@ -564,9 +564,9 @@ public abstract class AugmentedAutofillService extends Service { } void reportResult(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { try { - mCallback.onSuccess(inlineSuggestionsData, clientState); + mCallback.onSuccess(inlineSuggestionsData, clientState, showingFillWindow); } catch (RemoteException e) { Log.e(TAG, "Error calling back with the inline suggestions data: " + e); } diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java index 8ba5c173890c..fc3baf1c9836 100644 --- a/core/java/android/service/autofill/augmented/FillCallback.java +++ b/core/java/android/service/autofill/augmented/FillCallback.java @@ -56,23 +56,24 @@ public final class FillCallback { if (response == null) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE); - mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ null); + mProxy.reportResult(/* inlineSuggestionsData */ null, /* clientState */ + null, /* showingFillWindow */ false); return; } - List<Dataset> inlineSuggestions = response.getInlineSuggestions(); - Bundle clientState = response.getClientState(); - // We need to report result regardless of whether inline suggestions are returned or not. - mProxy.reportResult(inlineSuggestions, clientState); + final List<Dataset> inlineSuggestions = response.getInlineSuggestions(); + final Bundle clientState = response.getClientState(); + final FillWindow fillWindow = response.getFillWindow(); + boolean showingFillWindow = false; if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) { mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE); - return; - } - - final FillWindow fillWindow = response.getFillWindow(); - if (fillWindow != null) { + } else if (fillWindow != null) { fillWindow.show(); + showingFillWindow = true; } + // We need to report result regardless of whether inline suggestions are returned or not. + mProxy.reportResult(inlineSuggestions, clientState, showingFillWindow); + // TODO(b/123099468): must notify the server so it can update the session state to avoid // showing conflicting UIs (for example, if a new request is made to the main autofill // service and it now wants to show something). diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl index 609e382e2b96..4dfdd4db27e5 100644 --- a/core/java/android/service/autofill/augmented/IFillCallback.aidl +++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl @@ -30,7 +30,9 @@ import java.util.List; */ interface IFillCallback { void onCancellable(in ICancellationSignal cancellation); - void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, in @nullable Bundle clientState); + void onSuccess(in @nullable List<Dataset> inlineSuggestionsData, + in @nullable Bundle clientState, + boolean showingFillWindow); boolean isCompleted(); void cancel(); } diff --git a/core/java/android/widget/inline/InlineContentView.java b/core/java/android/widget/inline/InlineContentView.java index 6a85de5ca757..8ca218c1d1a7 100644 --- a/core/java/android/widget/inline/InlineContentView.java +++ b/core/java/android/widget/inline/InlineContentView.java @@ -27,6 +27,7 @@ import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnPreDrawListener; import java.util.function.Consumer; @@ -130,6 +131,16 @@ public class InlineContentView extends ViewGroup { @Nullable private SurfacePackageUpdater mSurfacePackageUpdater; + @NonNull + private final OnPreDrawListener mDrawListener = new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + int visibility = InlineContentView.this.isShown() ? VISIBLE : GONE; + mSurfaceView.setVisibility(visibility); + return true; + } + }; + /** * @inheritDoc * @hide @@ -202,6 +213,8 @@ public class InlineContentView extends ViewGroup { } }); } + mSurfaceView.setVisibility(VISIBLE); + getViewTreeObserver().addOnPreDrawListener(mDrawListener); } @Override @@ -211,6 +224,7 @@ public class InlineContentView extends ViewGroup { if (mSurfacePackageUpdater != null) { mSurfacePackageUpdater.onSurfacePackageReleased(); } + getViewTreeObserver().removeOnPreDrawListener(mDrawListener); } @Override diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index e0b67da3bcd4..dc4c8fd76e8e 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -195,6 +195,7 @@ public class ChooserActivity extends ResolverActivity implements private boolean mIsAppPredictorComponentAvailable; private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache; private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache; + private Map<ComponentName, ComponentName> mChooserTargetComponentNameCache; public static final int TARGET_TYPE_DEFAULT = 0; public static final int TARGET_TYPE_CHOOSER_TARGET = 1; @@ -511,6 +512,11 @@ public class ChooserActivity extends ResolverActivity implements adapterForUserHandle.addServiceResults(sri.originalTarget, sri.resultTargets, TARGET_TYPE_CHOOSER_TARGET, /* directShareShortcutInfoCache */ null, mServiceConnections); + if (!sri.resultTargets.isEmpty() && sri.originalTarget != null) { + mChooserTargetComponentNameCache.put( + sri.resultTargets.get(0).getComponentName(), + sri.originalTarget.getResolvedComponentName()); + } } } unbindService(sri.connection); @@ -772,6 +778,7 @@ public class ChooserActivity extends ResolverActivity implements target.getAction() ); mDirectShareShortcutInfoCache = new HashMap<>(); + mChooserTargetComponentNameCache = new HashMap<>(); } @Override @@ -2242,15 +2249,18 @@ public class ChooserActivity extends ResolverActivity implements List<AppTargetId> targetIds = new ArrayList<>(); for (ChooserTargetInfo chooserTargetInfo : surfacedTargetInfo) { ChooserTarget chooserTarget = chooserTargetInfo.getChooserTarget(); - String componentName = chooserTarget.getComponentName().flattenToString(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); if (mDirectShareShortcutInfoCache.containsKey(chooserTarget)) { String shortcutId = mDirectShareShortcutInfoCache.get(chooserTarget).getId(); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", shortcutId, componentName, SHORTCUT_TARGET))); + String.format("%s/%s/%s", shortcutId, componentName.flattenToString(), + SHORTCUT_TARGET))); } else { String titleHash = ChooserUtil.md5(chooserTarget.getTitle().toString()); targetIds.add(new AppTargetId( - String.format("%s/%s/%s", titleHash, componentName, CHOOSER_TARGET))); + String.format("%s/%s/%s", titleHash, componentName.flattenToString(), + CHOOSER_TARGET))); } } directShareAppPredictor.notifyLaunchLocationShown(LAUNCH_LOCATION_DIRECT_SHARE, targetIds); @@ -2272,7 +2282,8 @@ public class ChooserActivity extends ResolverActivity implements } if (mChooserTargetRankingEnabled && appTarget == null) { // Send ChooserTarget sharing info to AppPredictor. - ComponentName componentName = chooserTarget.getComponentName(); + ComponentName componentName = mChooserTargetComponentNameCache.getOrDefault( + chooserTarget.getComponentName(), chooserTarget.getComponentName()); try { appTarget = new AppTarget.Builder( new AppTargetId(componentName.flattenToString()), @@ -2800,17 +2811,7 @@ public class ChooserActivity extends ResolverActivity implements || chooserListAdapter.mDisplayList.isEmpty()) { chooserListAdapter.notifyDataSetChanged(); } else { - new AsyncTask<Void, Void, Void>() { - @Override - protected Void doInBackground(Void... voids) { - chooserListAdapter.updateAlphabeticalList(); - return null; - } - @Override - protected void onPostExecute(Void aVoid) { - chooserListAdapter.notifyDataSetChanged(); - } - }.execute(); + chooserListAdapter.updateAlphabeticalList(); } // don't support direct share on low ram devices diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java index d6ff7b13c934..05169023000f 100644 --- a/core/java/com/android/internal/app/ChooserListAdapter.java +++ b/core/java/com/android/internal/app/ChooserListAdapter.java @@ -275,33 +275,43 @@ public class ChooserListAdapter extends ResolverListAdapter { } void updateAlphabeticalList() { - mSortedList.clear(); - List<DisplayResolveInfo> tempList = new ArrayList<>(); - tempList.addAll(mDisplayList); - tempList.addAll(mCallerTargets); - if (mEnableStackedApps) { - // Consolidate multiple targets from same app. - Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); - for (DisplayResolveInfo info : tempList) { - String packageName = info.getResolvedComponentName().getPackageName(); - DisplayResolveInfo multiDri = consolidated.get(packageName); - if (multiDri == null) { - consolidated.put(packageName, info); - } else if (multiDri instanceof MultiDisplayResolveInfo) { - ((MultiDisplayResolveInfo) multiDri).addTarget(info); - } else { - // create consolidated target from the single DisplayResolveInfo - MultiDisplayResolveInfo multiDisplayResolveInfo = + new AsyncTask<Void, Void, List<DisplayResolveInfo>>() { + @Override + protected List<DisplayResolveInfo> doInBackground(Void... voids) { + List<DisplayResolveInfo> allTargets = new ArrayList<>(); + allTargets.addAll(mDisplayList); + allTargets.addAll(mCallerTargets); + if (!mEnableStackedApps) { + return allTargets; + } + // Consolidate multiple targets from same app. + Map<String, DisplayResolveInfo> consolidated = new HashMap<>(); + for (DisplayResolveInfo info : allTargets) { + String packageName = info.getResolvedComponentName().getPackageName(); + DisplayResolveInfo multiDri = consolidated.get(packageName); + if (multiDri == null) { + consolidated.put(packageName, info); + } else if (multiDri instanceof MultiDisplayResolveInfo) { + ((MultiDisplayResolveInfo) multiDri).addTarget(info); + } else { + // create consolidated target from the single DisplayResolveInfo + MultiDisplayResolveInfo multiDisplayResolveInfo = new MultiDisplayResolveInfo(packageName, multiDri); - multiDisplayResolveInfo.addTarget(info); - consolidated.put(packageName, multiDisplayResolveInfo); + multiDisplayResolveInfo.addTarget(info); + consolidated.put(packageName, multiDisplayResolveInfo); + } } + List<DisplayResolveInfo> groupedTargets = new ArrayList<>(); + groupedTargets.addAll(consolidated.values()); + Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext)); + return groupedTargets; } - mSortedList.addAll(consolidated.values()); - } else { - mSortedList.addAll(tempList); - } - Collections.sort(mSortedList, new ChooserActivity.AzInfoComparator(mContext)); + @Override + protected void onPostExecute(List<DisplayResolveInfo> newList) { + mSortedList = newList; + notifyDataSetChanged(); + } + }.execute(); } @Override diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index daacd459a1a7..86c13a0581c2 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1938,7 +1938,7 @@ public class ResolverActivity extends Activity implements ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); activeListAdapter.notifyDataSetChanged(); - if (activeListAdapter.getCount() == 0) { + if (activeListAdapter.getCount() == 0 && !inactiveListAdapterHasItems()) { // We no longer have any items... just finish the activity. finish(); } @@ -1948,6 +1948,13 @@ public class ResolverActivity extends Activity implements } } + private boolean inactiveListAdapterHasItems() { + if (!shouldShowTabs()) { + return false; + } + return mMultiProfilePagerAdapter.getInactiveListAdapter().getCount() > 0; + } + private BroadcastReceiver createWorkProfileStateReceiver() { return new BroadcastReceiver() { @Override diff --git a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java index c11b939098c4..69ca9922e81f 100644 --- a/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java +++ b/core/java/com/android/internal/os/KernelCpuThreadReaderDiff.java @@ -59,7 +59,7 @@ import java.util.Objects; * #getProcessCpuUsageDiffed()} result. * * <p>Thresholding is done in this class, instead of {@link KernelCpuThreadReader}, and instead of - * WestWorld, because the thresholding should be done after diffing, not before. This is because of + * statsd, because the thresholding should be done after diffing, not before. This is because of * two issues with thresholding before diffing: * * <ul> diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java index 7bfed91c42b9..6fe1d8140371 100644 --- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java +++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java @@ -16,6 +16,7 @@ package com.android.internal.policy; +import android.graphics.Insets; import android.graphics.RecordingCanvas; import android.graphics.Rect; import android.graphics.RenderNode; @@ -69,16 +70,14 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame private ColorDrawable mNavigationBarColor; private boolean mOldFullscreen; private boolean mFullscreen; - private final Rect mOldSystemInsets = new Rect(); - private final Rect mOldStableInsets = new Rect(); - private final Rect mSystemInsets = new Rect(); - private final Rect mStableInsets = new Rect(); + private final Rect mOldSystemBarInsets = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private final Rect mTmpRect = new Rect(); public BackdropFrameRenderer(DecorView decorView, ThreadedRenderer renderer, Rect initialBounds, Drawable resizingBackgroundDrawable, Drawable captionBackgroundDrawable, Drawable userCaptionBackgroundDrawable, int statusBarColor, int navigationBarColor, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + boolean fullscreen, Insets systemBarInsets) { setName("ResizeFrame"); mRenderer = renderer; @@ -95,10 +94,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mTargetRect.set(initialBounds); mFullscreen = fullscreen; mOldFullscreen = fullscreen; - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); - mOldSystemInsets.set(systemInsets); - mOldStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets.toRect()); + mOldSystemBarInsets.set(systemBarInsets.toRect()); // Kick off our draw thread. start(); @@ -154,16 +151,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newTargetBounds The new target bounds. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. + * @param systemBarInsets The current visible system insets for the window. */ - public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + public void setTargetRect(Rect newTargetBounds, boolean fullscreen, Rect systemBarInsets) { synchronized (this) { mFullscreen = fullscreen; mTargetRect.set(newTargetBounds); - mSystemInsets.set(systemInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); // Notify of a bounds change. pingRenderLocked(false /* drawImmediate */); } @@ -247,14 +241,12 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame mNewTargetRect.set(mTargetRect); if (!mNewTargetRect.equals(mOldTargetRect) || mOldFullscreen != mFullscreen - || !mStableInsets.equals(mOldStableInsets) - || !mSystemInsets.equals(mOldSystemInsets) + || !mSystemBarInsets.equals(mOldSystemBarInsets) || mReportNextDraw) { mOldFullscreen = mFullscreen; mOldTargetRect.set(mNewTargetRect); - mOldSystemInsets.set(mSystemInsets); - mOldStableInsets.set(mStableInsets); - redrawLocked(mNewTargetRect, mFullscreen, mSystemInsets, mStableInsets); + mOldSystemBarInsets.set(mSystemBarInsets); + redrawLocked(mNewTargetRect, mFullscreen); } } @@ -304,11 +296,8 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame * * @param newBounds The window bounds which needs to be drawn. * @param fullscreen Whether the window is currently drawing in fullscreen. - * @param systemInsets The current visible system insets for the window. - * @param stableInsets The stable insets for the window. */ - private void redrawLocked(Rect newBounds, boolean fullscreen, Rect systemInsets, - Rect stableInsets) { + private void redrawLocked(Rect newBounds, boolean fullscreen) { // While a configuration change is taking place the view hierarchy might become // inaccessible. For that case we remember the previous metrics to avoid flashes. @@ -355,7 +344,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame } mFrameAndBackdropNode.endRecording(); - drawColorViews(left, top, width, height, fullscreen, systemInsets, stableInsets); + drawColorViews(left, top, width, height, fullscreen); // We need to render the node explicitly mRenderer.drawRenderNode(mFrameAndBackdropNode); @@ -363,14 +352,13 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame reportDrawIfNeeded(); } - private void drawColorViews(int left, int top, int width, int height, - boolean fullscreen, Rect systemInsets, Rect stableInsets) { + private void drawColorViews(int left, int top, int width, int height, boolean fullscreen) { if (mSystemBarBackgroundNode == null) { return; } RecordingCanvas canvas = mSystemBarBackgroundNode.beginRecording(width, height); mSystemBarBackgroundNode.setLeftTopRightBottom(left, top, left + width, top + height); - final int topInset = DecorView.getColorViewTopInset(mStableInsets.top, mSystemInsets.top); + final int topInset = mSystemBarInsets.top; if (mStatusBarColor != null) { mStatusBarColor.setBounds(0, 0, left + width, topInset); mStatusBarColor.draw(canvas); @@ -380,7 +368,7 @@ public class BackdropFrameRenderer extends Thread implements Choreographer.Frame // don't want the navigation bar background be moving around when resizing in docked mode. // However, we need it for the transitions into/out of docked mode. if (mNavigationBarColor != null && fullscreen) { - DecorView.getNavigationBarRect(width, height, stableInsets, systemInsets, mTmpRect, 1f); + DecorView.getNavigationBarRect(width, height, mSystemBarInsets, mTmpRect, 1f); mNavigationBarColor.setBounds(mTmpRect); mNavigationBarColor.draw(canvas); } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index c6135f2c81d3..b12c5e9ba5b0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -1050,22 +1050,6 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return false; } - public static int getColorViewTopInset(int stableTop, int systemTop) { - return Math.min(stableTop, systemTop); - } - - public static int getColorViewBottomInset(int stableBottom, int systemBottom) { - return Math.min(stableBottom, systemBottom); - } - - public static int getColorViewRightInset(int stableRight, int systemRight) { - return Math.min(stableRight, systemRight); - } - - public static int getColorViewLeftInset(int stableLeft, int systemLeft) { - return Math.min(stableLeft, systemLeft); - } - public static boolean isNavBarToRightEdge(int bottomInset, int rightInset) { return bottomInset == 0 && rightInset > 0; } @@ -1079,14 +1063,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset; } - public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect stableInsets, - Rect contentInsets, Rect outRect, float scale) { - final int bottomInset = - (int) (getColorViewBottomInset(stableInsets.bottom, contentInsets.bottom) * scale); - final int leftInset = - (int) (getColorViewLeftInset(stableInsets.left, contentInsets.left) * scale); - final int rightInset = - (int) (getColorViewLeftInset(stableInsets.right, contentInsets.right) * scale); + public static void getNavigationBarRect(int canvasWidth, int canvasHeight, Rect systemBarInsets, + Rect outRect, float scale) { + final int bottomInset = (int) (systemBarInsets.bottom * scale); + final int leftInset = (int) (systemBarInsets.left * scale); + final int rightInset = (int) (systemBarInsets.right * scale); final int size = getNavBarSize(bottomInset, rightInset, leftInset); if (isNavBarToRightEdge(bottomInset, rightInset)) { outRect.set(canvasWidth - size, 0, canvasWidth, canvasHeight); @@ -1113,31 +1094,30 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mLastWindowFlags = attrs.flags; if (insets != null) { - mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(), - insets.getSystemWindowInsetTop()); - mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(), - insets.getSystemWindowInsetBottom()); - mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(), - insets.getSystemWindowInsetRight()); - mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(), - insets.getSystemWindowInsetLeft()); + final Insets systemBarInsets = insets.getInsets(WindowInsets.Type.systemBars()); + final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( + WindowInsets.Type.systemBars()); + mLastTopInset = systemBarInsets.top; + mLastBottomInset = systemBarInsets.bottom; + mLastRightInset = systemBarInsets.right; + mLastLeftInset = systemBarInsets.left; // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the // first time, or the window size or position has changed. - boolean hasTopStableInset = insets.getStableInsetTop() != 0; + boolean hasTopStableInset = stableBarInsets.top != 0; disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); mLastHasTopStableInset = hasTopStableInset; - boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; + boolean hasBottomStableInset = stableBarInsets.bottom != 0; disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); mLastHasBottomStableInset = hasBottomStableInset; - boolean hasRightStableInset = insets.getStableInsetRight() != 0; + boolean hasRightStableInset = stableBarInsets.right != 0; disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); mLastHasRightStableInset = hasRightStableInset; - boolean hasLeftStableInset = insets.getStableInsetLeft() != 0; + boolean hasLeftStableInset = stableBarInsets.left != 0; disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); mLastHasLeftStableInset = hasLeftStableInset; @@ -2296,7 +2276,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind public void onWindowSizeIsChanging(Rect newBounds, boolean fullscreen, Rect systemInsets, Rect stableInsets) { if (mBackdropFrameRenderer != null) { - mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets, stableInsets); + mBackdropFrameRenderer.setTargetRect(newBounds, fullscreen, systemInsets); } } @@ -2314,11 +2294,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind final ThreadedRenderer renderer = getThreadedRenderer(); if (renderer != null) { loadBackgroundDrawablesIfNeeded(); + WindowInsets rootInsets = getRootWindowInsets(); mBackdropFrameRenderer = new BackdropFrameRenderer(this, renderer, initialBounds, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), - getCurrentColor(mNavigationColorViewState), fullscreen, systemInsets, - stableInsets); + getCurrentColor(mNavigationColorViewState), fullscreen, + rootInsets.getInsets(WindowInsets.Type.systemBars())); // Get rid of the shadow while we are resizing. Shadow drawing takes considerable time. // If we want to get the shadow shown while resizing, we would need to elevate a new diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java index b64923fb5bf8..5d4407bf8370 100644 --- a/core/java/com/android/internal/widget/ConversationLayout.java +++ b/core/java/com/android/internal/widget/ConversationLayout.java @@ -233,13 +233,20 @@ public class ConversationLayout extends FrameLayout oldVisibility = mImportanceRingView.getVisibility(); wasGone = oldVisibility == GONE; visibility = !mImportantConversation ? GONE : visibility; - isGone = visibility == GONE; - if (wasGone != isGone) { + boolean isRingGone = visibility == GONE; + if (wasGone != isRingGone) { // Keep the badge visibility in sync with the icon. This is necessary in cases // Where the icon is being hidden externally like in group children. mImportanceRingView.animate().cancel(); mImportanceRingView.setVisibility(visibility); } + + oldVisibility = mConversationIconBadge.getVisibility(); + wasGone = oldVisibility == GONE; + if (wasGone != isGone) { + mConversationIconBadge.animate().cancel(); + mConversationIconBadge.setVisibility(visibility); + } }); // When the small icon is gone, hide the rest of the badge mIcon.setOnForceHiddenChangedListener((forceHidden) -> { diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java index 53272f7eebf9..f312d1d4f25d 100644 --- a/core/java/com/android/internal/widget/MessagingGroup.java +++ b/core/java/com/android/internal/widget/MessagingGroup.java @@ -42,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RemoteViews; +import android.widget.TextView; import com.android.internal.R; @@ -109,6 +110,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou private ViewGroup mMessagingIconContainer; private int mConversationContentStart; private int mNonConversationMarginEnd; + private int mNotificationTextMarginTop; public MessagingGroup(@NonNull Context context) { super(context); @@ -148,6 +150,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou R.dimen.conversation_content_start); mNonConversationMarginEnd = getResources().getDimensionPixelSize( R.dimen.messaging_layout_margin_end); + mNotificationTextMarginTop = getResources().getDimensionPixelSize( + R.dimen.notification_text_margin_top); } public void updateClipRect() { @@ -612,7 +616,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou return 0; } - public View getSenderView() { + public TextView getSenderView() { return mSenderView; } @@ -664,10 +668,14 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou public void setSingleLine(boolean singleLine) { if (singleLine != mSingleLine) { mSingleLine = singleLine; + MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams(); + p.topMargin = singleLine ? 0 : mNotificationTextMarginTop; + mMessageContainer.setLayoutParams(p); mContentContainer.setOrientation( singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); + mSenderView.setSingleLine(singleLine); updateMaxDisplayedLines(); updateClipRect(); updateSenderVisibility(); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index ac04862d9a7d..4ee647a42116 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -300,6 +300,27 @@ public class MessagingLinearLayout extends ViewGroup { return mMessagingLayout; } + @Override + public int getBaseline() { + // When placed in a horizontal linear layout (as is the case in a single-line MessageGroup), + // align with the last visible child (which is the one that will be displayed in the single- + // line group. + int childCount = getChildCount(); + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (isGone(child)) { + continue; + } + final int childBaseline = child.getBaseline(); + if (childBaseline == -1) { + return -1; + } + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + return lp.topMargin + childBaseline; + } + return super.getBaseline(); + } + public interface MessagingChild { int MEASURED_NORMAL = 0; int MEASURED_SHORTENED = 1; diff --git a/core/proto/android/stats/launcher/launcher.proto b/core/proto/android/stats/launcher/launcher.proto index dbd0e038c40c..fc177d57b193 100644 --- a/core/proto/android/stats/launcher/launcher.proto +++ b/core/proto/android/stats/launcher/launcher.proto @@ -32,10 +32,12 @@ enum LauncherAction { } enum LauncherState { - BACKGROUND = 0; - HOME = 1; - OVERVIEW = 2; - ALLAPPS = 3; + LAUNCHER_STATE_UNSPECIFIED = 0; + BACKGROUND = 1; + HOME = 2; + OVERVIEW = 3; + ALLAPPS = 4; + UNCHANGED = 5; } message LauncherTarget { diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml index 3188861a52a5..5e3b657353b6 100644 --- a/core/res/res/layout/notification_template_messaging_group.xml +++ b/core/res/res/layout/notification_template_messaging_group.xml @@ -37,6 +37,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" + android:baselineAligned="true" android:orientation="vertical"> <com.android.internal.widget.ImageFloatingTextView android:id="@+id/message_name" diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp index d6542de91e08..e5492714b29f 100644 --- a/data/etc/car/Android.bp +++ b/data/etc/car/Android.bp @@ -129,6 +129,13 @@ prebuilt_etc { } prebuilt_etc { + name: "privapp_whitelist_com.android.car.companiondevicesupport", + sub_dir: "permissions", + src: "com.android.car.companiondevicesupport.xml", + filename_from_src: true, +} + +prebuilt_etc { name: "privapp_whitelist_com.google.android.car.kitchensink", sub_dir: "permissions", src: "com.google.android.car.kitchensink.xml", diff --git a/data/etc/car/com.android.car.companiondevicesupport.xml b/data/etc/car/com.android.car.companiondevicesupport.xml new file mode 100644 index 000000000000..2067bab20d3d --- /dev/null +++ b/data/etc/car/com.android.car.companiondevicesupport.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<permissions> + <privapp-permissions package="com.android.car.companiondevicesupport"> + <permission name="android.permission.INTERACT_ACROSS_USERS"/> + <permission name="android.permission.MANAGE_USERS"/> + <permission name="android.permission.PROVIDE_TRUST_AGENT"/> + <permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/> + </privapp-permissions> +</permissions> diff --git a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml index 7313d54c599a..0f561cb933e6 100644 --- a/packages/SystemUI/res/layout/bubbles_manage_button_education.xml +++ b/packages/SystemUI/res/layout/bubbles_manage_button_education.xml @@ -41,7 +41,8 @@ android:paddingStart="16dp" android:paddingBottom="16dp" android:fontFamily="@*android:string/config_bodyFontFamilyMedium" - android:maxLines="1" + android:maxLines="2" + android:ellipsize="end" android:text="@string/bubbles_user_education_manage_title" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Headline"/> @@ -52,6 +53,8 @@ android:paddingStart="16dp" android:paddingBottom="24dp" android:text="@string/bubbles_user_education_manage" + android:maxLines="2" + android:ellipsize="end" android:fontFamily="@*android:string/config_bodyFontFamily" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Body2"/> diff --git a/packages/SystemUI/res/layout/home_handle.xml b/packages/SystemUI/res/layout/home_handle.xml index 7c5db105a09e..54a0b9fba39c 100644 --- a/packages/SystemUI/res/layout/home_handle.xml +++ b/packages/SystemUI/res/layout/home_handle.xml @@ -21,6 +21,7 @@ android:layout_width="@dimen/navigation_home_handle_width" android:layout_height="match_parent" android:layout_weight="0" + android:contentDescription="@string/accessibility_home" android:paddingStart="@dimen/navigation_key_padding" android:paddingEnd="@dimen/navigation_key_padding" /> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java index 55be77ca3be5..a86a46960bcb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java @@ -100,6 +100,7 @@ public class BadgedImageView extends ImageView { mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE); setFocusable(true); + setClickable(true); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 7f78ddf2cf1c..6377b4f0a9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -15,7 +15,6 @@ */ package com.android.systemui.bubbles; -import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; @@ -29,21 +28,19 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Path; -import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.os.Bundle; +import android.graphics.drawable.Icon; import android.os.UserHandle; import android.provider.Settings; -import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.InstanceId; import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -57,17 +54,12 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; - /** - * NotificationEntry associated with the bubble. A null value implies this bubble is loaded - * from disk. - */ - @Nullable - private NotificationEntry mEntry; private final String mKey; private long mLastUpdated; private long mLastAccessed; + @Nullable private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ @@ -75,8 +67,6 @@ 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; @@ -92,6 +82,7 @@ class Bubble implements BubbleViewProvider { * Presentational info about the flyout. */ public static class FlyoutMessage { + @Nullable public Icon senderIcon; @Nullable public Drawable senderAvatar; @Nullable public CharSequence senderName; @Nullable public CharSequence message; @@ -109,16 +100,39 @@ class Bubble implements BubbleViewProvider { private UserHandle mUser; @NonNull private String mPackageName; + @Nullable + private String mTitle; + @Nullable + private Icon mIcon; + private boolean mIsBubble; + private boolean mIsVisuallyInterruptive; + private boolean mIsClearable; + private boolean mShouldSuppressNotificationDot; + private boolean mShouldSuppressNotificationList; + private boolean mShouldSuppressPeek; private int mDesiredHeight; @DimenRes private int mDesiredHeightResId; + /** for logging **/ + @Nullable + private InstanceId mInstanceId; + @Nullable + private String mChannelId; + private int mNotificationId; + private int mAppUid = -1; + + @Nullable + private PendingIntent mIntent; + @Nullable + private PendingIntent mDeleteIntent; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, - final int desiredHeight, final int desiredHeightResId) { + final int desiredHeight, final int desiredHeightResId, @Nullable final String title) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; @@ -126,8 +140,10 @@ class Bubble implements BubbleViewProvider { mFlags = 0; mUser = shortcutInfo.getUserHandle(); mPackageName = shortcutInfo.getPackage(); + mIcon = shortcutInfo.getIcon(); mDesiredHeight = desiredHeight; mDesiredHeightResId = desiredHeightResId; + mTitle = title; } /** Used in tests when no UI is required. */ @@ -145,12 +161,6 @@ class Bubble implements BubbleViewProvider { return mKey; } - @Nullable - public NotificationEntry getEntry() { - return mEntry; - } - - @NonNull public UserHandle getUser() { return mUser; } @@ -203,14 +213,7 @@ class Bubble implements BubbleViewProvider { @Nullable public String getTitle() { - final CharSequence titleCharSeq; - if (mEntry == null) { - titleCharSeq = null; - } else { - titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( - Notification.EXTRA_TITLE); - } - return titleCharSeq != null ? titleCharSeq.toString() : null; + return mTitle; } /** @@ -331,17 +334,45 @@ class Bubble implements BubbleViewProvider { void setEntry(@NonNull final NotificationEntry entry) { Objects.requireNonNull(entry); Objects.requireNonNull(entry.getSbn()); - mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); - mFlags = entry.getSbn().getNotification().flags; + mIsBubble = entry.getSbn().getNotification().isBubbleNotification(); mPackageName = entry.getSbn().getPackageName(); mUser = entry.getSbn().getUser(); + mTitle = getTitle(entry); + mIsClearable = entry.isClearable(); + mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot(); + mShouldSuppressNotificationList = entry.shouldSuppressNotificationList(); + mShouldSuppressPeek = entry.shouldSuppressPeek(); + mChannelId = entry.getSbn().getNotification().getChannelId(); + mNotificationId = entry.getSbn().getId(); + mAppUid = entry.getSbn().getUid(); + mInstanceId = entry.getSbn().getInstanceId(); + mFlyoutMessage = BubbleViewInfoTask.extractFlyoutMessage(entry); + mShortcutInfo = (entry.getBubbleMetadata() != null + && entry.getBubbleMetadata().getShortcutId() != null + && entry.getRanking() != null) ? entry.getRanking().getShortcutInfo() : null; + if (entry.getRanking() != null) { + mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive(); + } if (entry.getBubbleMetadata() != null) { + mFlags = entry.getBubbleMetadata().getFlags(); mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + mIcon = entry.getBubbleMetadata().getIcon(); + mIntent = entry.getBubbleMetadata().getIntent(); + mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent(); } } + @Nullable + Icon getIcon() { + return mIcon; + } + + boolean isVisuallyInterruptive() { + return mIsVisuallyInterruptive; + } + /** * @return the last time this bubble was updated or accessed, whichever is most recent. */ @@ -364,6 +395,19 @@ class Bubble implements BubbleViewProvider { return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; } + public InstanceId getInstanceId() { + return mInstanceId; + } + + @Nullable + public String getChannelId() { + return mChannelId; + } + + public int getNotificationId() { + return mNotificationId; + } + /** * Should be invoked whenever a Bubble is accessed (selected while expanded). */ @@ -384,24 +428,19 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { - if (mEntry == null) return false; - return !shouldSuppressNotification() || !mEntry.isClearable(); + return !shouldSuppressNotification() || !mIsClearable; } /** * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { - if (mEntry == null) return; boolean prevShowInShade = showInShade(); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - int flags = data.getFlags(); if (suppressNotification) { - flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } else { - flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - data.setFlags(flags); if (showInShade() != prevShowInShade && mSuppressionListener != null) { mSuppressionListener.onBubbleNotificationSuppressionChange(this); @@ -424,9 +463,8 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { - if (mEntry == null) return false; return mShowBubbleUpdateDot - && !mEntry.shouldSuppressNotificationDot() + && !mShouldSuppressNotificationDot && !shouldSuppressNotification(); } @@ -434,10 +472,9 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { - if (mEntry == null) return false; - return !mSuppressFlyout && !mEntry.shouldSuppressPeek() + return !mSuppressFlyout && !mShouldSuppressPeek && !shouldSuppressNotification() - && !mEntry.shouldSuppressNotificationList(); + && !mShouldSuppressNotificationList; } /** @@ -480,25 +517,14 @@ class Bubble implements BubbleViewProvider { } } - /** - * Whether shortcut information should be used to populate the bubble. - * <p> - * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}. - * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. - */ - boolean usingShortcutInfo() { - return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null - || mShortcutInfo != null; + @Nullable + PendingIntent getBubbleIntent() { + return mIntent; } @Nullable - PendingIntent getBubbleIntent() { - if (mEntry == null) return null; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - if (data != null) { - return data.getIntent(); - } - return null; + PendingIntent getDeleteIntent() { + return mDeleteIntent; } Intent getSettingsIntent(final Context context) { @@ -514,8 +540,12 @@ class Bubble implements BubbleViewProvider { return intent; } + public int getAppUid() { + return mAppUid; + } + private int getUid(final Context context) { - if (mEntry != null) return mEntry.getSbn().getUid(); + if (mAppUid != -1) return mAppUid; final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { @@ -548,24 +578,27 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { - if (mEntry == null) return true; - return mEntry.getBubbleMetadata() != null - && mEntry.getBubbleMetadata().isNotificationSuppressed(); + return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); } - boolean shouldAutoExpand() { - if (mEntry == null) return mShouldAutoExpand; - Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); - return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; + public boolean shouldAutoExpand() { + return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } void setShouldAutoExpand(boolean shouldAutoExpand) { - mShouldAutoExpand = shouldAutoExpand; + if (shouldAutoExpand) { + enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } else { + disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + } + } + + public void setIsBubble(final boolean isBubble) { + mIsBubble = isBubble; } public boolean isBubble() { - if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; - return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; + return mIsBubble; } public void enable(int option) { @@ -576,6 +609,10 @@ class Bubble implements BubbleViewProvider { mFlags &= ~option; } + public boolean isEnabled(int option) { + return (mFlags & option) != 0; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; @@ -610,34 +647,24 @@ class Bubble implements BubbleViewProvider { @Override public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { - if (this.getEntry() == null - || this.getEntry().getSbn() == null) { - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - null /* package name */, - null /* notification channel */, - 0 /* notification ID */, - 0 /* bubble position */, - bubbleCount, - action, - normalX, - normalY, - false /* unread bubble */, - false /* on-going bubble */, - false /* isAppForeground (unused) */); - } else { - StatusBarNotification notification = this.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - index, - bubbleCount, - action, - normalX, - normalY, - this.showInShade(), - false /* isOngoing (unused) */, - false /* isAppForeground (unused) */); - } + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + mPackageName, + mChannelId, + mNotificationId, + index, + bubbleCount, + action, + normalX, + normalY, + showInShade(), + false /* isOngoing (unused) */, + false /* isAppForeground (unused) */); + } + + @Nullable + private static String getTitle(@NonNull final NotificationEntry e) { + final CharSequence titleCharSeq = e.getSbn().getNotification().extras.getCharSequence( + Notification.EXTRA_TITLE); + return titleCharSeq == null ? null : titleCharSeq.toString(); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c4c5da42ec06..c42920965ed3 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -67,6 +67,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseSetArray; import android.view.Display; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -394,6 +395,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi : statusBarService; mBubbleScrim = new ScrimView(mContext); + mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mSavedBubbleKeysPerUser = new SparseSetArray<>(); mCurrentUserId = mNotifUserManager.getCurrentUserId(); @@ -505,8 +507,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi addNotifCallback(new NotifCallback() { @Override public void removeNotification(NotificationEntry entry, int reason) { - mNotificationEntryManager.performRemoveNotification(entry.getSbn(), - reason); + mNotificationEntryManager.performRemoveNotification(entry.getSbn(), reason); } @Override @@ -637,8 +638,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mStackView.setExpandListener(mExpandListener); } - mStackView.setUnbubbleConversationCallback(notificationEntry -> - onUserChangedBubble(notificationEntry, false /* shouldBubble */)); + mStackView.setUnbubbleConversationCallback(key -> { + final NotificationEntry entry = + mNotificationEntryManager.getPendingOrActiveNotif(key); + if (entry != null) { + onUserChangedBubble(entry, false /* shouldBubble */); + } + }); } addToWindowManagerMaybe(); @@ -1024,10 +1030,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ - public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { - if (entry == null) { - return; - } + public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) { NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); @@ -1103,7 +1106,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleData.removeSuppressedSummary(groupKey); // Remove any associated bubble children with the summary - final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } @@ -1161,21 +1165,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); - if (isBubble) { - b.enable(FLAG_BUBBLE); - } else { - b.disable(FLAG_BUBBLE); - } - if (b.getEntry() != null) { + b.setIsBubble(isBubble); + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(b.getKey()); + if (entry != null) { // Updating the entry to be a bubble will trigger our normal update flow - setIsBubble(b.getEntry(), isBubble, b.shouldAutoExpand()); + setIsBubble(entry, isBubble, b.shouldAutoExpand()); } else if (isBubble) { - // If we have no entry to update, it's a persisted bubble so - // we need to add it to the stack ourselves + // If bubble doesn't exist, it's a persisted bubble so we need to add it to the + // stack ourselves Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, !bubble.shouldAutoExpand() /* showInShade */); - } } @@ -1214,6 +1215,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (reason == DISMISS_NOTIF_CANCEL) { bubblesToBeRemovedFromRepository.add(bubble); } + final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif( + bubble.getKey()); if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey()) && (!bubble.showInShade() @@ -1222,26 +1225,27 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { - if (bubble.getEntry() != null) { - cb.removeNotification(bubble.getEntry(), REASON_CANCEL); + if (entry != null) { + cb.removeNotification(entry, REASON_CANCEL); } } } else { if (bubble.isBubble()) { setIsBubble(bubble, false /* isBubble */); } - if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { - bubble.getEntry().getRow().updateBubbleButton(); + if (entry != null && entry.getRow() != null) { + entry.getRow().updateBubbleButton(); } } } - if (bubble.getEntry() != null) { - final String groupKey = bubble.getEntry().getSbn().getGroupKey(); - if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { + if (entry != null) { + final String groupKey = entry.getSbn().getGroupKey(); + if (mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager).isEmpty()) { // Time to potentially remove the summary for (NotifCallback cb : mCallbacks) { - cb.maybeCancelSummary(bubble.getEntry()); + cb.maybeCancelSummary(entry); } } } @@ -1266,9 +1270,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged && mStackView != null) { mStackView.setSelectedBubble(update.selectedBubble); - if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { - mNotificationGroupManager.updateSuppression( - update.selectedBubble.getEntry()); + if (update.selectedBubble != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(update.selectedBubble.getKey()); + if (entry != null) { + mNotificationGroupManager.updateSuppression(entry); + } } } @@ -1341,7 +1348,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } String groupKey = entry.getSbn().getGroupKey(); - ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); + ArrayList<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup( + groupKey, mNotificationEntryManager); boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey) && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey())); boolean isSummary = entry.getSbn().getNotification().isGroupSummary(); @@ -1361,9 +1369,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // As far as group manager is concerned, once a child is no longer shown // in the shade, it is essentially removed. Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey()); - mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry()); - bubbleChild.setSuppressNotification(true); - bubbleChild.setShowDot(false /* show */); + if (bubbleChild != null) { + final NotificationEntry entry = mNotificationEntryManager + .getPendingOrActiveNotif(bubbleChild.getKey()); + if (entry != null) { + mNotificationGroupManager.onEntryRemoved(entry); + } + bubbleChild.setSuppressNotification(true); + bubbleChild.setShowDot(false /* show */); + } } else { // non-bubbled children can be removed for (NotifCallback cb : mCallbacks) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 20a9a8cf324c..c8706126c1ad 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -22,7 +22,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; -import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; @@ -34,6 +33,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleController.DismissReason; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; @@ -256,8 +256,7 @@ public class BubbleData { } mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); - suppressFlyout |= bubble.getEntry() == null - || !bubble.getEntry().getRanking().visuallyInterruptive(); + suppressFlyout |= !bubble.isVisuallyInterruptive(); if (prevBubble == null) { // Create a new bubble @@ -335,13 +334,15 @@ public class BubbleData { * Retrieves any bubbles that are part of the notification group represented by the provided * group key. */ - ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) { + ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey, @NonNull + NotificationEntryManager nem) { ArrayList<Bubble> bubbleChildren = new ArrayList<>(); if (groupKey == null) { return bubbleChildren; } for (Bubble b : mBubbles) { - if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { + final NotificationEntry entry = nem.getPendingOrActiveNotif(b.getKey()); + if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) { bubbleChildren.add(b); } } @@ -439,9 +440,7 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } - if (bubbleToRemove.getEntry() != null) { - maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); - } + maybeSendDeleteIntent(reason, bubbleToRemove); } void overflowBubble(@DismissReason int reason, Bubble bubble) { @@ -611,21 +610,14 @@ public class BubbleData { return true; } - private void maybeSendDeleteIntent(@DismissReason int reason, - @NonNull final NotificationEntry entry) { - if (reason == BubbleController.DISMISS_USER_GESTURE) { - Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); - PendingIntent deleteIntent = bubbleMetadata != null - ? bubbleMetadata.getDeleteIntent() - : null; - if (deleteIntent != null) { - try { - deleteIntent.send(); - } catch (PendingIntent.CanceledException e) { - Log.w(TAG, "Failed to send delete intent for bubble with key: " - + entry.getKey()); - } - } + private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) { + if (reason != BubbleController.DISMISS_USER_GESTURE) return; + PendingIntent deleteIntent = bubble.getDeleteIntent(); + if (deleteIntent == null) return; + try { + deleteIntent.send(); + } catch (PendingIntent.CanceledException e) { + Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey()); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index d20f40559b5d..0c25d144938c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -74,11 +74,15 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> - var shortcutId = b.shortcutInfo?.id - if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId - if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, - b.rawDesiredHeightResId) + BubbleEntity( + userId, + b.packageName, + b.shortcutInfo?.id ?: return@mapNotNull null, + b.key, + b.rawDesiredHeight, + b.rawDesiredHeightResId, + b.title + ) } } @@ -159,8 +163,13 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, - entity.desiredHeightResId) } + ?.let { shortcutInfo -> Bubble( + entity.key, + shortcutInfo, + entity.desiredHeight, + entity.desiredHeightResId, + entity.title + ) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index bc03bf981914..959130bbdd0f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -171,7 +171,7 @@ public class BubbleExpandedView extends LinearLayout { return; } try { - if (!mIsOverflow && mBubble.usingShortcutInfo()) { + if (!mIsOverflow && mBubble.getShortcutInfo() != null) { options.setApplyActivityFlagsForBubbles(true); mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), options, null /* sourceBounds */); @@ -668,7 +668,7 @@ public class BubbleExpandedView extends LinearLayout { desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); } float height = Math.min(desiredHeight, getMaxExpandedHeight()); - height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight); + height = Math.max(height, mMinHeight); ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); mNeedsNewHeight = lp.height != height; if (!mKeyboardVisible) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java index 8c76cda3290f..1fa3aaae5e61 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java @@ -31,6 +31,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PointF; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.text.TextUtils; import android.view.LayoutInflater; @@ -223,9 +224,10 @@ public class BubbleFlyoutView extends FrameLayout { float[] dotCenter, boolean hideDot) { - if (flyoutMessage.senderAvatar != null && flyoutMessage.isGroupChat) { + final Drawable senderAvatar = flyoutMessage.senderAvatar; + if (senderAvatar != null && flyoutMessage.isGroupChat) { mSenderAvatar.setVisibility(VISIBLE); - mSenderAvatar.setImageDrawable(flyoutMessage.senderAvatar); + mSenderAvatar.setImageDrawable(senderAvatar); } else { mSenderAvatar.setVisibility(GONE); mSenderAvatar.setTranslationX(0); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java index 74231c648f00..a799f2d739e5 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java @@ -15,7 +15,8 @@ */ package com.android.systemui.bubbles; -import android.app.Notification; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; @@ -50,15 +51,14 @@ public class BubbleIconFactory extends BaseIconFactory { /** * Returns the drawable that the developer has provided to display in the bubble. */ - Drawable getBubbleDrawable(Context context, ShortcutInfo shortcutInfo, - Notification.BubbleMetadata metadata) { + Drawable getBubbleDrawable(@NonNull final Context context, + @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) { if (shortcutInfo != null) { LauncherApps launcherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); int density = context.getResources().getConfiguration().densityDpi; return launcherApps.getShortcutIconDrawable(shortcutInfo, density); } else { - Icon ic = metadata.getIcon(); if (ic != null) { if (ic.getType() == Icon.TYPE_URI || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java index c5faae0d703e..c1dd8c36ff6f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLoggerImpl.java @@ -16,8 +16,6 @@ package com.android.systemui.bubbles; -import android.service.notification.StatusBarNotification; - import com.android.internal.logging.UiEventLoggerImpl; /** @@ -32,12 +30,11 @@ public class BubbleLoggerImpl extends UiEventLoggerImpl implements BubbleLogger * @param e UI event */ public void log(Bubble b, UiEventEnum e) { - if (b.getEntry() == null) { + if (b.getInstanceId() == null) { // Added from persistence -- TODO log this with specific event? return; } - StatusBarNotification sbn = b.getEntry().getSbn(); - logWithInstanceId(e, sbn.getUid(), sbn.getPackageName(), sbn.getInstanceId()); + logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId()); } /** diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index b644079be565..b9437078a330 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -27,7 +27,6 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; @@ -291,9 +290,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V }); // If the bubble was persisted, the entry is null but it should have shortcut info - ShortcutInfo info = b.getEntry() == null - ? b.getShortcutInfo() - : b.getEntry().getRanking().getShortcutInfo(); + ShortcutInfo info = b.getShortcutInfo(); if (info == null) { Log.d(TAG, "ShortcutInfo required to bubble but none found for " + b); } else { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 26ed1d121acb..297d92e3135f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -92,7 +92,6 @@ import com.android.systemui.bubbles.animation.StackAnimationController; import com.android.systemui.model.SysUiState; import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; import com.android.systemui.util.DismissCircleView; import com.android.systemui.util.FloatingContentCoordinator; @@ -303,7 +302,7 @@ public class BubbleStackView extends FrameLayout private BubbleController.BubbleExpandListener mExpandListener; /** Callback to run when we want to unbubble the given notification's conversation. */ - private Consumer<NotificationEntry> mUnbubbleConversationCallback; + private Consumer<String> mUnbubbleConversationCallback; private SysUiState mSysUiState; @@ -890,14 +889,7 @@ public class BubbleStackView extends FrameLayout (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { mExpandedAnimationController.updateResources(mOrientation, mDisplaySize); mStackAnimationController.updateResources(mOrientation); - - // Reposition & adjust the height for new orientation - if (mIsExpanded) { - mExpandedViewContainer.setTranslationY(getExpandedViewY()); - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateView(getLocationOnScreen()); - } - } + mBubbleOverflow.updateDimensions(); // Need to update the padding around the view WindowInsets insets = getRootWindowInsets(); @@ -921,9 +913,15 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { // Re-draw bubble row and pointer for new orientation. + beforeExpandedViewAnimation(); + updateOverflowVisibility(); + updatePointerPosition(); mExpandedAnimationController.expandFromStack(() -> { - updatePointerPosition(); + afterExpandedViewAnimation(); } /* after */); + mExpandedViewContainer.setTranslationX(0); + mExpandedViewContainer.setTranslationY(getExpandedViewY()); + mExpandedViewContainer.setAlpha(1f); } if (mVerticalPosPercentBeforeRotation >= 0) { mStackAnimationController.moveStackToSimilarPositionAfterRotation( @@ -1057,10 +1055,7 @@ public class BubbleStackView extends FrameLayout mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener( view -> { showManageMenu(false /* show */); - final Bubble bubble = mBubbleData.getSelectedBubble(); - if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { - mUnbubbleConversationCallback.accept(bubble.getEntry()); - } + mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener( @@ -1412,7 +1407,7 @@ public class BubbleStackView extends FrameLayout /** Sets the function to call to un-bubble the given conversation. */ public void setUnbubbleConversationCallback( - Consumer<NotificationEntry> unbubbleConversationCallback) { + Consumer<String> unbubbleConversationCallback) { mUnbubbleConversationCallback = unbubbleConversationCallback; } @@ -2515,6 +2510,10 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.SCALE_Y, 1f) .spring(DynamicAnimation.TRANSLATION_X, targetX) .spring(DynamicAnimation.TRANSLATION_Y, targetY) + .withEndActions(() -> { + View child = mManageMenu.getChildAt(0); + child.requestAccessibilityFocus(); + }) .start(); mManageMenu.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index 525d5b56cc8e..3e4ff5262bbd 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -37,8 +37,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Parcelable; -import android.os.UserHandle; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.PathParser; @@ -53,6 +51,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; /** * Simple task to inflate views & load necessary info to display a bubble. @@ -129,35 +128,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @Nullable static BubbleViewInfo populate(Context c, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) { - final NotificationEntry entry = b.getEntry(); - if (entry == null) { - // populate from ShortcutInfo when NotificationEntry is not available - final ShortcutInfo s = b.getShortcutInfo(); - return populate(c, stackView, iconFactory, skipInflation || b.isInflated(), - s.getPackage(), s.getUserHandle(), s, null); - } - final StatusBarNotification sbn = entry.getSbn(); - final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId(); - final ShortcutInfo si = bubbleShortcutId == null - ? null : entry.getRanking().getShortcutInfo(); - return populate( - c, stackView, iconFactory, skipInflation || b.isInflated(), - sbn.getPackageName(), sbn.getUser(), si, entry); - } - - private static BubbleViewInfo populate( - @NonNull final Context c, - @NonNull final BubbleStackView stackView, - @NonNull final BubbleIconFactory iconFactory, - final boolean isInflated, - @NonNull final String packageName, - @NonNull final UserHandle user, - @Nullable final ShortcutInfo shortcutInfo, - @Nullable final NotificationEntry entry) { BubbleViewInfo info = new BubbleViewInfo(); // View inflation: only should do this once per bubble - if (!isInflated) { + if (!skipInflation && !b.isInflated()) { LayoutInflater inflater = LayoutInflater.from(c); info.imageView = (BadgedImageView) inflater.inflate( R.layout.bubble_view, stackView, false /* attachToRoot */); @@ -167,8 +141,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask info.expandedView.setStackView(stackView); } - if (shortcutInfo != null) { - info.shortcutInfo = shortcutInfo; + if (b.getShortcutInfo() != null) { + info.shortcutInfo = b.getShortcutInfo(); } // App name & app icon @@ -178,7 +152,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Drawable appIcon; try { appInfo = pm.getApplicationInfo( - packageName, + b.getPackageName(), PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -186,17 +160,17 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (appInfo != null) { info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); } - appIcon = pm.getApplicationIcon(packageName); - badgedIcon = pm.getUserBadgedIcon(appIcon, user); + appIcon = pm.getApplicationIcon(b.getPackageName()); + badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser()); } catch (PackageManager.NameNotFoundException exception) { // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find package: " + packageName); + Log.w(TAG, "Unable to find package: " + b.getPackageName()); return null; } // Badged bubble image Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, - entry == null ? null : entry.getBubbleMetadata()); + b.getIcon()); if (bubbleDrawable == null) { // Default to app icon bubbleDrawable = appIcon; @@ -222,8 +196,10 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask Color.WHITE, WHITE_SCRIM_ALPHA); // Flyout - if (entry != null) { - info.flyoutMessage = extractFlyoutMessage(c, entry); + info.flyoutMessage = b.getFlyoutMessage(); + if (info.flyoutMessage != null) { + info.flyoutMessage.senderAvatar = + loadSenderAvatar(c, info.flyoutMessage.senderIcon); } return info; } @@ -235,8 +211,8 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask * notification, based on its type. Returns null if there should not be an update message. */ @NonNull - static Bubble.FlyoutMessage extractFlyoutMessage(Context context, - NotificationEntry entry) { + static Bubble.FlyoutMessage extractFlyoutMessage(NotificationEntry entry) { + Objects.requireNonNull(entry); final Notification underlyingNotif = entry.getSbn().getNotification(); final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); @@ -264,20 +240,9 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask if (latestMessage != null) { bubbleMessage.message = latestMessage.getText(); Person sender = latestMessage.getSenderPerson(); - bubbleMessage.senderName = sender != null - ? sender.getName() - : null; - + bubbleMessage.senderName = sender != null ? sender.getName() : null; bubbleMessage.senderAvatar = null; - if (sender != null && sender.getIcon() != null) { - if (sender.getIcon().getType() == Icon.TYPE_URI - || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { - context.grantUriPermission(context.getPackageName(), - sender.getIcon().getUri(), - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context); - } + bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null; return bubbleMessage; } } else if (Notification.InboxStyle.class.equals(style)) { @@ -306,4 +271,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask return bubbleMessage; } + + @Nullable + static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) { + Objects.requireNonNull(context); + if (icon == null) return null; + if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { + context.grantUriPermission(context.getPackageName(), + icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + return icon.loadDrawable(context); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java index cb8995a72dc3..8e232520a196 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java @@ -203,12 +203,22 @@ public class ExpandedAnimationController public void updateResources(int orientation, Point displaySize) { mScreenOrientation = orientation; mDisplaySize = displaySize; - if (mLayout != null) { - Resources res = mLayout.getContext().getResources(); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mStatusBarHeight = res.getDimensionPixelSize( - com.android.internal.R.dimen.status_bar_height); + if (mLayout == null) { + return; } + Resources res = mLayout.getContext().getResources(); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mStatusBarHeight = res.getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); + mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); + mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); + + // Includes overflow button. + float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) + - (mBubblesMaxRendered + 1) * mBubbleSizePx; + mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; } /** @@ -464,18 +474,7 @@ public class ExpandedAnimationController @Override void onActiveControllerForLayout(PhysicsAnimationLayout layout) { - final Resources res = layout.getResources(); - mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); - mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); - mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size); - mStatusBarHeight = - res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); - mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered); - - // Includes overflow button. - float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2) - - (mBubblesMaxRendered + 1) * mBubbleSizePx; - mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered; + updateResources(mScreenOrientation, mDisplaySize); // Ensure that all child views are at 1x scale, and visible, in case they were animating // in. diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 355c4b115c8d..24768cd84a76 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -24,5 +24,6 @@ data class BubbleEntity( val shortcutId: String, val key: String, val desiredHeight: Int, - @DimenRes val desiredHeightResId: Int + @DimenRes val desiredHeightResId: Int, + val title: String? = null ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index a8faf258da07..66fff3386ae1 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -33,6 +33,7 @@ private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" private const val ATTR_DESIRED_HEIGHT = "h" private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" +private const val ATTR_TITLE = "t" /** * Writes the bubbles in xml format into given output stream. @@ -63,6 +64,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_KEY, bubble.key) serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) + bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -92,7 +94,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, parser.getAttributeWithName(ATTR_KEY) ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, - parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_TITLE) ) } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 915092134cc5..b520717ee27f 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT; import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING; @@ -395,11 +396,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, controlsComponent.getControlsListingController().get() .addCallback(list -> { mControlsServiceInfos = list; - // This callback may occur after the dialog has been shown. - // If so, add controls into the already visible space - if (mDialog != null && !mDialog.isShowingControls() - && shouldShowControls()) { - mDialog.showControls(mControlsUiControllerOptional.get()); + // This callback may occur after the dialog has been shown. If so, add + // controls into the already visible space or show the lock msg if needed. + if (mDialog != null) { + if (!mDialog.isShowingControls() && shouldShowControls()) { + mDialog.showControls(mControlsUiControllerOptional.get()); + } else if (shouldShowLockMessage()) { + mDialog.showLockMessage(); + } } }); } @@ -700,9 +704,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mStatusBarService, mNotificationShadeWindowController, controlsAvailable(), uiController, mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter); - boolean walletViewAvailable = walletViewController != null - && walletViewController.getPanelContent() != null; - if (shouldShowLockMessage(walletViewAvailable)) { + + if (shouldShowLockMessage()) { dialog.showLockMessage(); } dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. @@ -2591,7 +2594,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean shouldShowControls() { return (mKeyguardStateController.isUnlocked() || mShowLockScreenCardsAndControls) - && controlsAvailable(); + && controlsAvailable() + && mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) + != STRONG_AUTH_REQUIRED_AFTER_BOOT; } private boolean controlsAvailable() { @@ -2601,10 +2606,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, && !mControlsServiceInfos.isEmpty(); } - private boolean shouldShowLockMessage(boolean walletViewAvailable) { + private boolean walletViewAvailable() { + GlobalActionsPanelPlugin.PanelViewController walletViewController = + getWalletViewController(); + return walletViewController != null && walletViewController.getPanelContent() != null; + } + + private boolean shouldShowLockMessage() { + boolean isLockedAfterBoot = mLockPatternUtils.getStrongAuthForUser(getCurrentUser().id) + == STRONG_AUTH_REQUIRED_AFTER_BOOT; return !mKeyguardStateController.isUnlocked() - && !mShowLockScreenCardsAndControls - && (controlsAvailable() || walletViewAvailable); + && (!mShowLockScreenCardsAndControls || isLockedAfterBoot) + && (controlsAvailable() || walletViewAvailable()); } private void onPowerMenuLockScreenSettingsChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 9b9a6b4b13ab..bccc3abd8a27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -13,6 +13,7 @@ import androidx.core.view.GestureDetectorCompat import com.android.systemui.R import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring import javax.inject.Inject @@ -31,7 +32,8 @@ class MediaViewManager @Inject constructor( private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, private val mediaHostStatesManager: MediaHostStatesManager, - mediaManager: MediaDataCombineLatest + mediaManager: MediaDataCombineLatest, + configurationController: ConfigurationController ) { /** @@ -74,6 +76,7 @@ class MediaViewManager @Inject constructor( private val mediaCarousel: HorizontalScrollView val mediaFrame: ViewGroup val mediaPlayers: MutableMap<String, MediaControlPanel> = mutableMapOf() + private val mediaData: MutableMap<String, MediaData> = mutableMapOf() private val mediaContent: ViewGroup private val pageIndicator: PageIndicator private val gestureDetector: GestureDetectorCompat @@ -120,6 +123,11 @@ class MediaViewManager @Inject constructor( return this@MediaViewManager.onTouch(view, motionEvent) } } + private val configListener = object : ConfigurationController.ConfigurationListener { + override fun onDensityOrFontScaleChanged() { + recreatePlayers() + } + } init { gestureDetector = GestureDetectorCompat(context, gestureListener) @@ -130,6 +138,7 @@ class MediaViewManager @Inject constructor( mediaCarousel.setOnTouchListener(touchListener) mediaCarousel.setOverScrollMode(View.OVER_SCROLL_NEVER) mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) + configurationController.addCallback(configListener) visualStabilityCallback = VisualStabilityManager.Callback { if (needsReordering) { needsReordering = false @@ -142,29 +151,14 @@ class MediaViewManager @Inject constructor( true /* persistent */) mediaManager.addListener(object : MediaDataManager.Listener { override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) { - updateView(key, oldKey, data) - updatePlayerVisibilities() - mediaCarousel.requiresRemeasuring = true + oldKey?.let { mediaData.remove(it) } + mediaData.put(key, data) + addOrUpdatePlayer(key, oldKey, data) } override fun onMediaDataRemoved(key: String) { - val removed = mediaPlayers.remove(key) - removed?.apply { - val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= - activeMediaIndex - mediaContent.removeView(removed.view?.player) - removed.onDestroy() - updateMediaPaddings() - if (beforeActive) { - // also update the index here since the scroll below might not always lead - // to a scrolling changed - activeMediaIndex = Math.max(0, activeMediaIndex - 1) - mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - - playerWidthPlusPadding, 0) - } - updatePlayerVisibilities() - updatePageIndicator() - } + mediaData.remove(key) + removePlayer(key) } }) mediaHostStatesManager.addCallback(object : MediaHostStatesManager.Callback { @@ -253,7 +247,7 @@ class MediaViewManager @Inject constructor( } } - private fun updateView(key: String, oldKey: String?, data: MediaData) { + private fun addOrUpdatePlayer(key: String, oldKey: String?, data: MediaData) { // If the key was changed, update entry val oldData = mediaPlayers[oldKey] if (oldData != null) { @@ -288,6 +282,39 @@ class MediaViewManager @Inject constructor( existingPlayer?.bind(data) updateMediaPaddings() updatePageIndicator() + updatePlayerVisibilities() + mediaCarousel.requiresRemeasuring = true + } + + private fun removePlayer(key: String) { + val removed = mediaPlayers.remove(key) + removed?.apply { + val beforeActive = mediaContent.indexOfChild(removed.view?.player) <= + activeMediaIndex + mediaContent.removeView(removed.view?.player) + removed.onDestroy() + updateMediaPaddings() + if (beforeActive) { + // also update the index here since the scroll below might not always lead + // to a scrolling changed + activeMediaIndex = Math.max(0, activeMediaIndex - 1) + mediaCarousel.scrollX = Math.max(mediaCarousel.scrollX - + playerWidthPlusPadding, 0) + } + updatePlayerVisibilities() + updatePageIndicator() + } + } + + private fun recreatePlayers() { + // Note that this will scramble the order of players. Actively playing sessions will, at + // least, still be put in the front. If we want to maintain order, then more work is + // needed. + mediaData.forEach { + key, data -> + removePlayer(key) + addOrUpdatePlayer(key = key, oldKey = null, data = data) + } } private fun updateMediaPaddings() { diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java index b93e07e65c73..9daa876038d5 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java +++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java @@ -708,6 +708,12 @@ public class PipTaskOrganizer extends TaskOrganizer implements Log.w(TAG, "Abort animation, invalid leash"); return; } + + if (startBounds.isEmpty() || destinationBounds.isEmpty()) { + Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting."); + return; + } + final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction(); mSurfaceTransactionHelper.scale(tx, mLeash, startBounds, destinationBounds); tx.apply(); diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java index 856c19290af6..06c98d00cca7 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Context; +import android.graphics.PointF; import android.graphics.Rect; import android.os.Debug; import android.util.Log; @@ -38,6 +39,9 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; import java.util.function.Consumer; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + /** * A helper to animate and manipulate the PiP. */ @@ -74,9 +78,15 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, new SfVsyncFrameCallbackProvider(); /** - * Bounds that are animated using the physics animator. + * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP + * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into + * and expanding out of the magnetic dismiss target. + * + * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary + * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to + * its new bounds. */ - private final Rect mAnimatedBounds = new Rect(); + private final Rect mTemporaryBounds = new Rect(); /** The destination bounds to which PIP is animating. */ private final Rect mAnimatingToBounds = new Rect(); @@ -85,20 +95,20 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private FloatingContentCoordinator mFloatingContentCoordinator; /** Callback that re-sizes PIP to the animated bounds. */ - private final Choreographer.FrameCallback mResizePipVsyncCallback = - l -> resizePipUnchecked(mAnimatedBounds); + private final Choreographer.FrameCallback mResizePipVsyncCallback; /** - * PhysicsAnimator instance for animating {@link #mAnimatedBounds} using physics animations. + * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations. */ - private PhysicsAnimator<Rect> mAnimatedBoundsPhysicsAnimator = PhysicsAnimator.getInstance( - mAnimatedBounds); + private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( + mTemporaryBounds); + + private MagnetizedObject<Rect> mMagnetizedPip; /** - * Update listener that resizes the PIP to {@link #mAnimatedBounds}. + * Update listener that resizes the PIP to {@link #mTemporaryBounds}. */ - final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener = - (target, values) -> mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); + private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener; /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ private PhysicsAnimator.FlingConfig mFlingConfigX; @@ -124,6 +134,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, private boolean mSpringingToTouch = false; /** + * Whether PIP was released in the dismiss target, and will be animated out and dismissed + * shortly. + */ + private boolean mDismissalPending = false; + + /** * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is * used to show menu activity when the expand animation is completed. */ @@ -155,6 +171,16 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, mSnapAlgorithm = snapAlgorithm; mFloatingContentCoordinator = floatingContentCoordinator; mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); + + mResizePipVsyncCallback = l -> { + if (!mTemporaryBounds.isEmpty()) { + mPipTaskOrganizer.scheduleUserResizePip( + mBounds, mTemporaryBounds, null); + } + }; + + mResizePipUpdateListener = (target, values) -> + mSfVsyncFrameProvider.postFrameCallback(mResizePipVsyncCallback); } @NonNull @@ -186,19 +212,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } } - /** - * Synchronizes the current bounds with either the pinned stack, or the ongoing animation. This - * is done to prepare for a touch gesture. - */ - void synchronizePinnedStackBoundsForTouchGesture() { - if (mAnimatingToBounds.isEmpty()) { - // If we're not animating anywhere, sync normally. - synchronizePinnedStackBounds(); - } else { - // If we're animating, set the current bounds to the animated bounds. That way, the - // touch gesture will begin at the most recent animated location of the bounds. - mBounds.set(mAnimatedBounds); - } + boolean isAnimating() { + return mTemporaryBoundsPhysicsAnimator.isRunning(); } /** @@ -224,32 +239,54 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // If we are moving PIP directly to the touch event locations, cancel any animations and // move PIP to the given bounds. cancelAnimations(); - resizePipUnchecked(toBounds); - mBounds.set(toBounds); + + if (!isDragging) { + resizePipUnchecked(toBounds); + mBounds.set(toBounds); + } else { + mTemporaryBounds.set(toBounds); + mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null); + } } else { // If PIP is 'catching up' after being stuck in the dismiss target, update the animation // to spring towards the new touch location. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) - .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig) - .withEndActions(() -> mSpringingToTouch = false); + .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */, false /* dismiss */); } } - /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ - void setSpringingToTouch(boolean springingToTouch) { - if (springingToTouch) { - mAnimatedBounds.set(mBounds); - } + /** Animates the PIP into the dismiss target, scaling it down. */ + void animateIntoDismissTarget( + MagnetizedObject.MagneticTarget target, + float velX, float velY, + boolean flung, Function0<Unit> after) { + final PointF targetCenter = target.getCenterOnScreen(); - mSpringingToTouch = springingToTouch; + final float desiredWidth = mBounds.width() / 2; + final float desiredHeight = mBounds.height() / 2; + + final float destinationX = targetCenter.x - (desiredWidth / 2f); + final float destinationY = targetCenter.y - (desiredHeight / 2f); + + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig) + .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig) + .spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig) + .withEndActions(after); + + startBoundsAnimator(destinationX, destinationY, false); } - void prepareForAnimation() { - mAnimatedBounds.set(mBounds); + /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ + void setSpringingToTouch(boolean springingToTouch) { + mSpringingToTouch = springingToTouch; } /** @@ -309,13 +346,22 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, } /** + * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds + * otherwise. + */ + Rect getPossiblyAnimatingBounds() { + return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds; + } + + /** * Flings the PiP to the closest snap target. */ void flingToSnapTarget( float velocityX, float velocityY, @Nullable Runnable updateAction, @Nullable Runnable endAction) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator + .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) + .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) .flingThenSpring( FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, true /* flingMustReachMinOrMax */) @@ -324,13 +370,14 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, .withEndActions(endAction); if (updateAction != null) { - mAnimatedBoundsPhysicsAnimator.addUpdateListener( + mTemporaryBoundsPhysicsAnimator.addUpdateListener( (target, values) -> updateAction.run()); } final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; final float estimatedFlingYEndValue = - PhysicsAnimator.estimateFlingEndValue(mBounds.top, velocityY, mFlingConfigY); + PhysicsAnimator.estimateFlingEndValue( + mTemporaryBounds.top, velocityY, mFlingConfigY); startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */, false /* dismiss */); @@ -341,8 +388,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * configuration */ void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { - mAnimatedBounds.set(mBounds); - mAnimatedBoundsPhysicsAnimator + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + // Animate from the current bounds if we're not already animating. + mTemporaryBounds.set(mBounds); + } + + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_X, bounds.left, springConfig) .spring(FloatProperties.RECT_Y, bounds.top, springConfig); startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */, @@ -353,18 +404,19 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Animates the dismissal of the PiP off the edge of the screen. */ void animateDismiss() { - mAnimatedBounds.set(mBounds); - // Animate off the bottom of the screen, then dismiss PIP. - mAnimatedBoundsPhysicsAnimator + mTemporaryBoundsPhysicsAnimator .spring(FloatProperties.RECT_Y, - mBounds.bottom + mBounds.height(), + mMovementBounds.bottom + mBounds.height() * 2, 0, mSpringConfig) .withEndActions(this::dismissPip); - startBoundsAnimator(mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, + startBoundsAnimator( + mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, true /* dismiss */); + + mDismissalPending = false; } /** @@ -415,7 +467,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * Cancels all existing animations. */ private void cancelAnimations() { - mAnimatedBoundsPhysicsAnimator.cancel(); + mTemporaryBoundsPhysicsAnimator.cancel(); mAnimatingToBounds.setEmpty(); mSpringingToTouch = false; } @@ -449,15 +501,36 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, (int) toY + mBounds.height()); setAnimatingToBounds(mAnimatingToBounds); - mAnimatedBoundsPhysicsAnimator - .withEndActions(() -> { - if (!dismiss) { - mPipTaskOrganizer.scheduleFinishResizePip(mAnimatedBounds); - } - mAnimatingToBounds.setEmpty(); - }) - .addUpdateListener(mResizePipUpdateListener) - .start(); + if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { + mTemporaryBoundsPhysicsAnimator + .addUpdateListener(mResizePipUpdateListener) + .withEndActions(this::onBoundsAnimationEnd); + } + + mTemporaryBoundsPhysicsAnimator.start(); + } + + /** + * Notify that PIP was released in the dismiss target and will be animated out and dismissed + * shortly. + */ + void notifyDismissalPending() { + mDismissalPending = true; + } + + private void onBoundsAnimationEnd() { + if (!mDismissalPending + && !mSpringingToTouch + && !mMagnetizedPip.getObjectStuckToTarget()) { + mBounds.set(mTemporaryBounds); + mPipTaskOrganizer.scheduleFinishResizePip(mBounds); + + mTemporaryBounds.setEmpty(); + } + + mAnimatingToBounds.setEmpty(); + mSpringingToTouch = false; + mDismissalPending = false; } /** @@ -503,25 +576,29 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, * magnetic dismiss target so it can calculate PIP's size and position. */ MagnetizedObject<Rect> getMagnetizedPip() { - return new MagnetizedObject<Rect>( - mContext, mAnimatedBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { - @Override - public float getWidth(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.width(); - } - - @Override - public float getHeight(@NonNull Rect animatedPipBounds) { - return animatedPipBounds.height(); - } + if (mMagnetizedPip == null) { + mMagnetizedPip = new MagnetizedObject<Rect>( + mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { + @Override + public float getWidth(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.width(); + } + + @Override + public float getHeight(@NonNull Rect animatedPipBounds) { + return animatedPipBounds.height(); + } + + @Override + public void getLocationOnScreen( + @NonNull Rect animatedPipBounds, @NonNull int[] loc) { + loc[0] = animatedPipBounds.left; + loc[1] = animatedPipBounds.top; + } + }; + } - @Override - public void getLocationOnScreen( - @NonNull Rect animatedPipBounds, @NonNull int[] loc) { - loc[0] = animatedPipBounds.left; - loc[1] = animatedPipBounds.top; - } - }; + return mMagnetizedPip; } public void dump(PrintWriter pw, String prefix) { 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 3cc9127068bf..2f9b29d13744 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -70,6 +70,8 @@ import com.android.systemui.util.magnetictarget.MagnetizedObject; import java.io.PrintWriter; +import kotlin.Unit; + /** * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. @@ -262,12 +264,14 @@ public class PipTouchHandler { mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0); updateMagneticTargetSize(); - mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener); + mMagnetizedPip.setAnimateStuckToTarget( + (target, velX, velY, flung, after) -> { + mMotionHelper.animateIntoDismissTarget(target, velX, velY, flung, after); + return Unit.INSTANCE; + }); mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) { - mMotionHelper.prepareForAnimation(); - // Show the dismiss target, in case the initial touch event occurred within the // magnetic field radius. showDismissTargetMaybe(); @@ -286,12 +290,13 @@ public class PipTouchHandler { @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { + mMotionHelper.notifyDismissalPending(); + mHandler.post(() -> { mMotionHelper.animateDismiss(); hideDismissTarget(); }); - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, PipUtils.getTopPipActivity(mContext, mActivityManager)); } @@ -617,11 +622,16 @@ public class PipTouchHandler { } MotionEvent ev = (MotionEvent) inputEvent; - - if (mPipResizeGestureHandler.isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY())) { + if (!mTouchState.isDragging() + && !mMagnetizedPip.getObjectStuckToTarget() + && !mMotionHelper.isAnimating() + && mPipResizeGestureHandler.isWithinTouchRegion( + (int) ev.getRawX(), (int) ev.getRawY())) { return true; } - if (mMagnetizedPip.maybeConsumeMotionEvent(ev)) { + + if ((ev.getAction() == MotionEvent.ACTION_DOWN || mTouchState.isUserInteracting()) + && mMagnetizedPip.maybeConsumeMotionEvent(ev)) { // If the first touch event occurs within the magnetic field, pass the ACTION_DOWN event // to the touch state. Touch state needs a DOWN event in order to later process MOVE // events it'll receive if the object is dragged out of the magnetic field. @@ -643,7 +653,6 @@ public class PipTouchHandler { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: { - mMotionHelper.synchronizePinnedStackBoundsForTouchGesture(); mGesture.onDown(mTouchState); break; } @@ -688,11 +697,11 @@ public class PipTouchHandler { break; } case MotionEvent.ACTION_HOVER_EXIT: { - mHideMenuAfterShown = true; // If Touch Exploration is enabled, some a11y services (e.g. Talkback) is probably // on and changing MotionEvents into HoverEvents. // Let's not enable menu show/hide for a11y services. if (!mAccessibilityManager.isTouchExplorationEnabled()) { + mHideMenuAfterShown = true; mMenuController.hideMenu(); } if (!shouldDeliverToMenu && mSendingHoverAccessibilityEvents) { @@ -872,7 +881,7 @@ public class PipTouchHandler { return; } - Rect bounds = mMotionHelper.getBounds(); + Rect bounds = mMotionHelper.getPossiblyAnimatingBounds(); mDelta.set(0f, 0f); mStartPosition.set(bounds.left, bounds.top); mMovementWithinDismiss = touchState.getDownTouchPosition().y >= mMovementBounds.bottom; @@ -914,7 +923,7 @@ public class PipTouchHandler { mDelta.x += left - lastX; mDelta.y += top - lastY; - mTmpBounds.set(mMotionHelper.getBounds()); + mTmpBounds.set(mMotionHelper.getPossiblyAnimatingBounds()); mTmpBounds.offsetTo((int) left, (int) top); mMotionHelper.movePip(mTmpBounds, true /* isDragging */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java index 5ee4693a32bf..e0532c3e2b28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java @@ -17,9 +17,11 @@ package com.android.systemui.statusbar.notification; import android.content.res.Resources; +import android.text.Layout; import android.util.Pools; import android.view.View; import android.view.ViewGroup; +import android.widget.TextView; import com.android.internal.widget.IMessagingLayout; import com.android.internal.widget.MessagingGroup; @@ -229,6 +231,15 @@ public class MessagingLayoutTransformState extends TransformState { return result; } + private boolean hasEllipses(TextView textView) { + Layout layout = textView.getLayout(); + return layout != null && layout.getEllipsisCount(layout.getLineCount() - 1) > 0; + } + + private boolean needsReflow(TextView own, TextView other) { + return hasEllipses(own) != hasEllipses(other); + } + /** * Transform two groups towards each other. * @@ -238,13 +249,20 @@ public class MessagingLayoutTransformState extends TransformState { float transformationAmount, boolean to) { boolean useLinearTransformation = otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating(); - transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(), - true /* sameAsAny */, useLinearTransformation); + TextView ownSenderView = ownGroup.getSenderView(); + TextView otherSenderView = otherGroup.getSenderView(); + transformView(transformationAmount, to, ownSenderView, otherSenderView, + // Normally this would be handled by the TextViewMessageState#sameAs check, but in + // this case it doesn't work because our text won't match, due to the appended colon + // in the collapsed view. + !needsReflow(ownSenderView, otherSenderView), + useLinearTransformation); int totalAvatarTranslation = transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(), true /* sameAsAny */, useLinearTransformation); List<MessagingMessage> ownMessages = ownGroup.getMessages(); List<MessagingMessage> otherMessages = otherGroup.getMessages(); float previousTranslation = 0; + boolean isLastView = true; for (int i = 0; i < ownMessages.size(); i++) { View child = ownMessages.get(ownMessages.size() - 1 - i).getView(); if (isGone(child)) { @@ -278,6 +296,9 @@ public class MessagingLayoutTransformState extends TransformState { mMessagingLayout.setMessagingClippingDisabled(true); } if (otherChild == null) { + if (isLastView) { + previousTranslation = ownSenderView.getTranslationY(); + } child.setTranslationY(previousTranslation); setClippingDeactivated(child, true); } else if (ownGroup.getIsolatedMessage() == child || otherIsIsolated) { @@ -287,6 +308,7 @@ public class MessagingLayoutTransformState extends TransformState { } else { previousTranslation = child.getTranslationY(); } + isLastView = false; } ownGroup.updateClipRect(); return totalAvatarTranslation; @@ -382,6 +404,9 @@ public class MessagingLayoutTransformState extends TransformState { if (view.getParent() == null) { return true; } + if (view.getWidth() == 0) { + return true; + } final ViewGroup.LayoutParams lp = view.getLayoutParams(); if (lp instanceof MessagingLinearLayout.LayoutParams && ((MessagingLinearLayout.LayoutParams) lp).hide) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java index 978394ca05ab..cad1c91975bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -17,7 +17,11 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.INVALID_DISPLAY; +import android.app.ActivityManager; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.PixelFormat; @@ -75,6 +79,8 @@ import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto; import com.android.systemui.tracing.nano.SystemUiTraceProto; import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; /** @@ -121,9 +127,18 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa } }; + private TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { + @Override + public void onTaskStackChanged() { + mGestureBlockingActivityRunning = isGestureBlockingActivityRunning(); + } + }; + private final Context mContext; private final OverviewProxyService mOverviewProxyService; - private PluginManager mPluginManager; + private final PluginManager mPluginManager; + // Activities which should not trigger Back gesture. + private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>(); private final Point mDisplaySize = new Point(); private final int mDisplayId; @@ -162,6 +177,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa private boolean mIsEnabled; private boolean mIsNavBarShownTransiently; private boolean mIsBackGestureAllowed; + private boolean mGestureBlockingActivityRunning; private InputMonitor mInputMonitor; private InputEventReceiver mInputEventReceiver; @@ -203,6 +219,29 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mMainExecutor = context.getMainExecutor(); mOverviewProxyService = overviewProxyService; mPluginManager = pluginManager; + ComponentName recentsComponentName = ComponentName.unflattenFromString( + context.getString(com.android.internal.R.string.config_recentsComponentName)); + if (recentsComponentName != null) { + String recentsPackageName = recentsComponentName.getPackageName(); + PackageManager manager = context.getPackageManager(); + try { + Resources resources = manager.getResourcesForApplication(recentsPackageName); + int resId = resources.getIdentifier( + "gesture_blocking_activities", "array", recentsPackageName); + + if (resId == 0) { + Log.e(TAG, "No resource found for gesture-blocking activities"); + } else { + String[] gestureBlockingActivities = resources.getStringArray(resId); + for (String gestureBlockingActivity : gestureBlockingActivities) { + mGestureBlockingActivities.add( + ComponentName.unflattenFromString(gestureBlockingActivity)); + } + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Failed to add gesture blocking activities", e); + } + } Dependency.get(ProtoTracer.class).add(this); mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, @@ -324,6 +363,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mGestureNavigationSettingsObserver.unregister(); mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this); mPluginManager.removePluginListener(this); + ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); try { WindowManagerGlobal.getWindowManagerService() @@ -338,6 +378,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa updateDisplaySize(); mContext.getSystemService(DisplayManager.class).registerDisplayListener(this, mContext.getMainThreadHandler()); + ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); try { WindowManagerGlobal.getWindowManagerService() @@ -491,6 +532,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa mLogGesture = false; mInRejectedExclusion = false; mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed + && !mGestureBlockingActivityRunning && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) && isWithinTouchRegion((int) ev.getX(), (int) ev.getY()); if (mAllowGesture) { @@ -633,6 +675,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa pw.println(" mEdgeWidthRight=" + mEdgeWidthRight); } + private boolean isGestureBlockingActivityRunning() { + ActivityManager.RunningTaskInfo runningTask = + ActivityManagerWrapper.getInstance().getRunningTask(); + ComponentName topActivity = runningTask == null ? null : runningTask.topActivity; + return topActivity != null && mGestureBlockingActivities.contains(topActivity); + } + @Override public void writeToProto(SystemUiTraceProto proto) { if (proto.edgeBackGestureHandler == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java index 83d398d3e7ae..0d6597f1b11b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenGestureLogger.java @@ -39,7 +39,7 @@ import javax.inject.Singleton; public class LockscreenGestureLogger { /** - * Contains Lockscreen related Westworld UiEvent enums. + * Contains Lockscreen related statsd UiEvent enums. */ public enum LockscreenUiEvent implements UiEventLogger.UiEventEnum { @UiEvent(doc = "Lockscreen > Pull shade open") diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt index ecd3afd687b3..a284a747da21 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/FloatProperties.kt @@ -67,6 +67,40 @@ class FloatProperties { } /** + * Represents the width of a [Rect]. Typically used to animate resizing a Rect horizontally. + * + * This property's getter returns [Rect.width], and its setter changes the value of + * [Rect.right] by adding the animated width value to [Rect.left]. + */ + @JvmField + val RECT_WIDTH = object : FloatPropertyCompat<Rect>("RectWidth") { + override fun getValue(rect: Rect): Float { + return rect.width().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.right = rect.left + value.toInt() + } + } + + /** + * Represents the height of a [Rect]. Typically used to animate resizing a Rect vertically. + * + * This property's getter returns [Rect.height], and its setter changes the value of + * [Rect.bottom] by adding the animated height value to [Rect.top]. + */ + @JvmField + val RECT_HEIGHT = object : FloatPropertyCompat<Rect>("RectHeight") { + override fun getValue(rect: Rect): Float { + return rect.height().toFloat() + } + + override fun setValue(rect: Rect, value: Float) { + rect.bottom = rect.top + value.toInt() + } + } + + /** * Represents the x-coordinate of a [RectF]. Typically used to animate moving a RectF * horizontally. * diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt index 5a2b064c5389..47b607fc6285 100644 --- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt +++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt @@ -178,6 +178,18 @@ abstract class MagnetizedObject<T : Any>( var physicsAnimatorEndListener: PhysicsAnimator.EndListener<T>? = null /** + * Method that is called when the object should be animated stuck to the target. The default + * implementation uses the object's x and y properties to animate the object centered inside the + * target. You can override this if you need custom animation. + * + * The method is invoked with the MagneticTarget that the object is sticking to, the X and Y + * velocities of the gesture that brought the object into the magnetic radius, whether or not it + * was flung, and a callback you must call after your animation completes. + */ + var animateStuckToTarget: (MagneticTarget, Float, Float, Boolean, (() -> Unit)?) -> Unit = + ::animateStuckToTargetInternal + + /** * Sets whether forcefully flinging the object vertically towards a target causes it to be * attracted to the target and then released immediately, despite never being dragged within the * magnetic field. @@ -373,7 +385,7 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = targetObjectIsInMagneticFieldOf cancelAnimations() magnetListener.onStuckToTarget(targetObjectIsInMagneticFieldOf!!) - animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false) + animateStuckToTarget(targetObjectIsInMagneticFieldOf, velX, velY, false, null) vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } else if (targetObjectIsInMagneticFieldOf == null && objectStuckToTarget) { @@ -430,8 +442,8 @@ abstract class MagnetizedObject<T : Any>( targetObjectIsStuckTo = flungToTarget animateStuckToTarget(flungToTarget, velX, velY, true) { - targetObjectIsStuckTo = null magnetListener.onReleasedInTarget(flungToTarget) + targetObjectIsStuckTo = null vibrateIfEnabled(VibrationEffect.EFFECT_HEAVY_CLICK) } @@ -465,7 +477,7 @@ abstract class MagnetizedObject<T : Any>( } /** Animates sticking the object to the provided target with the given start velocities. */ - private fun animateStuckToTarget( + private fun animateStuckToTargetInternal( target: MagneticTarget, velX: Float, velY: Float, @@ -581,10 +593,10 @@ abstract class MagnetizedObject<T : Any>( * multiple objects. */ class MagneticTarget( - internal val targetView: View, + val targetView: View, var magneticFieldRadiusPx: Int ) { - internal val centerOnScreen = PointF() + val centerOnScreen = PointF() private val tempLoc = IntArray(2) diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java index 6131e3b504af..369552fc814d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/Events.java +++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java @@ -340,7 +340,7 @@ public class Events { } /** - * Logs an event to the event log and UiEvent (Westworld) logging. Compare writeEvent, which + * Logs an event to the event log and UiEvent (statsd) logging. Compare writeEvent, which * adds more log destinations. * @param tag One of the EVENT_* codes above. * @param list Any additional event-specific arguments, documented above. 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 59f8c4e329a4..36398a6fc122 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -304,6 +304,10 @@ public class BubbleControllerTest extends SysuiTestCase { public void testPromoteBubble_autoExpand() throws Exception { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -331,6 +335,10 @@ public class BubbleControllerTest extends SysuiTestCase { mBubbleController.updateBubble(mRow2.getEntry()); mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */ false, /* showInShade */ true); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); mBubbleController.removeBubble( mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE); @@ -433,15 +441,16 @@ public class BubbleControllerTest extends SysuiTestCase { assertTrue(mSysUiStateBubblesExpanded); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Switch which bubble is expanded - mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); + mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey( + mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -543,27 +552,27 @@ public class BubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()) - .getEntry().getKey(), + mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -839,6 +848,12 @@ public class BubbleControllerTest extends SysuiTestCase { mRow2.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); mBubbleController.updateBubble( mRow3.getEntry(), /* suppressFlyout */ false, /* showInShade */ false); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow2.getEntry().getKey())) + .thenReturn(mRow2.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow3.getEntry().getKey())) + .thenReturn(mRow3.getEntry()); assertEquals(mBubbleData.getBubbles().size(), 3); mBubbleData.setMaxOverflowBubbles(1); @@ -908,6 +923,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with a bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -927,6 +944,8 @@ public class BubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -948,6 +967,8 @@ public class BubbleControllerTest extends SysuiTestCase { // GIVEN a group summary with two (non-bubble) children and one bubble child ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); mEntryListener.onPendingEntryAdded(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java index 72f816ff56b5..be03923e7264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java @@ -86,8 +86,7 @@ public class BubbleTest extends SysuiTestCase { final String msg = "Hello there!"; doReturn(Notification.Style.class).when(mNotif).getNotificationStyle(); mExtras.putCharSequence(Notification.EXTRA_TEXT, msg); - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -98,8 +97,7 @@ public class BubbleTest extends SysuiTestCase { mExtras.putCharSequence(Notification.EXTRA_BIG_TEXT, msg); // Should be big text, not the small text. - assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertEquals(msg, BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -107,8 +105,7 @@ public class BubbleTest extends SysuiTestCase { doReturn(Notification.MediaStyle.class).when(mNotif).getNotificationStyle(); // Media notifs don't get update messages. - assertNull(BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).message); + assertNull(BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -124,7 +121,7 @@ public class BubbleTest extends SysuiTestCase { // Should be the last one only. assertEquals("Really? I prefer them that way.", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); + BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); } @Test @@ -139,11 +136,8 @@ public class BubbleTest extends SysuiTestCase { "Oh, hello!", 0, "Mady").toBundle()}); // Should be the last one only. - assertEquals("Oh, hello!", - BubbleViewInfoTask.extractFlyoutMessage(mContext, mEntry).message); - assertEquals("Mady", - BubbleViewInfoTask.extractFlyoutMessage(mContext, - mEntry).senderName); + assertEquals("Oh, hello!", BubbleViewInfoTask.extractFlyoutMessage(mEntry).message); + assertEquals("Mady", BubbleViewInfoTask.extractFlyoutMessage(mEntry).senderName); } @Test 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 58e06b5178c6..1c70db3a548e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -302,6 +302,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { public void testRemoveBubble_withDismissedNotif_notInOverflow() { mEntryListener.onEntryAdded(mRow.getEntry()); mBubbleController.updateBubble(mRow.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(mRow.getEntry().getKey())) + .thenReturn(mRow.getEntry()); assertTrue(mBubbleController.hasBubbles()); assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow.getEntry())); @@ -388,14 +390,14 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { true, mRow.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getSelectedBubble().getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(mRow2.getEntry())); // Switch which bubble is expanded mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey())); mBubbleData.setExpanded(true); - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow.getEntry())); @@ -488,27 +490,27 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey()); // Last added is the one that is expanded - assertEquals(mRow2.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow2.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade( mRow2.getEntry())); // Dismiss currently expanded mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey()); // Make sure first bubble is selected - assertEquals(mRow.getEntry(), - mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry()); + assertEquals(mRow.getEntry().getKey(), mBubbleData.getBubbleInStackWithKey( + stackView.getExpandedBubble().getKey()).getKey()); verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey()); // Dismiss that one mBubbleController.removeBubble( mBubbleData.getBubbleInStackWithKey( - stackView.getExpandedBubble().getKey()).getEntry().getKey(), + stackView.getExpandedBubble().getKey()).getKey(), BubbleController.DISMISS_USER_GESTURE); // Make sure state changes and collapse happens @@ -767,6 +769,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -785,6 +789,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey())); @@ -807,6 +813,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2); ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup(); mEntryListener.onEntryAdded(groupedBubble.getEntry()); + when(mNotificationEntryManager.getPendingOrActiveNotif(groupedBubble.getEntry().getKey())) + .thenReturn(groupedBubble.getEntry()); groupSummary.addChildNotification(groupedBubble); // WHEN the summary is dismissed diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index 1d02b8dba910..9b8fd11febe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -32,7 +32,7 @@ class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"), BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index f9d611c2bb33..76c58339726c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,10 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0) + private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", + "key-2", 0, 16537428, "title") + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 49467874dd8b..81687c7fbe1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -54,12 +54,12 @@ class BubbleXmlHelperTest : SysuiTestCase() { @Test fun testReadXml() { val src = """ - <?xml version='1.0' encoding='utf-8' standalone='yes' ?> - <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> - </bs> +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<bs> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +</bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertEquals("failed parsing bubbles from xml\n$src", bubbles, actual) 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 3c1cc232f5c9..e08fe7a13a60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java @@ -16,6 +16,8 @@ package com.android.systemui.globalactions; +import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED; + import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; @@ -34,11 +36,13 @@ import android.app.IActivityManager; import android.app.admin.DevicePolicyManager; import android.app.trust.TrustManager; import android.content.ContentResolver; +import android.content.pm.UserInfo; import android.content.res.Resources; import android.graphics.Color; import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Handler; +import android.os.RemoteException; import android.os.UserManager; import android.service.dreams.IDreamManager; import android.telephony.TelephonyManager; @@ -424,10 +428,12 @@ public class GlobalActionsDialogTest extends SysuiTestCase { } @Test - public void testShouldShowLockScreenMessage() { + public void testShouldShowLockScreenMessage() throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = false; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -444,10 +450,13 @@ public class GlobalActionsDialogTest extends SysuiTestCase { } @Test - public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() { + public void testShouldNotShowLockScreenMessage_whenWalletOrControlsShownOnLockScreen() + throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -464,10 +473,14 @@ public class GlobalActionsDialogTest extends SysuiTestCase { } @Test - public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() { + public void testShouldNotShowLockScreenMessage_whenControlsAndWalletBothDisabled() + throws RemoteException { mGlobalActionsDialog = spy(mGlobalActionsDialog); mGlobalActionsDialog.mDialog = null; when(mKeyguardStateController.isUnlocked()).thenReturn(false); + + when(mActivityManager.getCurrentUser()).thenReturn(newUserInfo()); + when(mLockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED); mGlobalActionsDialog.mShowLockScreenCardsAndControls = true; setupDefaultActions(); when(mWalletPlugin.onPanelShown(any(), anyBoolean())).thenReturn(mWalletController); @@ -484,6 +497,10 @@ public class GlobalActionsDialogTest extends SysuiTestCase { mGlobalActionsDialog.showOrHideDialog(false, true, mWalletPlugin); } + private UserInfo newUserInfo() { + return new UserInfo(0, null, null, UserInfo.FLAG_PRIMARY, null); + } + private void setupDefaultActions() { String[] actions = { GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER, diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java index 4e0970f1e40b..11f901538868 100644 --- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java +++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java @@ -167,14 +167,16 @@ final class RemoteAugmentedAutofillService new IFillCallback.Stub() { @Override public void onSuccess(@Nullable List<Dataset> inlineSuggestionsData, - @Nullable Bundle clientState) { + @Nullable Bundle clientState, boolean showingFillWindow) { mCallbacks.resetLastResponse(); maybeRequestShowInlineSuggestions(sessionId, inlineSuggestionsRequest, inlineSuggestionsData, clientState, focusedId, focusedValue, inlineSuggestionsCallback, client, onErrorCallback, remoteRenderService); - requestAutofill.complete(null); + if (!showingFillWindow) { + requestAutofill.complete(null); + } } @Override diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 158ed8ca6906..712b413c6e1a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -3279,16 +3279,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState }; // When the inline suggestion render service is available and the view is focused, there - // are 2 cases when augmented autofill should ask IME for inline suggestion request, + // are 3 cases when augmented autofill should ask IME for inline suggestion request, // because standard autofill flow didn't: // 1. the field is augmented autofill only (when standard autofill provider is None or // when it returns null response) // 2. standard autofill provider doesn't support inline suggestion + // 3. we re-entered the autofill session and standard autofill was not re-triggered, this is + // recognized by seeing mExpiredResponse == true final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); if (remoteRenderService != null && (mForAugmentedAutofillOnly - || !isInlineSuggestionsEnabledByAutofillProviderLocked()) + || !isInlineSuggestionsEnabledByAutofillProviderLocked() + || mExpiredResponse) && isViewFocusedLocked(flags)) { if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill"); remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback( diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 1ce3dfe9f3a0..63a984ccdc6f 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3535,6 +3535,13 @@ class StorageManagerService extends IStorageManager.Stub // point final boolean systemUserUnlocked = isSystemUnlocked(UserHandle.USER_SYSTEM); + // When the caller is the app actually hosting external storage, we + // should never attempt to augment the actual storage volume state, + // otherwise we risk confusing it with race conditions as users go + // through various unlocked states + final boolean callerIsMediaStore = UserHandle.isSameApp(Binder.getCallingUid(), + mMediaStoreAuthorityAppId); + final boolean userIsDemo; final boolean userKeyUnlocked; final boolean storagePermission; @@ -3554,6 +3561,7 @@ class StorageManagerService extends IStorageManager.Stub final ArraySet<String> resUuids = new ArraySet<>(); synchronized (mLock) { for (int i = 0; i < mVolumes.size(); i++) { + final String volId = mVolumes.keyAt(i); final VolumeInfo vol = mVolumes.valueAt(i); switch (vol.getType()) { case VolumeInfo.TYPE_PUBLIC: @@ -3578,11 +3586,19 @@ class StorageManagerService extends IStorageManager.Stub if (!match) continue; boolean reportUnmounted = false; - if (!systemUserUnlocked) { + if (callerIsMediaStore) { + // When the caller is the app actually hosting external storage, we + // should never attempt to augment the actual storage volume state, + // otherwise we risk confusing it with race conditions as users go + // through various unlocked states + } else if (!systemUserUnlocked) { reportUnmounted = true; + Slog.w(TAG, "Reporting " + volId + " unmounted due to system locked"); } else if ((vol.getType() == VolumeInfo.TYPE_EMULATED) && !userKeyUnlocked) { reportUnmounted = true; + Slog.w(TAG, "Reporting " + volId + "unmounted due to " + userId + " locked"); } else if (!storagePermission && !realState) { + Slog.w(TAG, "Reporting " + volId + "unmounted due to missing permissions"); reportUnmounted = true; } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 769956d797b0..e3eeb6c41d9f 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -47,6 +47,10 @@ import android.service.dreams.IDreamManager; import android.util.Slog; import android.view.Display; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; @@ -77,6 +81,8 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final UiEventLogger mUiEventLogger; + private final ComponentName mAmbientDisplayComponent; private Binder mCurrentDreamToken; private ComponentName mCurrentDreamName; @@ -91,6 +97,26 @@ public final class DreamManagerService extends SystemService { private AmbientDisplayConfiguration mDozeConfig; + @VisibleForTesting + public enum DreamManagerEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "The screensaver has started.") + DREAM_START(577), + + @UiEvent(doc = "The screensaver has stopped.") + DREAM_STOP(578); + + private final int mId; + + DreamManagerEvent(int id) { + mId = id; + } + + @Override + public int getId() { + return mId; + } + } + public DreamManagerService(Context context) { super(context); mContext = context; @@ -102,6 +128,9 @@ public final class DreamManagerService extends SystemService { mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); + mUiEventLogger = new UiEventLoggerImpl(); + AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext); + mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent()); } @Override @@ -388,6 +417,9 @@ public final class DreamManagerService extends SystemService { .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); mHandler.post(wakeLock.wrap(() -> { mAtmInternal.notifyDreamStateChanged(true); + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_START); + } mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock); })); } @@ -415,6 +447,9 @@ public final class DreamManagerService extends SystemService { } private void cleanupDreamLocked() { + if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { + mUiEventLogger.log(DreamManagerEvent.DREAM_STOP); + } mCurrentDreamToken = null; mCurrentDreamName = null; mCurrentDreamIsTest = false; diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index ccda5875d9fa..ab6f9e2dccda 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -96,6 +96,14 @@ public class AppsFilter { private final SparseSetArray<Integer> mQueriesViaComponent = new SparseSetArray<>(); /** + * Pending full recompute of mQueriesViaComponent. Occurs when a package adds a new set of + * protected broadcast. This in turn invalidates all prior additions and require a very + * computationally expensive recomputing. + * Full recompute is done lazily at the point when we use mQueriesViaComponent to filter apps. + */ + private boolean mQueriesViaComponentRequireRecompute = false; + + /** * A set of App IDs that are always queryable by any package, regardless of their manifest * content. */ @@ -278,7 +286,7 @@ public class AppsFilter { private void updateEnabledState(AndroidPackage pkg) { // TODO(b/135203078): Do not use toAppInfo - final boolean enabled = mInjector.getCompatibility().isChangeEnabled( + final boolean enabled = mInjector.getCompatibility().isChangeEnabledInternal( PackageManager.FILTER_APPLICATION_QUERY, pkg.toAppInfoWithoutState()); if (enabled) { mDisabledPackages.remove(pkg.getPackageName()); @@ -523,9 +531,8 @@ public class AppsFilter { return; } - if (!newPkg.getProtectedBroadcasts().isEmpty()) { - mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts()); - recomputeComponentVisibility(existingSettings, newPkg.getPackageName()); + if (mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts())) { + mQueriesViaComponentRequireRecompute = true; } final boolean newIsForceQueryable = @@ -550,7 +557,8 @@ public class AppsFilter { final AndroidPackage existingPkg = existingSetting.pkg; // let's evaluate the ability of already added packages to see this new package if (!newIsForceQueryable) { - if (canQueryViaComponents(existingPkg, newPkg, mProtectedBroadcasts)) { + if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(existingPkg, + newPkg, mProtectedBroadcasts)) { mQueriesViaComponent.add(existingSetting.appId, newPkgSetting.appId); } if (canQueryViaPackage(existingPkg, newPkg) @@ -560,7 +568,8 @@ public class AppsFilter { } // now we'll evaluate our new package's ability to see existing packages if (!mForceQueryable.contains(existingSetting.appId)) { - if (canQueryViaComponents(newPkg, existingPkg, mProtectedBroadcasts)) { + if (!mQueriesViaComponentRequireRecompute && canQueryViaComponents(newPkg, + existingPkg, mProtectedBroadcasts)) { mQueriesViaComponent.add(newPkgSetting.appId, existingSetting.appId); } if (canQueryViaPackage(newPkg, existingPkg) @@ -689,13 +698,11 @@ public class AppsFilter { return ret; } - private void recomputeComponentVisibility(ArrayMap<String, PackageSetting> existingSettings, - @Nullable String excludePackage) { + private void recomputeComponentVisibility(ArrayMap<String, PackageSetting> existingSettings) { mQueriesViaComponent.clear(); for (int i = existingSettings.size() - 1; i >= 0; i--) { PackageSetting setting = existingSettings.valueAt(i); if (setting.pkg == null - || setting.pkg.getPackageName().equals(excludePackage) || mForceQueryable.contains(setting.appId)) { continue; } @@ -704,8 +711,7 @@ public class AppsFilter { continue; } final PackageSetting otherSetting = existingSettings.valueAt(j); - if (otherSetting.pkg == null - || otherSetting.pkg.getPackageName().equals(excludePackage)) { + if (otherSetting.pkg == null) { continue; } if (canQueryViaComponents(setting.pkg, otherSetting.pkg, mProtectedBroadcasts)) { @@ -713,6 +719,7 @@ public class AppsFilter { } } } + mQueriesViaComponentRequireRecompute = false; } /** @@ -787,9 +794,11 @@ public class AppsFilter { } } - mQueriesViaComponent.remove(setting.appId); - for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { - mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); + if (!mQueriesViaComponentRequireRecompute) { + mQueriesViaComponent.remove(setting.appId); + for (int i = mQueriesViaComponent.size() - 1; i >= 0; i--) { + mQueriesViaComponent.remove(mQueriesViaComponent.keyAt(i), setting.appId); + } } mQueriesViaPackage.remove(setting.appId); for (int i = mQueriesViaPackage.size() - 1; i >= 0; i--) { @@ -810,10 +819,11 @@ public class AppsFilter { if (!setting.pkg.getProtectedBroadcasts().isEmpty()) { final String removingPackageName = setting.pkg.getPackageName(); - mProtectedBroadcasts.clear(); - mProtectedBroadcasts.addAll( - collectProtectedBroadcasts(settings, removingPackageName)); - recomputeComponentVisibility(settings, removingPackageName); + final Set<String> protectedBroadcasts = mProtectedBroadcasts; + mProtectedBroadcasts = collectProtectedBroadcasts(settings, removingPackageName); + if (!mProtectedBroadcasts.containsAll(protectedBroadcasts)) { + mQueriesViaComponentRequireRecompute = true; + } } mOverlayReferenceMapper.removePkg(setting.name); @@ -1003,6 +1013,11 @@ public class AppsFilter { } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "mQueriesViaComponent"); + if (mQueriesViaComponentRequireRecompute) { + mStateProvider.runWithState((settings, users) -> { + recomputeComponentVisibility(settings); + }); + } if (mQueriesViaComponent.contains(callingAppId, targetAppId)) { if (DEBUG_LOGGING) { log(callingSetting, targetPkgSetting, "queries component"); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f3bc0561b6ad..13145d00274f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -3175,6 +3175,10 @@ public class PackageManagerService extends IPackageManager.Stub psit.remove(); logCriticalInfo(Log.WARN, "System package " + ps.name + " no longer exists; it's data will be wiped"); + + // Assume package is truly gone and wipe residual permissions. + mPermissionManager.updatePermissions(ps.name, null); + // Actual deletion of code and data will be handled by later // reconciliation step } else { diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 3c4a9ad08199..8cfe1cd5e98f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -1833,10 +1833,11 @@ public class DisplayPolicy { if (navBarPosition == NAV_BAR_BOTTOM) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. - final int top = cutoutSafeUnrestricted.bottom - - getNavigationBarHeight(rotation, uiMode); final int topNavBar = cutoutSafeUnrestricted.bottom - getNavigationBarFrameHeight(rotation, uiMode); + final int top = mNavButtonForcedVisible + ? topNavBar + : cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode); navigationFrame.set(0, topNavBar, displayWidth, displayFrames.mUnrestricted.bottom); displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top; if (transientNavBarShowing) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index c749125ec531..6670dbfea282 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2927,9 +2927,17 @@ class Task extends WindowContainer<WindowContainer> { // Don't crop HOME/RECENTS windows to stack bounds. This is because in split-screen // they extend past their stack and sysui uses the stack surface to control cropping. // TODO(b/158242495): get rid of this when drag/drop can use surface bounds. - final boolean isTopHomeOrRecents = (isActivityTypeHome() || isActivityTypeRecents()) - && getRootTask().getTopMostTask() == this; - return isResizeable() && !isTopHomeOrRecents; + if (isActivityTypeHome() || isActivityTypeRecents()) { + // Make sure this is the top-most non-organizer root task (if not top-most, it means + // another translucent task could be above this, so this needs to stay cropped. + final Task rootTask = getRootTask(); + final Task topNonOrgTask = + rootTask.mCreatedByOrganizer ? rootTask.getTopMostTask() : rootTask; + if (isDescendantOf(topNonOrgTask)) { + return false; + } + } + return isResizeable(); } /** diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index 1a2672bd0132..51cf858715b4 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -28,6 +28,7 @@ import android.app.ActivityManager.TaskSnapshot; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.RecordingCanvas; @@ -37,8 +38,11 @@ import android.os.Environment; import android.os.Handler; import android.util.ArraySet; import android.util.Slog; +import android.view.InsetsSource; +import android.view.InsetsState; import android.view.SurfaceControl; import android.view.ThreadedRenderer; +import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; @@ -475,9 +479,12 @@ class TaskSnapshotController { final int color = ColorUtils.setAlphaComponent( task.getTaskDescription().getBackgroundColor(), 255); final LayoutParams attrs = mainWindow.getAttrs(); + final InsetsPolicy insetsPolicy = mainWindow.getDisplayContent().getInsetsPolicy(); + final InsetsState insetsState = insetsPolicy.getInsetsForDispatch(mainWindow); + final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrameLw(), insetsState); final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags, attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(), - mHighResTaskSnapshotScale, mainWindow.getRequestedInsetsState()); + mHighResTaskSnapshotScale, insetsState); final int taskWidth = task.getBounds().width(); final int taskHeight = task.getBounds().height(); final int width = (int) (taskWidth * mHighResTaskSnapshotScale); @@ -488,7 +495,7 @@ class TaskSnapshotController { node.setClipToBounds(false); final RecordingCanvas c = node.start(width, height); c.drawColor(color); - decorPainter.setInsets(mainWindow.getContentInsets(), mainWindow.getStableInsets()); + decorPainter.setInsets(systemBarInsets); decorPainter.drawDecors(c, null /* statusBarExcludeFrame */); node.end(c); final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height); @@ -593,6 +600,13 @@ class TaskSnapshotController { return 0; } + static Rect getSystemBarInsets(Rect frame, InsetsState state) { + return state.calculateInsets(frame, null /* ignoringVisibilityState */, + false /* isScreenRound */, false /* alwaysConsumeSystemBars */, + null /* displayCutout */, 0 /* legacySoftInputMode */, 0 /* legacySystemUiFlags */, + null /* typeSideMap */).getInsets(WindowInsets.Type.systemBars()).toRect(); + } + void dump(PrintWriter pw, String prefix) { pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale); mCache.dump(pw, prefix); diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java index e26f1e1fe06f..f1f576220a9a 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java @@ -39,10 +39,9 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static com.android.internal.policy.DecorView.NAVIGATION_BAR_COLOR_VIEW_ATTRIBUTES; import static com.android.internal.policy.DecorView.STATUS_BAR_COLOR_VIEW_ATTRIBUTES; -import static com.android.internal.policy.DecorView.getColorViewLeftInset; -import static com.android.internal.policy.DecorView.getColorViewTopInset; import static com.android.internal.policy.DecorView.getNavigationBarRect; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; +import static com.android.server.wm.TaskSnapshotController.getSystemBarInsets; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; @@ -131,9 +130,8 @@ class TaskSnapshotSurface implements StartingSurface { private final IWindowSession mSession; private final WindowManagerService mService; private final Rect mTaskBounds; - private final Rect mStableInsets = new Rect(); - private final Rect mContentInsets = new Rect(); private final Rect mFrame = new Rect(); + private final Rect mSystemBarInsets = new Rect(); private TaskSnapshot mSnapshot; private final RectF mTmpSnapshotSize = new RectF(); private final RectF mTmpDstFrame = new RectF(); @@ -174,6 +172,7 @@ class TaskSnapshotSurface implements StartingSurface { final int windowFlags; final int windowPrivateFlags; final int currentOrientation; + final InsetsState insetsState; synchronized (service.mGlobalLock) { final WindowState mainWindow = activity.findMainWindow(); final Task task = activity.getTask(); @@ -241,6 +240,10 @@ class TaskSnapshotSurface implements StartingSurface { taskBounds = new Rect(); task.getBounds(taskBounds); currentOrientation = topFullscreenOpaqueWindow.getConfiguration().orientation; + + final InsetsPolicy insetsPolicy = topFullscreenOpaqueWindow.getDisplayContent() + .getInsetsPolicy(); + insetsState = insetsPolicy.getInsetsForDispatch(topFullscreenOpaqueWindow); } try { final int res = session.addToDisplay(window, window.mSeq, layoutParams, @@ -255,8 +258,7 @@ class TaskSnapshotSurface implements StartingSurface { } final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window, surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis, - windowFlags, windowPrivateFlags, taskBounds, - currentOrientation, topFullscreenOpaqueWindow.getRequestedInsetsState()); + windowFlags, windowPrivateFlags, taskBounds, currentOrientation, insetsState); window.setOuter(snapshotSurface); try { session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1, @@ -266,7 +268,9 @@ class TaskSnapshotSurface implements StartingSurface { } catch (RemoteException e) { // Local call. } - snapshotSurface.setFrames(tmpFrame, tmpContentInsets, tmpStableInsets); + + final Rect systemBarInsets = getSystemBarInsets(tmpFrame, insetsState); + snapshotSurface.setFrames(tmpFrame, systemBarInsets); snapshotSurface.drawSnapshot(); return snapshotSurface; } @@ -315,13 +319,12 @@ class TaskSnapshotSurface implements StartingSurface { } @VisibleForTesting - void setFrames(Rect frame, Rect contentInsets, Rect stableInsets) { + void setFrames(Rect frame, Rect systemBarInsets) { mFrame.set(frame); - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + mSystemBarInsets.set(systemBarInsets); mSizeMismatch = (mFrame.width() != mSnapshot.getSnapshot().getWidth() || mFrame.height() != mSnapshot.getSnapshot().getHeight()); - mSystemBarBackgroundPainter.setInsets(contentInsets, stableInsets); + mSystemBarBackgroundPainter.setInsets(systemBarInsets); } private void drawSnapshot() { @@ -453,9 +456,7 @@ class TaskSnapshotSurface implements StartingSurface { ); // However, we also need to make space for the navigation bar on the left side. - final int colorViewLeftInset = getColorViewLeftInset(mStableInsets.left, - mContentInsets.left); - frame.offset(colorViewLeftInset, 0); + frame.offset(mSystemBarInsets.left, 0); return frame; } @@ -540,8 +541,6 @@ class TaskSnapshotSurface implements StartingSurface { */ static class SystemBarBackgroundPainter { - private final Rect mContentInsets = new Rect(); - private final Rect mStableInsets = new Rect(); private final Paint mStatusBarPaint = new Paint(); private final Paint mNavigationBarPaint = new Paint(); private final int mStatusBarColor; @@ -551,6 +550,7 @@ class TaskSnapshotSurface implements StartingSurface { private final int mSysUiVis; private final float mScale; private final InsetsState mInsetsState; + private final Rect mSystemBarInsets = new Rect(); SystemBarBackgroundPainter(int windowFlags, int windowPrivateFlags, int sysUiVis, TaskDescription taskDescription, float scale, InsetsState insetsState) { @@ -576,9 +576,8 @@ class TaskSnapshotSurface implements StartingSurface { mInsetsState = insetsState; } - void setInsets(Rect contentInsets, Rect stableInsets) { - mContentInsets.set(contentInsets); - mStableInsets.set(stableInsets); + void setInsets(Rect systemBarInsets) { + mSystemBarInsets.set(systemBarInsets); } int getStatusBarColorViewHeight() { @@ -589,7 +588,7 @@ class TaskSnapshotSurface implements StartingSurface { mSysUiVis, mStatusBarColor, mWindowFlags, forceBarBackground) : STATUS_BAR_COLOR_VIEW_ATTRIBUTES.isVisible( mInsetsState, mStatusBarColor, mWindowFlags, forceBarBackground)) { - return (int) (getColorViewTopInset(mStableInsets.top, mContentInsets.top) * mScale); + return (int) (mSystemBarInsets.top * mScale); } else { return 0; } @@ -615,8 +614,7 @@ class TaskSnapshotSurface implements StartingSurface { int statusBarHeight) { if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { - final int rightInset = (int) (DecorView.getColorViewRightInset(mStableInsets.right, - mContentInsets.right) * mScale); + final int rightInset = (int) (mSystemBarInsets.right * mScale); final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; c.drawRect(left, 0, c.getWidth() - rightInset, statusBarHeight, mStatusBarPaint); } @@ -625,8 +623,8 @@ class TaskSnapshotSurface implements StartingSurface { @VisibleForTesting void drawNavigationBarBackground(Canvas c) { final Rect navigationBarRect = new Rect(); - getNavigationBarRect(c.getWidth(), c.getHeight(), mStableInsets, mContentInsets, - navigationBarRect, mScale); + getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, + mScale); final boolean visible = isNavigationBarColorViewVisible(); if (visible && Color.alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { c.drawRect(navigationBarRect, mNavigationBarPaint); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 6406f0ae51a6..dd08f4208eca 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1032,7 +1032,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< * @return Whether this child is on top of the window hierarchy. */ boolean isOnTop() { - return getParent().getTopChild() == this && getParent().isOnTop(); + final WindowContainer parent = getParent(); + return parent != null && parent.getTopChild() == this && parent.isOnTop(); } /** Returns the top child container. */ diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 980b7f1f3be1..5fc519c86f11 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3416,6 +3416,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private void setTouchableRegionCropIfNeeded(InputWindowHandle handle) { final Task task = getTask(); if (task == null || !task.cropWindowsToStackBounds()) { + handle.setTouchableRegionCrop(null); return; } diff --git a/services/tests/PackageManagerServiceTests/host/Android.bp b/services/tests/PackageManagerServiceTests/host/Android.bp index dad001b52b15..41dfade1a09a 100644 --- a/services/tests/PackageManagerServiceTests/host/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/Android.bp @@ -28,6 +28,14 @@ java_test_host { ":PackageManagerDummyAppVersion1", ":PackageManagerDummyAppVersion2", ":PackageManagerDummyAppVersion3", + ":PackageManagerDummyAppVersion4", ":PackageManagerDummyAppOriginalOverride", + ":PackageManagerServiceHostTestsResources", ] } + +filegroup { + name: "PackageManagerServiceHostTestsResources", + srcs: [ "resources/*" ], + path: "resources/" +} diff --git a/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk b/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk Binary files differnew file mode 100644 index 000000000000..127886cf8e9e --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/resources/PackageManagerDummyAppVersion3Invalid.apk diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt index 4927c45550b5..490f96d8f426 100644 --- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/HostUtils.kt @@ -19,6 +19,7 @@ package com.android.server.pm.test import com.android.internal.util.test.SystemPreparer import com.android.tradefed.device.ITestDevice import java.io.File +import java.io.FileOutputStream internal fun SystemPreparer.pushApk(file: String, partition: Partition) = pushResourceFile(file, HostUtils.makePathForApk(file, partition)) @@ -43,4 +44,13 @@ internal object HostUtils { .resolve(file.nameWithoutExtension) .resolve(file.name) .toString() + + fun copyResourceToHostFile(javaResourceName: String, file: File): File { + javaClass.classLoader!!.getResource(javaResourceName).openStream().use { input -> + FileOutputStream(file).use { output -> + input.copyTo(output) + } + } + return file + } } diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt new file mode 100644 index 000000000000..98e045d0a203 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/InvalidNewSystemAppTest.kt @@ -0,0 +1,83 @@ +/* + * 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.pm.test + +import com.android.internal.util.test.SystemPreparer +import com.android.tradefed.testtype.DeviceJUnit4ClassRunner +import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test +import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before +import org.junit.ClassRule +import org.junit.Rule +import org.junit.Test +import org.junit.rules.RuleChain +import org.junit.rules.TemporaryFolder +import org.junit.runner.RunWith + +@RunWith(DeviceJUnit4ClassRunner::class) +class InvalidNewSystemAppTest : BaseHostJUnit4Test() { + + companion object { + private const val TEST_PKG_NAME = "com.android.server.pm.test.dummy_app" + private const val VERSION_ONE = "PackageManagerDummyAppVersion1.apk" + private const val VERSION_TWO = "PackageManagerDummyAppVersion2.apk" + private const val VERSION_THREE_INVALID = "PackageManagerDummyAppVersion3Invalid.apk" + private const val VERSION_FOUR = "PackageManagerDummyAppVersion4.apk" + + @get:ClassRule + val deviceRebootRule = SystemPreparer.TestRuleDelegate(true) + } + + private val tempFolder = TemporaryFolder() + private val preparer: SystemPreparer = SystemPreparer(tempFolder, + SystemPreparer.RebootStrategy.START_STOP, deviceRebootRule) { this.device } + + @get:Rule + val rules = RuleChain.outerRule(tempFolder).around(preparer)!! + + @Before + @After + fun uninstallDataPackage() { + device.uninstallPackage(TEST_PKG_NAME) + } + + @Test + fun verify() { + // First, push a system app to the device and then update it so there's a data variant + val filePath = HostUtils.makePathForApk("PackageManagerDummyApp.apk", Partition.PRODUCT) + + preparer.pushResourceFile(VERSION_ONE, filePath) + .reboot() + + val versionTwoFile = HostUtils.copyResourceToHostFile(VERSION_TWO, tempFolder.newFile()) + + assertThat(device.installPackage(versionTwoFile, true)).isNull() + + // Then push a bad update to the system, overwriting the existing file as if an OTA occurred + preparer.deleteFile(filePath) + .pushResourceFile(VERSION_THREE_INVALID, filePath) + .reboot() + + // This will remove the package from the device, which is expected + assertThat(device.getAppPackageInfo(TEST_PKG_NAME)).isNull() + + // Then check that a user would still be able to install the application manually + val versionFourFile = HostUtils.copyResourceToHostFile(VERSION_FOUR, tempFolder.newFile()) + assertThat(device.installPackage(versionFourFile, true)).isNull() + } +} diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp index 9568faa7dfd0..c9b29275a731 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp +++ b/services/tests/PackageManagerServiceTests/host/test-apps/Android.bp @@ -28,6 +28,11 @@ android_test_helper_app { } android_test_helper_app { + name: "PackageManagerDummyAppVersion4", + manifest: "AndroidManifestVersion4.xml" +} + +android_test_helper_app { name: "PackageManagerDummyAppOriginalOverride", manifest: "AndroidManifestOriginalOverride.xml" } diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml index d772050d7fd0..b492a31349fc 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion1.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="1" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml index 53f836b222e6..25e9f8eb2a67 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion2.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="2" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml index 90ca9d0ac02c..935f5e62f508 100644 --- a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion3.xml @@ -18,4 +18,11 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.server.pm.test.dummy_app" android:versionCode="3" - /> + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml new file mode 100644 index 000000000000..d0643cbb2aeb --- /dev/null +++ b/services/tests/PackageManagerServiceTests/host/test-apps/AndroidManifestVersion4.xml @@ -0,0 +1,28 @@ +<?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. + --> +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.pm.test.dummy_app" + android:versionCode="4" + > + + <permission + android:name="com.android.server.pm.test.dummy_app.TEST_PERMISSION" + android:protectionLevel="normal" + /> + +</manifest> diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 2d45f9ea40c7..7d20da198371 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -213,7 +213,7 @@ public class AppStandbyControllerTests { } @Override - boolean isNonIdleWhitelisted(String packageName) throws RemoteException { + boolean isNonIdleWhitelisted(String packageName) { return mNonIdleWhitelistApps.contains(packageName); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java index 4907bdc5e1f0..d6ec78837f7d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java @@ -190,7 +190,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(0, 0, 100, 80), mSurface.calculateSnapshotFrame(new Rect(0, 10, 100, 90))); } @@ -199,7 +199,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_navBarLeft() { setupSurface(100, 100); final Rect insets = new Rect(10, 10, 0, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); assertEquals(new Rect(10, 0, 100, 90), mSurface.calculateSnapshotFrame(new Rect(10, 10, 100, 100))); } @@ -208,7 +208,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testCalculateSnapshotFrame_waterfall() { setupSurface(100, 100, new Rect(5, 10, 5, 10), 0, 0, new Rect(0, 0, 100, 100)); final Rect insets = new Rect(0, 10, 0, 10); - mSurface.setFrames(new Rect(5, 0, 95, 100), insets, insets); + mSurface.setFrames(new Rect(5, 0, 95, 100), insets); assertEquals(new Rect(0, 0, 90, 90), mSurface.calculateSnapshotFrame(new Rect(5, 0, 95, 90))); } @@ -217,7 +217,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -230,7 +230,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nullFrame() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -243,7 +243,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { public void testDrawStatusBarBackground_nope() { setupSurface(100, 100); final Rect insets = new Rect(0, 10, 10, 0); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -257,7 +257,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 0, 10); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -270,7 +270,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(10, 10, 0, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); @@ -283,7 +283,7 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { final Rect insets = new Rect(0, 10, 10, 0); setupSurface(100, 100, insets, 0, FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, new Rect(0, 0, 100, 100)); - mSurface.setFrames(new Rect(0, 0, 100, 100), insets, insets); + mSurface.setFrames(new Rect(0, 0, 100, 100), insets); final Canvas mockCanvas = mock(Canvas.class); when(mockCanvas.getWidth()).thenReturn(100); when(mockCanvas.getHeight()).thenReturn(100); |