summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp2
-rw-r--r--apex/blobstore/framework/java/android/app/blob/BlobHandle.java9
-rw-r--r--apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java8
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java26
-rw-r--r--apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java2
-rw-r--r--cmds/statsd/src/atoms.proto27
-rw-r--r--cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java3
-rw-r--r--core/java/android/app/ContextImpl.java7
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java7
-rw-r--r--core/java/android/service/autofill/Dataset.java7
-rw-r--r--core/java/android/service/autofill/InlinePresentation.java20
-rw-r--r--core/java/android/view/SurfaceView.java14
-rw-r--r--core/java/android/view/ViewConfiguration.java5
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java9
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java16
-rw-r--r--core/res/res/anim/screen_rotate_180_enter.xml6
-rw-r--r--core/res/res/anim/screen_rotate_180_exit.xml7
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java68
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java13
-rw-r--r--data/etc/privapp-permissions-platform.xml2
-rw-r--r--data/etc/services.core.protolog.json30
-rw-r--r--media/java/android/media/MediaCodecInfo.java38
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml2
-rw-r--r--packages/Shell/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/res/layout/media_view.xml23
-rw-r--r--packages/SystemUI/res/values-ar/strings.xml4
-rw-r--r--packages/SystemUI/res/values-as/strings.xml3
-rw-r--r--packages/SystemUI/res/values-bs/strings.xml4
-rw-r--r--packages/SystemUI/res/values-gu/strings.xml3
-rw-r--r--packages/SystemUI/res/values-hi/strings.xml2
-rw-r--r--packages/SystemUI/res/values-iw/strings.xml3
-rw-r--r--packages/SystemUI/res/values-kn/strings.xml3
-rw-r--r--packages/SystemUI/res/values-ml/strings.xml3
-rw-r--r--packages/SystemUI/res/values-mr/strings.xml3
-rw-r--r--packages/SystemUI/res/values-ne/strings.xml3
-rw-r--r--packages/SystemUI/res/values-pa/strings.xml3
-rw-r--r--packages/SystemUI/res/values-te/strings.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/xml/media_collapsed.xml45
-rw-r--r--packages/SystemUI/res/xml/media_expanded.xml20
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt153
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt216
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt57
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java7
-rw-r--r--packages/Tethering/jarjar-rules.txt2
-rw-r--r--packages/Tethering/tests/unit/jarjar-rules.txt2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java11
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java27
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java9
-rw-r--r--services/core/java/com/android/server/adb/AdbShellCommand.java68
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java14
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java4
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java7
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java4
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java76
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteProvider.java47
-rw-r--r--services/core/java/com/android/server/notification/ShortcutHelper.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java4
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java57
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java38
-rw-r--r--services/core/java/com/android/server/textclassifier/IconsContentProvider.java66
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java17
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java23
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java24
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java59
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java9
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java21
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java61
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java26
-rw-r--r--tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java5
127 files changed, 1951 insertions, 679 deletions
diff --git a/Android.bp b/Android.bp
index ec7740437c2d..14a2bff8ad13 100644
--- a/Android.bp
+++ b/Android.bp
@@ -534,6 +534,8 @@ java_library {
static_libs: [
"exoplayer2-extractor",
"android.hardware.wifi-V1.0-java-constants",
+ // Additional dependencies needed to build the ike API classes.
+ "ike-internals",
],
apex_available: ["//apex_available:platform"],
visibility: [
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
index ecc78ce7cf34..113f8fe9e248 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java
@@ -51,6 +51,7 @@ public final class BlobHandle implements Parcelable {
};
private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters
+ private static final int LIMIT_BLOB_LABEL_LENGTH = 100; // characters
/**
* Cyrptographically secure hash algorithm used to generate hash of the blob this handle is
@@ -128,6 +129,9 @@ public final class BlobHandle implements Parcelable {
*
* @param digest the SHA-256 hash of the blob this is representing.
* @param label a label indicating what the blob is, that can be surfaced to the user.
+ * The length of the label cannot be more than 100 characters. It is recommended
+ * to keep this brief. This may be truncated and ellipsized if it is too long
+ * to be displayed to the user.
* @param expiryTimeMillis the time in secs after which the blob should be invalidated and not
* allowed to be accessed by any other app,
* in {@link System#currentTimeMillis()} timebase or {@code 0} to
@@ -205,9 +209,9 @@ public final class BlobHandle implements Parcelable {
final BlobHandle other = (BlobHandle) obj;
return this.algorithm.equals(other.algorithm)
&& Arrays.equals(this.digest, other.digest)
- && this.label.equals(other.label)
+ && this.label.toString().equals(other.label.toString())
&& this.expiryTimeMillis == other.expiryTimeMillis
- && this.tag.equals(tag);
+ && this.tag.equals(other.tag);
}
@Override
@@ -233,6 +237,7 @@ public final class BlobHandle implements Parcelable {
Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm);
Preconditions.checkByteArrayNotEmpty(digest, "digest");
Preconditions.checkStringNotEmpty(label, "label must not be null");
+ Preconditions.checkArgument(label.length() <= LIMIT_BLOB_LABEL_LENGTH, "label too long");
Preconditions.checkArgumentNonnegative(expiryTimeMillis,
"expiryTimeMillis must not be negative");
Preconditions.checkStringNotEmpty(tag, "tag must not be null");
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 9c1acafa800d..39f7526560a9 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -347,7 +347,9 @@ public class BlobStoreManager {
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param description a short description string that can be surfaced
- * to the user explaining what the blob is used for.
+ * to the user explaining what the blob is used for. It is recommended to
+ * keep this description brief. This may be truncated and ellipsized
+ * if it is too long to be displayed to the user.
* @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
* automatically released, in {@link System#currentTimeMillis()}
* timebase. If its value is {@code 0}, then the behavior of this
@@ -458,7 +460,9 @@ public class BlobStoreManager {
* @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
* acquire a lease for.
* @param description a short description string that can be surfaced
- * to the user explaining what the blob is used for.
+ * to the user explaining what the blob is used for. It is recommended to
+ * keep this description brief. This may be truncated and
+ * ellipsized if it is too long to be displayed to the user.
*
* @throws IOException when there is an I/O error while acquiring a lease to the blob.
* @throws SecurityException when the blob represented by the {@code blobHandle} does not
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 79cd1b17a5b5..bb9f13f1712c 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.os.Environment;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
@@ -171,6 +172,13 @@ class BlobStoreConfig {
public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES =
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
+ /**
+ * Denotes the maximum number of characters that a lease description can have.
+ */
+ public static final String KEY_LEASE_DESC_CHAR_LIMIT = "lease_desc_char_limit";
+ public static int DEFAULT_LEASE_DESC_CHAR_LIMIT = 300;
+ public static int LEASE_DESC_CHAR_LIMIT = DEFAULT_LEASE_DESC_CHAR_LIMIT;
+
static void refresh(Properties properties) {
if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) {
return;
@@ -221,6 +229,10 @@ class BlobStoreConfig {
MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key,
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES);
break;
+ case KEY_LEASE_DESC_CHAR_LIMIT:
+ LEASE_DESC_CHAR_LIMIT = properties.getInt(key,
+ DEFAULT_LEASE_DESC_CHAR_LIMIT);
+ break;
default:
Slog.wtf(TAG, "Unknown key in device config properties: " + key);
}
@@ -262,6 +274,8 @@ class BlobStoreConfig {
fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
MAX_BLOB_ACCESS_PERMITTED_PACKAGES,
DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES));
+ fout.println(String.format(dumpFormat, KEY_LEASE_DESC_CHAR_LIMIT,
+ LEASE_DESC_CHAR_LIMIT, DEFAULT_LEASE_DESC_CHAR_LIMIT));
}
}
@@ -368,6 +382,18 @@ class BlobStoreConfig {
return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES;
}
+ /**
+ * Returns the lease description truncated to
+ * {@link DeviceConfigProperties#LEASE_DESC_CHAR_LIMIT} characters.
+ */
+ public static CharSequence getTruncatedLeaseDescription(CharSequence description) {
+ if (TextUtils.isEmpty(description)) {
+ return description;
+ }
+ return TextUtils.trimToLengthWithEllipsis(description,
+ DeviceConfigProperties.LEASE_DESC_CHAR_LIMIT);
+ }
+
@Nullable
public static File prepareBlobFile(long sessionId) {
final File blobsDir = prepareBlobsDir();
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 520e8bbf9f93..d37dfdeaa583 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1500,6 +1500,8 @@ public class BlobStoreManagerService extends SystemService {
"leaseExpiryTimeMillis must not be negative");
Objects.requireNonNull(packageName, "packageName must not be null");
+ description = BlobStoreConfig.getTruncatedLeaseDescription(description);
+
final int callingUid = Binder.getCallingUid();
verifyCallingPackage(callingUid, packageName);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 12e428ce5674..7a016522d597 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -587,6 +587,8 @@ message Atom {
BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
10083 [(module) = "framework"];
DNDModeProto dnd_mode_rule = 10084 [(module) = "framework"];
+ GeneralExternalStorageAccessStats general_external_storage_access_stats =
+ 10085 [(module) = "mediaprovider"];
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -4553,6 +4555,31 @@ message VmsClientConnectionStateChanged {
optional State state = 2;
}
+message MimeTypes {
+ repeated string mime_types = 1;
+}
+
+/**
+ * Logs statistics regarding accesses to external storage.
+ * All stats are normalized for one day period.
+ *
+ * Logged from:
+ * packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
+ */
+message GeneralExternalStorageAccessStats {
+ optional int32 uid = 1 [(is_uid) = true];
+ // Total number of accesses like creation, open, delete and rename/update.
+ // Includes file path and ContentResolver accesses
+ optional uint32 total_accesses = 2;
+ // Number of file path accesses, as opposed to file path and ContentResolver.
+ optional uint32 file_path_accesses = 3;
+ // Number of accesses on secondary volumes like SD cards.
+ // Includes file path and ContentResolver accesses
+ optional uint32 secondary_storage_accesses = 4;
+ // Comma-separated list of mime types that were accessed.
+ optional MimeTypes mime_types_accessed = 5;
+}
+
/**
* Logs when MediaProvider has successfully finished scanning a storage volume.
*
diff --git a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
index 6384fb12ca68..51bcad115cc5 100644
--- a/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
+++ b/cmds/statsd/tools/localtools/src/com/android/statsd/shelltools/testdrive/TestDrive.java
@@ -342,6 +342,9 @@ public class TestDrive {
.addPullAtomPackages(PullAtomPackages.newBuilder()
.setAtomId(Atom.TRAIN_INFO_FIELD_NUMBER)
.addPackages("AID_STATSD"))
+ .addPullAtomPackages(PullAtomPackages.newBuilder()
+ .setAtomId(Atom.GENERAL_EXTERNAL_STORAGE_ACCESS_STATS_FIELD_NUMBER)
+ .addPackages("com.google.android.providers.media.module"))
.setHashStringsInMetricReport(false);
}
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 86a3579effe1..a828aac78ded 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1900,11 +1900,13 @@ class ContextImpl extends Context {
@Override
public Object getSystemService(String name) {
+ // We may override this API from outer context.
+ final boolean isUiContext = isUiContext() || getOuterContext().isUiContext();
// Check incorrect Context usage.
- if (isUiComponent(name) && !isUiContext() && vmIncorrectContextUseEnabled()) {
+ if (isUiComponent(name) && !isUiContext && vmIncorrectContextUseEnabled()) {
final String errorMessage = "Tried to access visual service "
+ SystemServiceRegistry.getSystemServiceClassName(name)
- + " from a non-visual Context. ";
+ + " from a non-visual Context:" + getOuterContext();
final String message = "Visual services, such as WindowManager, WallpaperService or "
+ "LayoutInflater should be accessed from Activity or other visual Context. "
+ "Use an Activity or a Context created with "
@@ -2369,6 +2371,7 @@ class ContextImpl extends Context {
context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
mResources.getLoaders()));
+ context.mIsUiContext = isUiContext() || getOuterContext().isUiContext();
return context;
}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4bd7b059dfaa..591a714bfb93 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -65,6 +65,13 @@ public final class CompanionDeviceManager {
/**
* A device, returned in the activity result of the {@link IntentSender} received in
* {@link Callback#onDeviceFound}
+ *
+ * Type is:
+ * <ul>
+ * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
+ * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
+ * <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+ * </ul>
*/
public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index 08aa534be152..2d99c413cc89 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -312,7 +312,12 @@ public final class Dataset implements Parcelable {
* setting it to the {@link
* android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you
* provide a dataset in the result, it will replace the authenticated dataset and
- * will be immediately filled in. If you provide a response, it will replace the
+ * will be immediately filled in. An exception to this behavior is if the original
+ * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset
+ * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then
+ * the original dataset will not be replaced,
+ * so that it can be triggered as a pending intent again.
+ * If you provide a response, it will replace the
* current response and the UI will be refreshed. For example, if you provided
* credit card information without the CVV for the data set in the {@link FillResponse
* response} then the returned data set should contain the CVV entry.
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
index 9cf1b87f7eab..914169485979 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -50,7 +50,11 @@ public final class InlinePresentation implements Parcelable {
/**
* Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
- * host.
+ * host. However, it's eventually up to the host whether the UI is pinned or not.
+ *
+ * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the
+ * new data set returned from authentication intent. See
+ * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information.
*/
private final boolean mPinned;
@@ -90,7 +94,11 @@ public final class InlinePresentation implements Parcelable {
* Specifies the UI specification for the inline suggestion.
* @param pinned
* Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
- * host.
+ * host. However, it's eventually up to the host whether the UI is pinned or not.
+ *
+ * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the
+ * new data set returned from authentication intent. See
+ * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information.
*/
@DataClass.Generated.Member
public InlinePresentation(
@@ -126,7 +134,11 @@ public final class InlinePresentation implements Parcelable {
/**
* Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the
- * host.
+ * host. However, it's eventually up to the host whether the UI is pinned or not.
+ *
+ * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the
+ * new data set returned from authentication intent. See
+ * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information.
*/
@DataClass.Generated.Member
public boolean isPinned() {
@@ -232,7 +244,7 @@ public final class InlinePresentation implements Parcelable {
};
@DataClass.Generated(
- time = 1586992400667L,
+ time = 1593131904745L,
codegenVersion = "1.0.15",
sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index bb0de120dedc..f937bc9e84a9 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -471,6 +471,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
}
private void performDrawFinished() {
+ if (mDeferredDestroySurfaceControl != null) {
+ synchronized (mSurfaceControlLock) {
+ mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply();
+ mDeferredDestroySurfaceControl = null;
+ }
+ }
+
if (mPendingReportDraws > 0) {
mDrawFinished = true;
if (mAttachedToWindow) {
@@ -1194,13 +1201,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
+ "finishedDrawing");
}
- if (mDeferredDestroySurfaceControl != null) {
- synchronized (mSurfaceControlLock) {
- mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply();
- mDeferredDestroySurfaceControl = null;
- }
- }
-
runOnUiThread(this::performDrawFinished);
}
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 0d2d4d13eb38..ffeeb806ba54 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -500,12 +500,13 @@ public class ViewConfiguration {
*/
public static ViewConfiguration get(Context context) {
if (!context.isUiContext() && vmIncorrectContextUseEnabled()) {
- final String errorMessage = "Tried to access UI constants from a non-visual Context.";
+ final String errorMessage = "Tried to access UI constants from a non-visual Context:"
+ + context;
final String message = "UI constants, such as display metrics or window metrics, "
+ "must be accessed from Activity or other visual Context. "
+ "Use an Activity or a Context created with "
+ "Context#createWindowContext(int, Bundle), which are adjusted to the "
- + "configuration and visual bounds of an area on screen.";
+ + "configuration and visual bounds of an area on screen";
final Exception exception = new IllegalArgumentException(errorMessage);
StrictMode.onIncorrectContextUsed(message, exception);
Log.e(TAG, errorMessage + message, exception);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 8c2358d253be..14cf258f18ab 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2040,6 +2040,9 @@ public class ChooserActivity extends ResolverActivity implements
if (!isUserRunning(userHandle)) {
return false;
}
+ if (!isUserUnlocked(userHandle)) {
+ return false;
+ }
if (isQuietModeEnabled(userHandle)) {
return false;
}
@@ -2892,6 +2895,12 @@ public class ChooserActivity extends ResolverActivity implements
}
@VisibleForTesting
+ protected boolean isUserUnlocked(UserHandle userHandle) {
+ UserManager userManager = getSystemService(UserManager.class);
+ return userManager.isUserUnlocked(userHandle);
+ }
+
+ @VisibleForTesting
protected boolean isQuietModeEnabled(UserHandle userHandle) {
UserManager userManager = getSystemService(UserManager.class);
return userManager.isQuietModeEnabled(userHandle);
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index fba4675a8c9f..233231cfcfdf 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1279,13 +1279,17 @@ public class ResolverActivity extends Activity implements
}
private void safelyStartActivityInternal(TargetInfo cti) {
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.unregister();
- }
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
+ // If the target is suspended, the activity will not be successfully launched.
+ // Do not unregister from package manager updates in this case
+ if (!cti.isSuspended()) {
+ if (mPersonalPackageMonitor != null) {
+ mPersonalPackageMonitor.unregister();
+ }
+ if (mWorkPackageMonitor != null) {
+ mWorkPackageMonitor.unregister();
+ }
+ mRegistered = false;
}
- mRegistered = false;
// If needed, show that intent is forwarded
// from managed profile to owner or other way around.
if (mProfileSwitchMessageId != -1) {
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index 889a615e07f4..3b6b4072dbcd 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -25,4 +25,10 @@
android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/fast_out_slow_in"
android:duration="@android:integer/config_screen_rotation_total_180" />
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/screen_rotation_alpha_in"
+ android:startOffset="@android:integer/config_screen_rotation_fade_in_delay"
+ android:duration="@android:integer/config_screen_rotation_fade_in" />
</set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index 766fcfae1f91..26fb6d8df506 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -25,4 +25,9 @@
android:fillBefore="true" android:fillAfter="true"
android:interpolator="@interpolator/fast_out_slow_in"
android:duration="@android:integer/config_screen_rotation_total_180" />
-</set> \ No newline at end of file
+ <alpha android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/screen_rotation_alpha_out"
+ android:duration="@android:integer/config_screen_rotation_fade_out" />
+</set>
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index 0bf10cb710cb..090645f6f7a8 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -1996,7 +1996,7 @@ public class ChooserActivityTest {
}
@Test
- public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() {
+ public void testWorkTab_selectingWorkTabWithNotRunningWorkUser_directShareTargetsNotQueried() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
@@ -2035,7 +2035,7 @@ public class ChooserActivityTest {
}
@Test
- public void testWorkTab_workUserLocked_workTargetsShown() {
+ public void testWorkTab_workUserNotRunning_workTargetsShown() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
@@ -2059,6 +2059,70 @@ public class ChooserActivityTest {
assertEquals(3, activity.getWorkListAdapter().getCount());
}
+ @Test
+ public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ sOverrides.isWorkProfileUserUnlocked = false;
+ boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false };
+ sOverrides.onQueryDirectShareTargets = chooserListAdapter -> {
+ isQueryDirectShareCalledOnWorkProfile[0] =
+ (chooserListAdapter.getUserHandle().getIdentifier() == 10);
+ return null;
+ };
+ boolean[] isQueryTargetServicesCalledOnWorkProfile = new boolean[] { false };
+ sOverrides.onQueryTargetServices = chooserListAdapter -> {
+ isQueryTargetServicesCalledOnWorkProfile[0] =
+ (chooserListAdapter.getUserHandle().getIdentifier() == 10);
+ return null;
+ };
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ assertFalse("Direct share targets were queried on a locked work profile user",
+ isQueryDirectShareCalledOnWorkProfile[0]);
+ assertFalse("Target services were queried on a locked work profile user",
+ isQueryTargetServicesCalledOnWorkProfile[0]);
+ }
+
+ @Test
+ public void testWorkTab_workUserLocked_workTargetsShown() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ sOverrides.isWorkProfileUserUnlocked = false;
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withId(R.id.contentPanel))
+ .perform(swipeUp());
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ assertEquals(3, activity.getWorkListAdapter().getCount());
+ }
+
private Intent createChooserIntent(Intent intent, Intent[] initialIntents) {
Intent chooserIntent = new Intent();
chooserIntent.setAction(Intent.ACTION_CHOOSER);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index b7d6c6196495..d3d5caf3f7e2 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -229,9 +229,20 @@ public class ChooserWrapperActivity extends ChooserActivity {
@Override
protected boolean isUserRunning(UserHandle userHandle) {
+ if (userHandle.equals(UserHandle.SYSTEM)) {
+ return super.isUserRunning(userHandle);
+ }
return sOverrides.isWorkProfileUserRunning;
}
+ @Override
+ protected boolean isUserUnlocked(UserHandle userHandle) {
+ if (userHandle.equals(UserHandle.SYSTEM)) {
+ return super.isUserUnlocked(userHandle);
+ }
+ return sOverrides.isWorkProfileUserUnlocked;
+ }
+
/**
* We cannot directly mock the activity created since instrumentation creates it.
* <p>
@@ -258,6 +269,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
+ public boolean isWorkProfileUserUnlocked;
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
public PackageManager packageManager;
@@ -281,6 +293,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
isWorkProfileUserRunning = true;
+ isWorkProfileUserUnlocked = true;
packageManager = null;
multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() {
@Override
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8520fffb3444..c710bed29361 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -424,6 +424,8 @@ applications that come with the platform
<permission name="android.permission.LOCATION_HARDWARE" />
<!-- Permissions required for GTS test - GtsDialerAudioTestCases -->
<permission name="android.permission.CAPTURE_AUDIO_OUTPUT" />
+ <!-- Permissions required for CTS test - AdbManagerTest -->
+ <permission name="android.permission.MANAGE_DEBUGGING" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index bfc623faeaef..67a64ac592f2 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -757,6 +757,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-547111355": {
+ "message": "hideIme Control target: %s ",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_IME",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-545190927": {
"message": "<<< CLOSE TRANSACTION animate",
"level": "INFO",
@@ -1087,6 +1093,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "95216706": {
+ "message": "hideIme target: %s ",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_IME",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"95281111": {
"message": "Attempted to get IME flag of a display that does not exist: %d",
"level": "WARN",
@@ -1663,12 +1675,6 @@
"group": "WM_SHOW_SURFACE_ALLOC",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
- "1108406230": {
- "message": "stopFreezingDisplayLocked: Returning mWaitingForConfig=%b, mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, mClientFreezingScreen=%b, mOpeningApps.size()=%d",
- "level": "DEBUG",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
- },
"1112047265": {
"message": "finishDrawingWindow: %s mDrawState=%s",
"level": "DEBUG",
@@ -1729,6 +1735,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1246035185": {
+ "message": "stopFreezingDisplayLocked: Returning waitingForConfig=%b, waitingForRemoteRotation=%b, mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1254403969": {
"message": "Surface returned was null: %s",
"level": "VERBOSE",
@@ -1951,12 +1963,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
"at": "com\/android\/server\/wm\/AppTransition.java"
},
- "1591969812": {
- "message": "updateImeControlTarget %s",
- "level": "INFO",
- "group": "WM_DEBUG_IME",
- "at": "com\/android\/server\/wm\/DisplayContent.java"
- },
"1628345525": {
"message": "Now opening app %s",
"level": "VERBOSE",
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 197786f42490..c2168f12a351 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -654,6 +654,9 @@ public final class MediaCodecInfo {
* <p>
*
* The following table summarizes the format keys considered by this method.
+ * This is especially important to consider when targeting a higher SDK version than the
+ * minimum SDK version, as this method will disregard some keys on devices below the target
+ * SDK version.
*
* <table style="width: 0%">
* <thead>
@@ -668,7 +671,7 @@ public final class MediaCodecInfo {
* </thead>
* <tbody>
* <tr>
- * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</th>
+ * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP}</td>
* <td rowspan=3>{@link MediaFormat#KEY_MIME}<sup>*</sup>,<br>
* {@link MediaFormat#KEY_SAMPLE_RATE},<br>
* {@link MediaFormat#KEY_CHANNEL_COUNT},</td>
@@ -679,30 +682,51 @@ public final class MediaCodecInfo {
* {@link MediaFormat#KEY_WIDTH},<br>
* {@link MediaFormat#KEY_HEIGHT},<br>
* <strong>no</strong> {@code KEY_FRAME_RATE}</td>
- * <td rowspan=4>{@link MediaFormat#KEY_BITRATE_MODE},<br>
+ * <td rowspan=10>as to the left, plus<br>
+ * {@link MediaFormat#KEY_BITRATE_MODE},<br>
* {@link MediaFormat#KEY_PROFILE}
* (and/or {@link MediaFormat#KEY_AAC_PROFILE}<sup>~</sup>),<br>
* <!-- {link MediaFormat#KEY_QUALITY},<br> -->
* {@link MediaFormat#KEY_COMPLEXITY}
* (and/or {@link MediaFormat#KEY_FLAC_COMPRESSION_LEVEL}<sup>~</sup>)</td>
* </tr><tr>
- * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</th>
+ * <td>{@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1}</td>
* <td rowspan=2>as above, plus<br>
* {@link MediaFormat#KEY_FRAME_RATE}</td>
* </tr><tr>
- * <td>{@link android.os.Build.VERSION_CODES#M}</th>
+ * <td>{@link android.os.Build.VERSION_CODES#M}</td>
* </tr><tr>
- * <td>{@link android.os.Build.VERSION_CODES#N}</th>
- * <td>as above, plus<br>
+ * <td>{@link android.os.Build.VERSION_CODES#N}</td>
+ * <td rowspan=2>as above, plus<br>
* {@link MediaFormat#KEY_PROFILE},<br>
* <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
* {@link MediaFormat#KEY_BIT_RATE}</td>
- * <td>as above, plus<br>
+ * <td rowspan=2>as above, plus<br>
* {@link MediaFormat#KEY_PROFILE},<br>
* {@link MediaFormat#KEY_LEVEL}<sup>+</sup>,<br>
* <!-- {link MediaFormat#KEY_MAX_BIT_RATE},<br> -->
* {@link MediaFormat#KEY_BIT_RATE},<br>
* {@link CodecCapabilities#FEATURE_IntraRefresh}<sup>E</sup></td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#N_MR1}</td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#O}</td>
+ * <td rowspan=3 colspan=2>as above, plus<br>
+ * {@link CodecCapabilities#FEATURE_PartialFrame}<sup>D</sup></td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#O_MR1}</td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#P}</td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#Q}</td>
+ * <td colspan=2>as above, plus<br>
+ * {@link CodecCapabilities#FEATURE_FrameParsing}<sup>D</sup>,<br>
+ * {@link CodecCapabilities#FEATURE_MultipleFrames},<br>
+ * {@link CodecCapabilities#FEATURE_DynamicTimestamp}</td>
+ * </tr><tr>
+ * <td>{@link android.os.Build.VERSION_CODES#R}</td>
+ * <td colspan=2>as above, plus<br>
+ * {@link CodecCapabilities#FEATURE_LowLatency}<sup>D</sup></td>
* </tr>
* <tr>
* <td colspan=4>
diff --git a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
index e57d1cc11a20..908e2fbbff5b 100644
--- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml
@@ -18,5 +18,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string>
- <string name="disabled_by_admin" msgid="4023569940620832713">"Desativada pelo administrador"</string>
+ <string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string>
</resources>
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b64a9e7a25bf..570c2ab3efd1 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -314,6 +314,9 @@
<!-- Permissions required for GTS test - GtsDialerAudioTestCases -->
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
+ <!-- Permissions required for CTS test - AdbManagerTest -->
+ <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 8441282bd46d..3c641afea0d6 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -65,25 +65,16 @@
<!-- Actions must be ordered left-to-right even in RTL layout. However, they appear in a chain
with the album art and the title, and must as a group appear at the end of that chain. This is
- accomplished by having the guidebox (an invisible view that is positioned around all 5 actions)
- in the chain with the album art and the title. The actions are in a LTR chain bounded by that
- guidebox, and the ambiguity of how wide the guidebox should be is resolved by using a barrier
- which forces it's starting edge to be as far to the end as possible while fitting the actions.
- -->
+ accomplished by having all actions appear in a LTR chain within the parent, and then biasing it
+ to the right side, then this barrier is used to bound the text views. -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/media_action_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
+ app:layout_constraintTop_toTopOf="parent"
app:barrierDirection="start"
- />
-
- <View
- android:id="@+id/media_action_guidebox"
- android:layout_width="0dp"
- android:layout_height="48dp"
- android:layout_marginTop="16dp"
- android:visibility="invisible"
+ app:constraint_referenced_ids="action0,action1,action2,action3,action4"
/>
<ImageButton
@@ -201,7 +192,6 @@
android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
android:singleLine="true"
android:textColor="@color/media_primary_text"
- android:textDirection="locale"
android:textSize="16sp" />
<!-- Artist name -->
@@ -212,14 +202,13 @@
android:fontFamily="@*android:string/config_headlineFontFamily"
android:singleLine="true"
android:textColor="@color/media_secondary_text"
- android:textDirection="locale"
android:textSize="14sp" />
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:tint="@color/media_primary_text"
- android:layout_width="20dp"
- android:layout_height="20dp" />
+ android:layout_width="@dimen/qs_media_icon_size"
+ android:layout_height="@dimen/qs_media_icon_size" />
<!-- Buttons to remove this view when no longer needed -->
<include
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index df2169624b37..ec1e076f8f67 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -721,7 +721,7 @@
<string name="inline_turn_off_notifications" msgid="8543989584403106071">"إيقاف الإشعارات"</string>
<string name="inline_keep_showing_app" msgid="4393429060390649757">"هل تريد الاستمرار في تلقي إشعارات من هذا التطبيق؟"</string>
<string name="notification_silence_title" msgid="8608090968400832335">"صامتة"</string>
- <string name="notification_alert_title" msgid="3656229781017543655">"تلقائي"</string>
+ <string name="notification_alert_title" msgid="3656229781017543655">"تلقائية"</string>
<string name="notification_bubble_title" msgid="8330481035191903164">"فقاعة"</string>
<string name="notification_channel_summary_low" msgid="4860617986908931158">"بدون صوت أو اهتزاز"</string>
<string name="notification_conversation_summary_low" msgid="1734433426085468009">"بدون صوت أو اهتزاز وتظهر في موضع أسفل في قسم المحادثات"</string>
@@ -1045,7 +1045,7 @@
<string name="magnification_overlay_title" msgid="6584179429612427958">"نافذة تراكب التكبير"</string>
<string name="magnification_window_title" msgid="4863914360847258333">"نافذة التكبير"</string>
<string name="magnification_controls_title" msgid="8421106606708891519">"عناصر التحكم في نافذة التكبير"</string>
- <string name="quick_controls_title" msgid="6839108006171302273">"أدوات التحكم بالجهاز"</string>
+ <string name="quick_controls_title" msgid="6839108006171302273">"أدوات التحكم بالأجهزة"</string>
<string name="quick_controls_subtitle" msgid="1667408093326318053">"إضافة عناصر تحكّم لأجهزتك المتصلة"</string>
<string name="quick_controls_setup_title" msgid="8901436655997849822">"إعداد أدوات التحكم بالجهاز"</string>
<string name="quick_controls_setup_subtitle" msgid="1681506617879773824">"اضغط مع الاستمرار على زر التشغيل للوصول إلى عناصر التحكّم"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 949852802b2c..289efd5c7f52 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"নিয়ন্ত্ৰণসমূহ পুনৰ সজাবলৈ ধৰি ৰাখক আৰু টানি আনি এৰক"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"সকলো নিয়ন্ত্ৰণ আঁতৰোৱা হৈছে"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"সালসলনিসমূহ ছেভ নহ’ল"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"অন্য এপ্‌সমূহ চাওক"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"নিয়ন্ত্ৰণসমূহ ল’ড কৰিবপৰা নগ’ল। এপ্‌টোৰ ছেটিংসমূহ সলনি কৰা হোৱা নাই বুলি নিশ্চিত কৰিবলৈ <xliff:g id="APP">%s</xliff:g> এপ্‌টো পৰীক্ষা কৰক।"</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"সমিল নিয়ন্ত্ৰণসমূহ উপলব্ধ নহয়"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"অন্য"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 9dbb82cbfa86..34f4b9fbc3e2 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -628,9 +628,7 @@
<string name="volume_ringer_status_silent" msgid="3691324657849880883">"Isključi zvuk"</string>
<string name="qs_status_phone_vibrate" msgid="7055409506885541979">"Na telefonu je uključena vibracija"</string>
<string name="qs_status_phone_muted" msgid="3763664791309544103">"Zvuk na telefonu je isključen"</string>
- <!-- String.format failed for translation -->
- <!-- no translation found for volume_stream_content_description_unmute (7729576371406792977) -->
- <skip />
+ <string name="volume_stream_content_description_unmute" msgid="7729576371406792977">"%1$s. Dodirnite da uključite zvukove."</string>
<string name="volume_stream_content_description_vibrate" msgid="4858111994183089761">"%1$s. Dodirnite za postavljanje vibracije. Zvukovi usluga pristupačnosti mogu biti isključeni."</string>
<string name="volume_stream_content_description_mute" msgid="4079046784917920984">"%1$s. Dodirnite da isključite zvuk. Zvukovi usluga pristupačnosti mogu biti isključeni."</string>
<string name="volume_stream_content_description_vibrate_a11y" msgid="2742330052979397471">"%1$s. Dodirnite da postavite vibraciju."</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index ee286266ef28..54f45ea652e9 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"નિયંત્રણોને ફરીથી ગોઠવવા માટે તેમને હોલ્ડ કરીને ખેંચો"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"બધા નિયંત્રણો કાઢી નાખ્યા"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"ફેરફારો સાચવ્યા નથી"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"અન્ય બધી ઍપ જુઓ"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"નિયંત્રણો લોડ કરી શકાયા નથી. ઍપના સેટિંગ બદલાયા નથી તેની ખાતરી કરવા માટે <xliff:g id="APP">%s</xliff:g> ઍપ ચેક કરો."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"સુસંગત નિયંત્રણો ઉપલબ્ધ નથી"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"અન્ય"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 96d837fe77cb..1f6000b4f49d 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1048,7 +1048,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"कंट्रोल का क्रम फिर से बदलने के लिए उन्हें दबाकर रखें और खींचें"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"सभी कंट्रोल हटा दिए गए"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"बदलाव सेव नहीं किए गए"</string>
- <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"अन्य ऐप्लिकेशन देखें"</string>
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"दूसरे ऐप्लिकेशन देखें"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"कंट्रोल लोड नहीं किए जा सके. <xliff:g id="APP">%s</xliff:g> ऐप्लिकेशन देखें, ताकि यह पक्का किया जा सके कि ऐप्लिकेशन की सेटिंग में कोई बदलाव नहीं हुआ है."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"इस सेटिंग के साथ काम करने वाले कंट्रोल उपलब्ध नहीं हैं"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"अन्य"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 7a5b446a6b52..cb4fade417f2 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1058,8 +1058,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"יש ללחוץ לחיצה ארוכה ולגרור כדי לארגן מחדש את הפקדים"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"כל הפקדים הוסרו"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"השינויים לא נשמרו"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"הצגת אפליקציות אחרות"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"לא ניתן היה לטעון את הפקדים. יש לבדוק את האפליקציה <xliff:g id="APP">%s</xliff:g> כדי לוודא שהגדרות האפליקציה לא השתנו."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"פקדים תואמים לא זמינים"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"אחר"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 58633e67656a..d545e3123f21 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"ನಿಯಂತ್ರಣಗಳನ್ನು ಮರುಹೊಂದಿಸಲು ಹೋಲ್ಡ್ ಮಾಡಿ ಮತ್ತು ಡ್ರ್ಯಾಗ್‌ ಮಾಡಿ"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"ಎಲ್ಲಾ ನಿಯಂತ್ರಣಗಳನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"ಬದಲಾವಣೆಗಳನ್ನು ಉಳಿಸಲಾಗಿಲ್ಲ"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"ಇತರ ಆ್ಯಪ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"ನಿಯಂತ್ರಣಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ. ಆ್ಯಪ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು ಬದಲಾಗಿಲ್ಲ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು <xliff:g id="APP">%s</xliff:g> ಆ್ಯಪ್ ಅನ್ನು ಪರಿಶೀಲಿಸಿ."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"ಹೊಂದಾಣಿಕೆಯ ನಿಯಂತ್ರಣಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"ಇತರ"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 280c0c43ac6a..2a3ce7460f20 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"നിയന്ത്രണങ്ങൾ പുനഃക്രമീകരിക്കാൻ അമർത്തിപ്പിടിച്ച് വലിച്ചിടുക"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"എല്ലാ നിയന്ത്രണങ്ങളും നീക്കം ചെയ്തു"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"മാറ്റങ്ങൾ സംരക്ഷിച്ചിട്ടില്ല"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"മറ്റ് ആപ്പുകൾ കാണുക"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"നിയന്ത്രണങ്ങൾ ലോഡ് ചെയ്യാനായില്ല. ആപ്പ് ക്രമീകരണം മാറ്റിയിട്ടില്ലെന്ന് ഉറപ്പാക്കാൻ <xliff:g id="APP">%s</xliff:g> ആപ്പ് പരിശോധിക്കുക."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"അനുയോജ്യമായ നിയന്ത്രണങ്ങൾ ലഭ്യമല്ല"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"മറ്റുള്ളവ"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 1c4ff5aabe84..850ef28a021f 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"नियंत्रणांची पुनर्रचना करण्यासाठी धरून ठेवा आणि ड्रॅग करा"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"सर्व नियंत्रणे काढून टाकली आहेत"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"बदल सेव्ह केले गेले नाहीत"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"इतर अ‍ॅप्स पहा"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"नियंत्रणे लोड करता अली नाहीत. ॲपची सेटिंग्ज बदलली नसल्याची खात्री करण्यासाठी <xliff:g id="APP">%s</xliff:g> ॲप तपासा."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"कंपॅटिबल नियंत्रणे उपलब्ध नाहीत"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"इतर"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 811d9d272cce..daa5f365aadb 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"नियन्त्रणहरूको क्रम मिलाउन तिनलाई थिचेर ड्र्याग गर्नुहोस्"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"सबै नियन्त्रणहरू हटाइए"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"परिवर्तनहरू सुरक्षित गरिएका छैनन्"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"अन्य एपहरू हेर्नुहोस्"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"नियन्त्रण सुविधाहरू लोड गर्न सकिएन। <xliff:g id="APP">%s</xliff:g> एपका सेटिङ परिवर्तन गरिएका छैनन् भन्ने कुरा सुनिश्चित गर्न उक्त एप जाँच्नुहोस्।"</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"मिल्दा नियन्त्रण सुविधाहरू उपलब्ध छैनन्"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"अन्य"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 25cfb9c14cf9..e1d4a634eeb6 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"ਕੰਟਰੋਲਾਂ ਨੂੰ ਮੁੜ-ਵਿਵਸਥਿਤ ਕਰਨ ਲਈ ਫੜ੍ਹ ਕੇ ਘਸੀਟੋ"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"ਸਾਰੇ ਕੰਟਰੋਲ ਹਟਾਏ ਗਏ"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"ਤਬਦੀਲੀਆਂ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਗਿਆ"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"ਹੋਰ ਐਪਾਂ ਦੇਖੋ"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"ਕੰਟਰੋਲਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ। ਇਹ ਪੱਕਾ ਕਰਨ ਲਈ <xliff:g id="APP">%s</xliff:g> ਐਪ ਦੀ ਜਾਂਚ ਕਰੋ ਕਿ ਐਪ ਸੈਟਿੰਗਾਂ ਨਹੀਂ ਬਦਲੀਆਂ ਹਨ।"</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"ਕੋਈ ਅਨੁਰੂਪ ਕੰਟਰੋਲ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"ਹੋਰ"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 368e4e57787b..addc1b432e86 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1046,8 +1046,7 @@
<string name="controls_favorite_rearrange" msgid="5616952398043063519">"నియంత్రణల క్రమం మార్చడానికి దేనినైనా పట్టుకుని, లాగి వదిలేయండి"</string>
<string name="controls_favorite_removed" msgid="5276978408529217272">"అన్ని నియంత్రణలు తీసివేయబడ్డాయి"</string>
<string name="controls_favorite_toast_no_changes" msgid="7094494210840877931">"మార్పులు సేవ్ చేయబడలేదు"</string>
- <!-- no translation found for controls_favorite_see_other_apps (7709087332255283460) -->
- <skip />
+ <string name="controls_favorite_see_other_apps" msgid="7709087332255283460">"ఇతర యాప్‌లను చూడండి"</string>
<string name="controls_favorite_load_error" msgid="5126216176144877419">"కంట్రోల్‌లను లోడ్ చేయడం సాధ్యపడలేదు. యాప్ సెట్టింగ్‌లు మారలేదని నిర్ధారించడానికి <xliff:g id="APP">%s</xliff:g> యాప్‌ను చెక్ చేయండి."</string>
<string name="controls_favorite_load_none" msgid="7687593026725357775">"అనుకూల కంట్రోల్‌లు అందుబాటులో లేవు"</string>
<string name="controls_favorite_other_zone_header" msgid="9089613266575525252">"ఇతరం"</string>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 87e1499713bd..eb8758c0d921 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1269,10 +1269,10 @@
<dimen name="qs_media_padding">16dp</dimen>
<dimen name="qs_media_panel_outer_padding">16dp</dimen>
<dimen name="qs_media_album_size">52dp</dimen>
+ <dimen name="qs_media_icon_size">16dp</dimen>
<dimen name="qs_center_guideline_padding">10dp</dimen>
- <dimen name="qs_seamless_icon_size">20dp</dimen>
- <dimen name="qs_seamless_fallback_icon_size">20dp</dimen>
- <dimen name="qs_seamless_fallback_top_margin">18dp</dimen>
+ <dimen name="qs_seamless_icon_size">@dimen/qs_media_icon_size</dimen>
+ <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen>
<dimen name="qs_seamless_fallback_end_margin">16dp</dimen>
<dimen name="qqs_media_spacing">16dp</dimen>
<dimen name="qs_footer_horizontal_margin">22dp</dimen>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index b17aa42f1e9f..ee958f28c51b 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -19,11 +19,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
+ android:layout_width="@dimen/qs_media_icon_size"
+ android:layout_height="@dimen/qs_media_icon_size"
android:layout_marginStart="18dp"
- android:layout_marginTop="22dp"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_name"
+ app:layout_constraintBottom_toBottomOf="@id/app_name"
app:layout_constraintStart_toStartOf="parent"
/>
@@ -59,14 +59,14 @@
android:id="@+id/media_seamless_fallback"
android:layout_width="@dimen/qs_seamless_fallback_icon_size"
android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_seamless_fallback_top_margin"
android:layout_marginEnd="@dimen/qs_seamless_fallback_end_margin"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
android:alpha="0.5"
android:visibility="gone"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_name"
+ app:layout_constraintBottom_toBottomOf="@id/app_name"
app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
/>
@@ -85,10 +85,11 @@
<!-- Song name -->
<Constraint
android:id="@+id/header_title"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="17dp"
android:layout_marginStart="16dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/app_name"
app:layout_constraintBottom_toTopOf="@id/header_artist"
app:layout_constraintStart_toEndOf="@id/album_art"
@@ -98,10 +99,11 @@
<!-- Artist name -->
<Constraint
android:id="@+id/header_artist"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:layout_marginBottom="24dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toStartOf="@id/media_action_barrier"
@@ -135,27 +137,6 @@
/>
<Constraint
- android:id="@+id/media_action_barrier"
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:orientation="vertical"
- app:layout_constraintTop_toTopOf="parent"
- app:barrierDirection="start"
- app:constraint_referenced_ids="media_action_guidebox,action0,action1,action2,action3,action4"
- />
-
- <Constraint
- android:id="@+id/media_action_guidebox"
- android:layout_width="0dp"
- android:layout_height="48dp"
- android:layout_marginTop="18dp"
- android:visibility="invisible"
- app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintStart_toEndOf="@id/header_title"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/action0"
android:layout_width="48dp"
android:layout_height="48dp"
@@ -165,8 +146,9 @@
android:visibility="gone"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/app_name"
- app:layout_constraintLeft_toLeftOf="@id/media_action_guidebox"
+ app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/action1"
+ app:layout_constraintHorizontal_bias="1"
>
</Constraint>
@@ -220,7 +202,8 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@id/app_name"
app:layout_constraintLeft_toRightOf="@id/action3"
- app:layout_constraintRight_toRightOf="@id/media_action_guidebox"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintHorizontal_bias="0"
>
</Constraint>
</ConstraintSet>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 8010f97bf560..d5a02c2d39d5 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -19,11 +19,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
android:id="@+id/icon"
- android:layout_width="16dp"
- android:layout_height="16dp"
+ android:layout_width="@dimen/qs_media_icon_size"
+ android:layout_height="@dimen/qs_media_icon_size"
android:layout_marginStart="18dp"
- android:layout_marginTop="22dp"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_name"
+ app:layout_constraintBottom_toBottomOf="@id/app_name"
app:layout_constraintStart_toStartOf="parent"
/>
@@ -59,14 +59,14 @@
android:id="@+id/media_seamless_fallback"
android:layout_width="@dimen/qs_seamless_fallback_icon_size"
android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_seamless_fallback_top_margin"
android:layout_marginEnd="@dimen/qs_seamless_fallback_end_margin"
android:layout_marginStart="@dimen/qs_center_guideline_padding"
android:alpha="0.5"
android:visibility="gone"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintTop_toTopOf="@id/app_name"
+ app:layout_constraintBottom_toBottomOf="@id/app_name"
app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
+ app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
/>
@@ -83,11 +83,12 @@
<!-- Song name -->
<Constraint
android:id="@+id/header_title"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
android:layout_marginTop="17dp"
android:layout_marginStart="16dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@+id/app_name"
app:layout_constraintStart_toEndOf="@id/album_art"
app:layout_constraintEnd_toEndOf="parent"
@@ -96,10 +97,11 @@
<!-- Artist name -->
<Constraint
android:id="@+id/header_artist"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/qs_media_panel_outer_padding"
android:layout_marginTop="3dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintTop_toBottomOf="@id/header_title"
app:layout_constraintStart_toStartOf="@id/header_title"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 6dc8322a5cf3..c6d128631930 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -124,8 +124,26 @@ class Bubble implements BubbleViewProvider {
private int mNotificationId;
private int mAppUid = -1;
+ /**
+ * A bubble is created and can be updated. This intent is updated until the user first
+ * expands the bubble. Once the user has expanded the contents, we ignore the intent updates
+ * to prevent restarting the intent & possibly altering UI state in the activity in front of
+ * the user.
+ *
+ * Once the bubble is overflowed, the activity is finished and updates to the
+ * notification are respected. Typically an update to an overflowed bubble would result in
+ * that bubble being added back to the stack anyways.
+ */
@Nullable
private PendingIntent mIntent;
+ private boolean mIntentActive;
+ @Nullable
+ private PendingIntent.CancelListener mIntentCancelListener;
+
+ /**
+ * Sent when the bubble & notification are no longer visible to the user (i.e. no
+ * notification in the shade, no bubble in the stack or overflow).
+ */
@Nullable
private PendingIntent mDeleteIntent;
@@ -150,13 +168,19 @@ class Bubble implements BubbleViewProvider {
mShowBubbleUpdateDot = false;
}
- /** Used in tests when no UI is required. */
@VisibleForTesting(visibility = PRIVATE)
Bubble(@NonNull final NotificationEntry e,
- @Nullable final BubbleController.NotificationSuppressionChangedListener listener) {
+ @Nullable final BubbleController.NotificationSuppressionChangedListener listener,
+ final BubbleController.PendingIntentCanceledListener intentCancelListener) {
Objects.requireNonNull(e);
mKey = e.getKey();
mSuppressionListener = listener;
+ mIntentCancelListener = intent -> {
+ if (mIntent != null) {
+ mIntent.unregisterCancelListener(mIntentCancelListener);
+ }
+ intentCancelListener.onPendingIntentCanceled(this);
+ };
setEntry(e);
}
@@ -238,6 +262,10 @@ class Bubble implements BubbleViewProvider {
mExpandedView = null;
}
mIconView = null;
+ if (mIntent != null) {
+ mIntent.unregisterCancelListener(mIntentCancelListener);
+ }
+ mIntentActive = false;
}
void setPendingIntentCanceled() {
@@ -371,11 +399,24 @@ class Bubble implements BubbleViewProvider {
mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
mIcon = entry.getBubbleMetadata().getIcon();
- mIntent = entry.getBubbleMetadata().getIntent();
+
+ if (!mIntentActive || mIntent == null) {
+ if (mIntent != null) {
+ mIntent.unregisterCancelListener(mIntentCancelListener);
+ }
+ mIntent = entry.getBubbleMetadata().getIntent();
+ if (mIntent != null) {
+ mIntent.registerCancelListener(mIntentCancelListener);
+ }
+ } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
+ // Was an intent bubble now it's a shortcut bubble... still unregister the listener
+ mIntent.unregisterCancelListener(mIntentCancelListener);
+ mIntent = null;
+ }
mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
}
mIsImportantConversation =
- entry.getChannel() == null ? false : entry.getChannel().isImportantConversation();
+ entry.getChannel() != null && entry.getChannel().isImportantConversation();
}
@Nullable
@@ -395,10 +436,15 @@ class Bubble implements BubbleViewProvider {
}
/**
- * @return if the bubble was ever expanded
+ * Sets if the intent used for this bubble is currently active (i.e. populating an
+ * expanded view, expanded or not).
*/
- boolean getWasAccessed() {
- return mLastAccessed != 0L;
+ void setIntentActive() {
+ mIntentActive = true;
+ }
+
+ boolean isIntentActive() {
+ return mIntentActive;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 6ea0cde44282..6f103b020814 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -263,6 +263,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
/**
+ * Listener to be notified when a pending intent has been canceled for a bubble.
+ */
+ public interface PendingIntentCanceledListener {
+ /**
+ * Called when the pending intent for a bubble has been canceled.
+ */
+ void onPendingIntentCanceled(Bubble bubble);
+ }
+
+ /**
* Callback for when the BubbleController wants to interact with the notification pipeline to:
* - Remove a previously bubbled notification
* - Update the notification shade since bubbled notification should/shouldn't be showing
@@ -390,6 +400,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
});
+ mBubbleData.setPendingIntentCancelledListener(bubble -> {
+ if (bubble.getBubbleIntent() == null) {
+ return;
+ }
+ if (bubble.isIntentActive()) {
+ bubble.setPendingIntentCanceled();
+ return;
+ }
+ mHandler.post(
+ () -> removeBubble(bubble.getKey(),
+ BubbleController.DISMISS_INVALID_INTENT));
+ });
mNotificationEntryManager = entryManager;
mNotificationGroupManager = groupManager;
@@ -1101,23 +1123,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// Lazy init stack view when a bubble is created
ensureStackViewCreated();
bubble.setInflateSynchronously(mInflateSynchronously);
- bubble.inflate(
- b -> {
- mBubbleData.notificationEntryUpdated(b, suppressFlyout,
- showInShade);
- if (bubble.getBubbleIntent() == null) {
- return;
- }
- bubble.getBubbleIntent().registerCancelListener(pendingIntent -> {
- if (bubble.getWasAccessed()) {
- bubble.setPendingIntentCanceled();
- return;
- }
- mHandler.post(
- () -> removeBubble(bubble.getKey(),
- BubbleController.DISMISS_INVALID_INTENT));
- });
- },
+ bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index c170ee271e1d..d2dc506c8e5c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -59,7 +59,7 @@ import javax.inject.Singleton;
@Singleton
public class BubbleData {
- BubbleLogger mLogger = new BubbleLoggerImpl();
+ private BubbleLogger mLogger = new BubbleLoggerImpl();
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
@@ -137,6 +137,7 @@ public class BubbleData {
@Nullable
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
+ private BubbleController.PendingIntentCanceledListener mCancelledListener;
/**
* We track groups with summaries that aren't visibly displayed but still kept around because
@@ -167,6 +168,11 @@ public class BubbleData {
mSuppressionListener = listener;
}
+ public void setPendingIntentCancelledListener(
+ BubbleController.PendingIntentCanceledListener listener) {
+ mCancelledListener = listener;
+ }
+
public boolean hasBubbles() {
return !mBubbles.isEmpty();
}
@@ -236,7 +242,7 @@ public class BubbleData {
bubbleToReturn = mPendingBubbles.get(key);
} else if (entry != null) {
// New bubble
- bubbleToReturn = new Bubble(entry, mSuppressionListener);
+ bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener);
} else {
// Persisted bubble being promoted
bubbleToReturn = persistedBubble;
@@ -476,6 +482,9 @@ public class BubbleData {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "Cancel overflow bubble: " + b);
}
+ if (b != null) {
+ b.stopInflation();
+ }
mLogger.logOverflowRemove(b, reason);
mStateChange.bubbleRemoved(b, reason);
mOverflowBubbles.remove(b);
@@ -483,6 +492,7 @@ public class BubbleData {
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
+ bubbleToRemove.stopInflation();
if (mBubbles.size() == 1) {
// Going to become empty, handle specially.
setExpandedInternal(false);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 1211fb491ced..3d3171208b15 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -183,6 +183,9 @@ public class BubbleExpandedView extends LinearLayout {
// Apply flags to make behaviour match documentLaunchMode=always.
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ if (mBubble != null) {
+ mBubble.setIntentActive();
+ }
mActivityView.startActivity(mPendingIntent, fillInIntent, options);
}
} catch (RuntimeException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index cd27fdf9c947..749b537ea364 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -290,6 +290,12 @@ public class BubbleStackView extends FrameLayout
/** Whether we're in the middle of dragging the stack around by touch. */
private boolean mIsDraggingStack = false;
+ /**
+ * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
+ * touches from other pointer indices.
+ */
+ private int mPointerIndexDown = -1;
+
/** Description of current animation controller state. */
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("Stack view state:");
@@ -2220,6 +2226,18 @@ public class BubbleStackView extends FrameLayout
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
+ // Ignore touches from additional pointer indices.
+ return false;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mPointerIndexDown = ev.getActionIndex();
+ } else if (ev.getAction() == MotionEvent.ACTION_UP
+ || ev.getAction() == MotionEvent.ACTION_CANCEL) {
+ mPointerIndexDown = -1;
+ }
+
boolean dispatched = super.dispatchTouchEvent(ev);
// If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6b5c8807fbd4..75f4809d752f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -589,7 +589,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
if (targetUserId != ActivityManager.getCurrentUser()) {
return;
}
-
+ if (DEBUG) Log.d(TAG, "keyguardDone");
tryKeyguardDone();
}
@@ -608,6 +608,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
@Override
public void keyguardDonePending(boolean strongAuth, int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
+ if (DEBUG) Log.d(TAG, "keyguardDonePending");
if (targetUserId != ActivityManager.getCurrentUser()) {
Trace.endSection();
return;
@@ -626,6 +627,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
@Override
public void keyguardGone() {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardGone");
+ if (DEBUG) Log.d(TAG, "keyguardGone");
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(false);
mKeyguardDisplayManager.hide();
Trace.endSection();
@@ -1690,9 +1692,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
};
private void tryKeyguardDone() {
+ if (DEBUG) {
+ Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
+ + mHideAnimationRun + " animRunning - " + mHideAnimationRunning);
+ }
if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
handleKeyguardDone();
} else if (!mHideAnimationRun) {
+ if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
mHideAnimationRun = true;
mHideAnimationRunning = true;
mKeyguardViewControllerLazy.get()
@@ -1919,6 +1926,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
};
private final Runnable mHideAnimationFinishedRunnable = () -> {
+ Log.e(TAG, "mHideAnimationFinishedRunnable#run");
mHideAnimationRunning = false;
tryKeyguardDone();
};
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 7c09accae649..127c5dd54d72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -39,9 +39,8 @@ class MediaCarouselController @Inject constructor(
private val mediaHostStatesManager: MediaHostStatesManager,
private val activityStarter: ActivityStarter,
@Main executor: DelayableExecutor,
- mediaManager: MediaDataCombineLatest,
+ mediaManager: MediaDataFilter,
configurationController: ConfigurationController,
- mediaDataManager: MediaDataManager,
falsingManager: FalsingManager
) {
/**
@@ -148,7 +147,7 @@ class MediaCarouselController @Inject constructor(
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
mediaCarouselScrollHandler = MediaCarouselScrollHandler(mediaCarousel, pageIndicator,
- executor, mediaDataManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
+ executor, mediaManager::onSwipeToDismiss, this::updatePageIndicatorLocation,
falsingManager)
isRtl = context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
inflateSettingsButton()
@@ -249,6 +248,7 @@ class MediaCarouselController @Inject constructor(
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
existingPlayer.view?.player?.setLayoutParams(lp)
+ existingPlayer.bind(data)
existingPlayer.setListening(currentlyExpanded)
updatePlayerToState(existingPlayer, noAnimation = true)
if (existingPlayer.isPlaying) {
@@ -256,16 +256,18 @@ class MediaCarouselController @Inject constructor(
} else {
mediaContent.addView(existingPlayer.view?.player)
}
- } else if (existingPlayer.isPlaying &&
- mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
- if (visualStabilityManager.isReorderingAllowed) {
- mediaContent.removeView(existingPlayer.view?.player)
- mediaContent.addView(existingPlayer.view?.player, 0)
- } else {
- needsReordering = true
+ } else {
+ existingPlayer.bind(data)
+ if (existingPlayer.isPlaying &&
+ mediaContent.indexOfChild(existingPlayer.view?.player) != 0) {
+ if (visualStabilityManager.isReorderingAllowed) {
+ mediaContent.removeView(existingPlayer.view?.player)
+ mediaContent.addView(existingPlayer.view?.player, 0)
+ } else {
+ needsReordering = true
+ }
}
}
- existingPlayer?.bind(data)
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
mediaCarousel.requiresRemeasuring = true
@@ -299,6 +301,7 @@ class MediaCarouselController @Inject constructor(
if (numPages == 1) {
pageIndicator.setLocation(0f)
}
+ updatePageIndicatorAlpha()
}
/**
@@ -464,7 +467,7 @@ class MediaCarouselController @Inject constructor(
val width = desiredHostState?.measurementInput?.width ?: 0
val height = desiredHostState?.measurementInput?.height ?: 0
if (width != carouselMeasureWidth && width != 0 ||
- height != carouselMeasureWidth && height != 0) {
+ height != carouselMeasureHeight && height != 0) {
carouselMeasureWidth = width
carouselMeasureHeight = height
val playerWidthPlusPadding = carouselMeasureWidth +
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index ef2f71100e1a..3096908aca21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -243,7 +243,7 @@ class MediaCarouselScrollHandler(
}
val rotation = (1.0f - settingsOffset) * 50
settingsButton.rotation = rotation * -Math.signum(contentTranslation)
- val alpha = MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset)
+ val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset))
settingsButton.alpha = alpha
settingsButton.visibility = if (alpha != 0.0f) View.VISIBLE else View.INVISIBLE
settingsButton.translationX = newTranslationX
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index 8c9cb1b240bf..dafc52ad8025 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -23,6 +23,7 @@ import android.media.session.MediaSession
/** State of a media view. */
data class MediaData(
+ val userId: Int,
val initialized: Boolean = false,
val backgroundColor: Int,
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
index 11cbc482459a..e8f0e069c98d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataCombineLatest.kt
@@ -58,6 +58,17 @@ class MediaDataCombineLatest @Inject constructor(
}
/**
+ * Get a map of all non-null data entries
+ */
+ fun getData(): Map<String, MediaData> {
+ return entries.filter {
+ (key, pair) -> pair.first != null && pair.second != null
+ }.mapValues {
+ (key, pair) -> pair.first!!.copy(device = pair.second)
+ }
+ }
+
+ /**
* Add a listener for [MediaData] changes that has been combined with latest [MediaDeviceData].
*/
fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
new file mode 100644
index 000000000000..662831e4a445
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+private const val TAG = "MediaDataFilter"
+
+/**
+ * Filters data updates from [MediaDataCombineLatest] based on the current user ID, and handles user
+ * switches (removing entries for the previous user, adding back entries for the current user)
+ *
+ * This is added downstream of [MediaDataManager] since we may still need to handle callbacks from
+ * background users (e.g. timeouts) that UI classes should ignore.
+ * Instead, UI classes should listen to this so they can stay in sync with the current user.
+ */
+@Singleton
+class MediaDataFilter @Inject constructor(
+ private val dataSource: MediaDataCombineLatest,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val mediaResumeListener: MediaResumeListener,
+ private val mediaDataManager: MediaDataManager,
+ private val lockscreenUserManager: NotificationLockscreenUserManager,
+ @Main private val executor: Executor
+) : MediaDataManager.Listener {
+ private val userTracker: CurrentUserTracker
+ private val listeners: MutableSet<MediaDataManager.Listener> = mutableSetOf()
+
+ // The filtered mediaEntries, which will be a subset of all mediaEntries in MediaDataManager
+ private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
+
+ init {
+ userTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ override fun onUserSwitched(newUserId: Int) {
+ // Post this so we can be sure lockscreenUserManager already got the broadcast
+ executor.execute { handleUserSwitched(newUserId) }
+ }
+ }
+ userTracker.startTracking()
+ dataSource.addListener(this)
+ }
+
+ override fun onMediaDataLoaded(key: String, oldKey: String?, data: MediaData) {
+ if (!lockscreenUserManager.isCurrentProfile(data.userId)) {
+ return
+ }
+
+ if (oldKey != null) {
+ mediaEntries.remove(oldKey)
+ }
+ mediaEntries.put(key, data)
+
+ // Notify listeners
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
+ it.onMediaDataLoaded(key, oldKey, data)
+ }
+ }
+
+ override fun onMediaDataRemoved(key: String) {
+ mediaEntries.remove(key)?.let {
+ // Only notify listeners if something actually changed
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
+ it.onMediaDataRemoved(key)
+ }
+ }
+ }
+
+ @VisibleForTesting
+ internal fun handleUserSwitched(id: Int) {
+ // If the user changes, remove all current MediaData objects and inform listeners
+ val listenersCopy = listeners.toSet()
+ val keyCopy = mediaEntries.keys.toMutableList()
+ // Clear the list first, to make sure callbacks from listeners if we have any entries
+ // are up to date
+ mediaEntries.clear()
+ keyCopy.forEach {
+ Log.d(TAG, "Removing $it after user change")
+ listenersCopy.forEach { listener ->
+ listener.onMediaDataRemoved(it)
+ }
+ }
+
+ dataSource.getData().forEach { (key, data) ->
+ if (lockscreenUserManager.isCurrentProfile(data.userId)) {
+ Log.d(TAG, "Re-adding $key after user change")
+ mediaEntries.put(key, data)
+ listenersCopy.forEach { listener ->
+ listener.onMediaDataLoaded(key, null, data)
+ }
+ }
+ }
+ }
+
+ /**
+ * Invoked when the user has dismissed the media carousel
+ */
+ fun onSwipeToDismiss() {
+ val mediaKeys = mediaEntries.keys.toSet()
+ mediaKeys.forEach {
+ mediaDataManager.setTimedOut(it, timedOut = true)
+ }
+ }
+
+ /**
+ * Are there any media notifications active?
+ */
+ fun hasActiveMedia() = mediaEntries.any { it.value.active }
+
+ /**
+ * Are there any media entries we should display?
+ * If resumption is enabled, this will include inactive players
+ * If resumption is disabled, we only want to show active players
+ */
+ fun hasAnyMedia() = if (mediaResumeListener.isResumptionEnabled()) {
+ mediaEntries.isNotEmpty()
+ } else {
+ hasActiveMedia()
+ }
+
+ /**
+ * Add a listener for filtered [MediaData] changes
+ */
+ fun addListener(listener: MediaDataManager.Listener) = listeners.add(listener)
+
+ /**
+ * Remove a listener that was registered with addListener
+ */
+ fun removeListener(listener: MediaDataManager.Listener) = listeners.remove(listener)
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index d6b6660b778c..8cb93bfc6d4d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -67,7 +67,7 @@ private const val DEFAULT_LUMINOSITY = 0.25f
private const val LUMINOSITY_THRESHOLD = 0.05f
private const val SATURATION_MULTIPLIER = 0.8f
-private val LOADING = MediaData(false, 0, null, null, null, null, null,
+private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
@@ -116,15 +116,6 @@ class MediaDataManager(
broadcastDispatcher, dumpManager, mediaTimeoutListener, mediaResumeListener,
Utils.useMediaResumption(context), Utils.useQsMediaPlayer(context))
- private val userChangeReceiver = object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (Intent.ACTION_USER_SWITCHED == intent.action) {
- // Remove all controls, regardless of state
- clearData()
- }
- }
- }
-
private val appChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
@@ -152,9 +143,6 @@ class MediaDataManager(
mediaResumeListener.setManager(this)
addListener(mediaResumeListener)
- val userFilter = IntentFilter(Intent.ACTION_USER_SWITCHED)
- broadcastDispatcher.registerReceiver(userChangeReceiver, userFilter, null, UserHandle.ALL)
-
val suspendFilter = IntentFilter(Intent.ACTION_PACKAGES_SUSPENDED)
broadcastDispatcher.registerReceiver(appChangeReceiver, suspendFilter, null, UserHandle.ALL)
@@ -169,7 +157,6 @@ class MediaDataManager(
fun destroy() {
context.unregisterReceiver(appChangeReceiver)
- broadcastDispatcher.unregisterReceiver(userChangeReceiver)
}
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
@@ -190,20 +177,6 @@ class MediaDataManager(
}
}
- private fun clearData() {
- // Called on user change. Remove all current MediaData objects and inform listeners
- val listenersCopy = listeners.toSet()
- val keyCopy = mediaEntries.keys.toMutableList()
- // Clear the list first, to make sure callbacks from listeners if we have any entries
- // are up to date
- mediaEntries.clear()
- keyCopy.forEach {
- listenersCopy.forEach { listener ->
- listener.onMediaDataRemoved(it)
- }
- }
- }
-
private fun removeAllForPackage(packageName: String) {
Assert.isMainThread()
val listenersCopy = listeners.toSet()
@@ -224,6 +197,7 @@ class MediaDataManager(
}
fun addResumptionControls(
+ userId: Int,
desc: MediaDescription,
action: Runnable,
token: MediaSession.Token,
@@ -238,7 +212,8 @@ class MediaDataManager(
mediaEntries.put(packageName, resumeData)
}
backgroundExecutor.execute {
- loadMediaDataInBgForResumption(desc, action, token, appName, appIntent, packageName)
+ loadMediaDataInBgForResumption(userId, desc, action, token, appName, appIntent,
+ packageName)
}
}
@@ -282,7 +257,7 @@ class MediaDataManager(
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
- private fun setTimedOut(token: String, timedOut: Boolean) {
+ internal fun setTimedOut(token: String, timedOut: Boolean) {
mediaEntries[token]?.let {
if (it.active == !timedOut) {
return
@@ -293,6 +268,7 @@ class MediaDataManager(
}
private fun loadMediaDataInBgForResumption(
+ userId: Int,
desc: MediaDescription,
resumeAction: Runnable,
token: MediaSession.Token,
@@ -307,7 +283,7 @@ class MediaDataManager(
return
}
- Log.d(TAG, "adding track from browser: $desc")
+ Log.d(TAG, "adding track for $userId from browser: $desc")
// Album art
var artworkBitmap = desc.iconBitmap
@@ -323,7 +299,7 @@ class MediaDataManager(
val mediaAction = getResumeMediaAction(resumeAction)
foregroundExecutor.execute {
- onMediaDataLoaded(packageName, null, MediaData(true, bgColor, appName,
+ onMediaDataLoaded(packageName, null, MediaData(userId, true, bgColor, appName,
null, desc.subtitle, desc.title, artworkIcon, listOf(mediaAction), listOf(0),
packageName, token, appIntent, device = null, active = false,
resumeAction = resumeAction, resumption = true, notificationKey = packageName,
@@ -439,10 +415,11 @@ class MediaDataManager(
val resumeAction: Runnable? = mediaEntries[key]?.resumeAction
val hasCheckedForResume = mediaEntries[key]?.hasCheckedForResume == true
val active = mediaEntries[key]?.active ?: true
- onMediaDataLoaded(key, oldKey, MediaData(true, bgColor, app, smallIconDrawable, artist,
- song, artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
- notif.contentIntent, null, active, resumeAction = resumeAction,
- notificationKey = key, hasCheckedForResume = hasCheckedForResume))
+ onMediaDataLoaded(key, oldKey, MediaData(sbn.normalizedUserId, true, bgColor, app,
+ smallIconDrawable, artist, song, artWorkIcon, actionIcons,
+ actionsToShowCollapsed, sbn.packageName, token, notif.contentIntent, null,
+ active, resumeAction = resumeAction, notificationKey = key,
+ hasCheckedForResume = hasCheckedForResume))
}
}
@@ -564,18 +541,6 @@ class MediaDataManager(
}
}
- /**
- * Are there any media notifications active?
- */
- fun hasActiveMedia() = mediaEntries.any { it.value.active }
-
- /**
- * Are there any media entries we should display?
- * If resumption is enabled, this will include inactive players
- * If resumption is disabled, we only want to show active players
- */
- fun hasAnyMedia() = if (useMediaResumption) mediaEntries.isNotEmpty() else hasActiveMedia()
-
fun setMediaResumptionEnabled(isEnabled: Boolean) {
if (useMediaResumption == isEnabled) {
return
@@ -596,16 +561,6 @@ class MediaDataManager(
}
}
- /**
- * Invoked when the user has dismissed the media carousel
- */
- fun onSwipeToDismiss() {
- val mediaKeys = mediaEntries.keys.toSet()
- mediaKeys.forEach {
- setTimedOut(it, timedOut = true)
- }
- }
-
interface Listener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index 3d2b72d8fd83..fc33391a9ad1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -40,6 +40,28 @@ import javax.inject.Inject
import javax.inject.Singleton
/**
+ * Similarly to isShown but also excludes views that have 0 alpha
+ */
+val View.isShownNotFaded: Boolean
+ get() {
+ var current: View = this
+ while (true) {
+ if (current.visibility != View.VISIBLE) {
+ return false
+ }
+ if (current.alpha == 0.0f) {
+ return false
+ }
+ val parent = current.parent ?: return false // We are not attached to the view root
+ if (parent !is View) {
+ // we reached the viewroot, hurray
+ return true
+ }
+ current = parent
+ }
+ }
+
+/**
* This manager is responsible for placement of the unique media view between the different hosts
* and animate the positions of the views to achieve seamless transitions.
*/
@@ -368,7 +390,7 @@ class MediaHierarchyManager @Inject constructor(
// non-trivial reattaching logic happening that will make the view not-shown earlier
return true
}
- return mediaFrame.isShown || animator.isRunning || animationPending
+ return mediaFrame.isShownNotFaded || animator.isRunning || animationPending
}
private fun adjustAnimatorForTransition(desiredLocation: Int, previousLocation: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 07a7e618b301..3598719fcb3a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -1,6 +1,5 @@
package com.android.systemui.media
-import android.graphics.PointF
import android.graphics.Rect
import android.util.ArraySet
import android.view.View
@@ -15,8 +14,7 @@ import javax.inject.Inject
class MediaHost @Inject constructor(
private val state: MediaHostStateHolder,
private val mediaHierarchyManager: MediaHierarchyManager,
- private val mediaDataManager: MediaDataManager,
- private val mediaDataManagerCombineLatest: MediaDataCombineLatest,
+ private val mediaDataFilter: MediaDataFilter,
private val mediaHostStatesManager: MediaHostStatesManager
) : MediaHostState by state {
lateinit var hostView: UniqueObjectHostView
@@ -81,12 +79,12 @@ class MediaHost @Inject constructor(
// be a delay until the views and the controllers are initialized, leaving us
// with either a blank view or the controllers not yet initialized and the
// measuring wrong
- mediaDataManagerCombineLatest.addListener(listener)
+ mediaDataFilter.addListener(listener)
updateViewVisibility()
}
override fun onViewDetachedFromWindow(v: View?) {
- mediaDataManagerCombineLatest.removeListener(listener)
+ mediaDataFilter.removeListener(listener)
}
})
@@ -101,7 +99,7 @@ class MediaHost @Inject constructor(
}
// This will trigger a state change that ensures that we now have a state available
state.measurementInput = input
- return mediaHostStatesManager.getPlayerDimensions(state)
+ return mediaHostStatesManager.updateCarouselDimensions(location, state)
}
}
@@ -115,9 +113,9 @@ class MediaHost @Inject constructor(
private fun updateViewVisibility() {
visible = if (showsOnlyActiveMedia) {
- mediaDataManager.hasActiveMedia()
+ mediaDataFilter.hasActiveMedia()
} else {
- mediaDataManager.hasAnyMedia()
+ mediaDataFilter.hasAnyMedia()
}
val newVisibility = if (visible) View.VISIBLE else View.GONE
if (newVisibility != hostView.visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
index f90af2a01de0..d3954b70ca71 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
@@ -32,6 +32,12 @@ class MediaHostStatesManager @Inject constructor() {
private val controllers: MutableSet<MediaViewController> = mutableSetOf()
/**
+ * The overall sizes of the carousel. This is needed to make sure all players in the carousel
+ * have equal size.
+ */
+ val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf()
+
+ /**
* A map with all media states of all locations.
*/
val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf()
@@ -45,6 +51,7 @@ class MediaHostStatesManager @Inject constructor() {
if (!hostState.equals(currentState)) {
val newState = hostState.copy()
mediaHostStates.put(location, newState)
+ updateCarouselDimensions(location, hostState)
// First update all the controllers to ensure they get the chance to measure
for (controller in controllers) {
controller.stateCallback.onHostStateChanged(location, newState)
@@ -61,7 +68,10 @@ class MediaHostStatesManager @Inject constructor() {
* Get the dimensions of all players combined, which determines the overall height of the
* media carousel and the media hosts.
*/
- fun getPlayerDimensions(hostState: MediaHostState): MeasurementOutput {
+ fun updateCarouselDimensions(
+ @MediaLocation location: Int,
+ hostState: MediaHostState
+ ): MeasurementOutput {
val result = MeasurementOutput(0, 0)
for (controller in controllers) {
val measurement = controller.getMeasurementsForState(hostState)
@@ -74,6 +84,7 @@ class MediaHostStatesManager @Inject constructor() {
}
}
}
+ carouselSizes[location] = result
return result
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index 0cc1e7bb1b56..4ec746fcb153 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -56,7 +56,7 @@ class MediaResumeListener @Inject constructor(
private lateinit var mediaDataManager: MediaDataManager
private var mediaBrowser: ResumeMediaBrowser? = null
- private var currentUserId: Int
+ private var currentUserId: Int = context.userId
private val userChangeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
@@ -65,7 +65,6 @@ class MediaResumeListener @Inject constructor(
} else if (Intent.ACTION_USER_SWITCHED == intent.action) {
currentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)
loadSavedComponents()
- loadMediaResumptionControls()
}
}
}
@@ -89,13 +88,12 @@ class MediaResumeListener @Inject constructor(
}
Log.d(TAG, "Adding resume controls $desc")
- mediaDataManager.addResumptionControls(desc, resumeAction, token, appName.toString(),
- appIntent, component.packageName)
+ mediaDataManager.addResumptionControls(currentUserId, desc, resumeAction, token,
+ appName.toString(), appIntent, component.packageName)
}
}
init {
- currentUserId = context.userId
if (useMediaResumption) {
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
@@ -118,6 +116,8 @@ class MediaResumeListener @Inject constructor(
}, Settings.Secure.MEDIA_CONTROLS_RESUME)
}
+ fun isResumptionEnabled() = useMediaResumption
+
private fun loadSavedComponents() {
// Make sure list is empty (if we switched users)
resumeComponents.clear()
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a134dbe0264..8662aacfdab2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -54,7 +54,32 @@ class MediaTimeoutListener @Inject constructor(
if (mediaListeners.containsKey(key)) {
return
}
+ // Having an old key means that we're migrating from/to resumption. We should invalidate
+ // the old listener and create a new one.
+ val migrating = oldKey != null && key != oldKey
+ var wasPlaying = false
+ if (migrating) {
+ if (mediaListeners.containsKey(oldKey)) {
+ val oldListener = mediaListeners.remove(oldKey)
+ wasPlaying = oldListener?.playing ?: false
+ oldListener?.destroy()
+ if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
+ } else {
+ Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
+ }
+ }
mediaListeners[key] = PlaybackStateListener(key, data)
+
+ // If a player becomes active because of a migration, we'll need to broadcast its state.
+ // Doing it now would lead to reentrant callbacks, so let's wait until we're done.
+ if (migrating && mediaListeners[key]?.playing != wasPlaying) {
+ mainExecutor.execute {
+ if (mediaListeners[key]?.playing == true) {
+ if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
+ timeoutCallback.invoke(key, false /* timedOut */)
+ }
+ }
+ }
}
override fun onMediaDataRemoved(key: String) {
@@ -71,7 +96,7 @@ class MediaTimeoutListener @Inject constructor(
) : MediaController.Callback() {
var timedOut = false
- private var playing: Boolean? = null
+ var playing: Boolean? = null
// Resume controls may have null token
private val mediaController = if (data.token != null) {
@@ -83,7 +108,9 @@ class MediaTimeoutListener @Inject constructor(
init {
mediaController?.registerCallback(this)
- onPlaybackStateChanged(mediaController?.playbackState)
+ // Let's register the cancellations, but not dispatch events now.
+ // Timeouts didn't happen yet and reentrant events are troublesome.
+ processState(mediaController?.playbackState, dispatchEvents = false)
}
fun destroy() {
@@ -91,8 +118,12 @@ class MediaTimeoutListener @Inject constructor(
}
override fun onPlaybackStateChanged(state: PlaybackState?) {
+ processState(state, dispatchEvents = true)
+ }
+
+ private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
if (DEBUG) {
- Log.v(TAG, "onPlaybackStateChanged: $state")
+ Log.v(TAG, "processState: $state")
}
val isPlaying = state != null && isPlayingState(state.state)
@@ -116,12 +147,16 @@ class MediaTimeoutListener @Inject constructor(
Log.v(TAG, "Execute timeout for $key")
}
timedOut = true
- timeoutCallback(key, timedOut)
+ if (dispatchEvents) {
+ timeoutCallback(key, timedOut)
+ }
}, PAUSED_MEDIA_TIMEOUT)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
- timeoutCallback(key, timedOut)
+ if (dispatchEvents) {
+ timeoutCallback(key, timedOut)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 033a42a03240..38817d7b579e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
@@ -79,6 +79,16 @@ class MediaViewController @Inject constructor(
private val tmpState = TransitionViewState()
/**
+ * A temporary state used to store intermediate measurements.
+ */
+ private val tmpState2 = TransitionViewState()
+
+ /**
+ * A temporary state used to store intermediate measurements.
+ */
+ private val tmpState3 = TransitionViewState()
+
+ /**
* A temporary cache key to be used to look up cache entries
*/
private val tmpKey = CacheKey()
@@ -258,7 +268,6 @@ class MediaViewController @Inject constructor(
fun attach(transitionLayout: TransitionLayout) {
this.transitionLayout = transitionLayout
layoutController.attach(transitionLayout)
- ensureAllMeasurements()
if (currentEndLocation == -1) {
return
}
@@ -304,8 +313,8 @@ class MediaViewController @Inject constructor(
// Obtain the view state that we'd want to be at the end
// The view might not be bound yet or has never been measured and in that case will be
// reset once the state is fully available
- val endViewState = obtainViewState(endHostState) ?: return
-
+ var endViewState = obtainViewState(endHostState) ?: return
+ endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!!
layoutController.setMeasureState(endViewState)
// If the view isn't bound, we can drop the animation, otherwise we'll execute it
@@ -315,7 +324,8 @@ class MediaViewController @Inject constructor(
}
val result: TransitionViewState
- val startViewState = obtainViewState(startHostState)
+ var startViewState = obtainViewState(startHostState)
+ startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3)
if (!endHostState.visible) {
// Let's handle the case where the end is gone first. In this case we take the
@@ -350,6 +360,22 @@ class MediaViewController @Inject constructor(
animationDelay)
}
+ private fun updateViewStateToCarouselSize(
+ viewState: TransitionViewState?,
+ location: Int,
+ outState: TransitionViewState
+ ) : TransitionViewState? {
+ val result = viewState?.copy(outState) ?: return null
+ val overrideSize = mediaHostStatesManager.carouselSizes[location]
+ overrideSize?.let {
+ // To be safe we're using a maximum here. The override size should always be set
+ // properly though.
+ result.height = Math.max(it.measuredHeight, result.height)
+ result.width = Math.max(it.measuredWidth, result.width)
+ }
+ return result
+ }
+
/**
* Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation].
* In the event of [location] not being visible, [locationWhenHidden] will be used instead.
@@ -387,13 +413,16 @@ class MediaViewController @Inject constructor(
* Clear all existing measurements and refresh the state to match the view.
*/
fun refreshState() {
- if (!firstRefresh) {
- // Let's clear all of our measurements and recreate them!
- viewStates.clear()
- setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
- applyImmediately = true)
+ // Let's clear all of our measurements and recreate them!
+ viewStates.clear()
+ if (firstRefresh) {
+ // This is the first bind, let's ensure we pre-cache all measurements. Otherwise
+ // We'll just load these on demand.
+ ensureAllMeasurements()
+ firstRefresh = false
}
- firstRefresh = false
+ setCurrentState(currentStartLocation, currentEndLocation, currentTransitionProgress,
+ applyImmediately = true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 1842564a4574..68b6785849aa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
@@ -94,7 +94,7 @@ public class ResumeMediaBrowser {
// a request with EXTRA_RECENT; if they don't, no resume controls
MediaBrowser.MediaItem child = children.get(0);
MediaDescription desc = child.getDescription();
- if (child.isPlayable()) {
+ if (child.isPlayable() && mMediaBrowser != null) {
mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(),
ResumeMediaBrowser.this);
} else {
@@ -129,7 +129,7 @@ public class ResumeMediaBrowser {
@Override
public void onConnected() {
Log.d(TAG, "Service connected for " + mComponentName);
- if (mMediaBrowser.isConnected()) {
+ if (mMediaBrowser != null && mMediaBrowser.isConnected()) {
String root = mMediaBrowser.getRoot();
if (!TextUtils.isEmpty(root)) {
mCallback.onConnected();
@@ -187,7 +187,7 @@ public class ResumeMediaBrowser {
@Override
public void onConnected() {
Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
- if (!mMediaBrowser.isConnected()) {
+ if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
mCallback.onError();
return;
}
@@ -246,7 +246,7 @@ public class ResumeMediaBrowser {
@Override
public void onConnected() {
Log.d(TAG, "connected");
- if (!mMediaBrowser.isConnected()
+ if (mMediaBrowser == null || !mMediaBrowser.isConnected()
|| TextUtils.isEmpty(mMediaBrowser.getRoot())) {
mCallback.onError();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index 2980f11b3cbc..ead17867844a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -18,16 +18,16 @@ package com.android.systemui.pip;
import android.animation.AnimationHandler;
import android.animation.Animator;
+import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.systemui.Interpolators;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -76,7 +76,6 @@ public class PipAnimationController {
|| direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
}
- private final Interpolator mFastOutSlowInInterpolator;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private PipTransitionAnimator mCurrentAnimator;
@@ -90,8 +89,6 @@ public class PipAnimationController {
@Inject
PipAnimationController(Context context, PipSurfaceTransactionHelper helper) {
- mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
- com.android.internal.R.interpolator.fast_out_slow_in);
mSurfaceTransactionHelper = helper;
}
@@ -113,10 +110,11 @@ public class PipAnimationController {
}
@SuppressWarnings("unchecked")
- PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds) {
+ PipTransitionAnimator getAnimator(SurfaceControl leash, Rect startBounds, Rect endBounds,
+ Rect sourceHintRect) {
if (mCurrentAnimator == null) {
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
} else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA
&& mCurrentAnimator.isRunning()) {
// If we are still animating the fade into pip, then just move the surface and ensure
@@ -131,7 +129,7 @@ public class PipAnimationController {
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
}
return mCurrentAnimator;
}
@@ -142,7 +140,7 @@ public class PipAnimationController {
private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) {
animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper);
- animator.setInterpolator(mFastOutSlowInInterpolator);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.setFloatValues(FRACTION_START, FRACTION_END);
animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get());
return animator;
@@ -341,6 +339,7 @@ public class PipAnimationController {
@Override
void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
getSurfaceTransactionHelper()
+ .resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds())
.round(tx, leash, shouldApplyCornerRadius());
tx.show(leash);
@@ -356,35 +355,46 @@ public class PipAnimationController {
}
static PipTransitionAnimator<Rect> ofBounds(SurfaceControl leash,
- Rect startValue, Rect endValue) {
+ Rect startValue, Rect endValue, Rect sourceHintRect) {
+ // Just for simplicity we'll interpolate between the source rect hint insets and empty
+ // insets to calculate the window crop
+ final Rect initialStartValue = new Rect(startValue);
+ final Rect sourceHintRectInsets = sourceHintRect != null
+ ? new Rect(sourceHintRect.left - startValue.left,
+ sourceHintRect.top - startValue.top,
+ startValue.right - sourceHintRect.right,
+ startValue.bottom - sourceHintRect.bottom)
+ : null;
+ final Rect sourceInsets = new Rect(0, 0, 0, 0);
+
// construct new Rect instances in case they are recycled
return new PipTransitionAnimator<Rect>(leash, ANIM_TYPE_BOUNDS,
endValue, new Rect(startValue), new Rect(endValue)) {
- private final Rect mTmpRect = new Rect();
-
- private int getCastedFractionValue(float start, float end, float fraction) {
- return (int) (start * (1 - fraction) + end * fraction + .5f);
- }
+ private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+ private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
final Rect start = getStartValue();
final Rect end = getEndValue();
- mTmpRect.set(
- getCastedFractionValue(start.left, end.left, fraction),
- getCastedFractionValue(start.top, end.top, fraction),
- getCastedFractionValue(start.right, end.right, fraction),
- getCastedFractionValue(start.bottom, end.bottom, fraction));
- setCurrentValue(mTmpRect);
+ Rect bounds = mRectEvaluator.evaluate(fraction, start, end);
+ setCurrentValue(bounds);
if (inScaleTransition()) {
if (isOutPipDirection(getTransitionDirection())) {
- getSurfaceTransactionHelper().scale(tx, leash, end, mTmpRect);
+ getSurfaceTransactionHelper().scale(tx, leash, end, bounds);
} else {
- getSurfaceTransactionHelper().scale(tx, leash, start, mTmpRect);
+ getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
}
} else {
- getSurfaceTransactionHelper().crop(tx, leash, mTmpRect);
+ if (sourceHintRectInsets != null) {
+ Rect insets = mInsetsEvaluator.evaluate(fraction, sourceInsets,
+ sourceHintRectInsets);
+ getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialStartValue,
+ bounds, insets);
+ } else {
+ getSurfaceTransactionHelper().scale(tx, leash, start, bounds);
+ }
}
tx.apply();
}
@@ -400,11 +410,11 @@ public class PipAnimationController {
@Override
void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {
- if (!inScaleTransition()) return;
// NOTE: intentionally does not apply the transaction here.
// this end transaction should get executed synchronously with the final
// WindowContainerTransaction in task organizer
- getSurfaceTransactionHelper().resetScale(tx, leash, getDestinationBounds())
+ getSurfaceTransactionHelper()
+ .resetScale(tx, leash, getDestinationBounds())
.crop(tx, leash, getDestinationBounds());
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index 0d3a16ec1028..8bbd15babf19 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -289,6 +289,24 @@ public class PipBoundsHandler {
}
/**
+ * Updatest the display info and display layout on rotation change. This is needed even when we
+ * aren't in PIP because the rotation layout is used to calculate the proper insets for the
+ * next enter animation into PIP.
+ */
+ public void onDisplayRotationChangedNotInPip(int toRotation) {
+ // Update the display layout, note that we have to do this on every rotation even if we
+ // aren't in PIP since we need to update the display layout to get the right resources
+ mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
+
+ // Populate the new {@link #mDisplayInfo}.
+ // The {@link DisplayInfo} queried from DisplayManager would be the one before rotation,
+ // therefore, the width/height may require a swap first.
+ // Moving forward, we should get the new dimensions after rotation from DisplayLayout.
+ mDisplayInfo.rotation = toRotation;
+ updateDisplayInfoIfNeeded();
+ }
+
+ /**
* Updates the display info, calculating and returning the new stack and movement bounds in the
* new orientation of the device if necessary.
*
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
index fc41d2ea8862..65ea887259be 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipSurfaceTransactionHelper.java
@@ -44,6 +44,7 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
private final float[] mTmpFloat9 = new float[9];
private final RectF mTmpSourceRectF = new RectF();
private final RectF mTmpDestinationRectF = new RectF();
+ private final Rect mTmpDestinationRect = new Rect();
@Inject
public PipSurfaceTransactionHelper(Context context, ConfigurationController configController) {
@@ -90,7 +91,30 @@ public class PipSurfaceTransactionHelper implements ConfigurationController.Conf
mTmpDestinationRectF.set(destinationBounds);
mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
- .setPosition(leash, destinationBounds.left, destinationBounds.top);
+ .setPosition(leash, mTmpDestinationRectF.left, mTmpDestinationRectF.top);
+ return this;
+ }
+
+ /**
+ * Operates the scale (setMatrix) on a given transaction and leash
+ * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+ */
+ PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
+ Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ mTmpSourceRectF.set(sourceBounds);
+ mTmpDestinationRect.set(sourceBounds);
+ mTmpDestinationRect.inset(insets);
+ // Scale by the shortest edge and offset such that the top/left of the scaled inset source
+ // rect aligns with the top/left of the destination bounds
+ final float scale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ final float left = destinationBounds.left - insets.left * scale;
+ final float top = destinationBounds.top - insets.top * scale;
+ mTmpTransform.setScale(scale, scale);
+ tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
+ .setWindowCrop(leash, mTmpDestinationRect)
+ .setPosition(leash, left, top);
return this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index c8a1ca02fdfb..0141dee04086 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -143,8 +143,10 @@ public class PipTaskOrganizer extends TaskOrganizer implements
case MSG_RESIZE_ANIMATE: {
Rect currentBounds = (Rect) args.arg2;
Rect toBounds = (Rect) args.arg3;
+ Rect sourceHintRect = (Rect) args.arg4;
int duration = args.argi2;
- animateResizePip(currentBounds, toBounds, args.argi1 /* direction */, duration);
+ animateResizePip(currentBounds, toBounds, sourceHintRect,
+ args.argi1 /* direction */, duration);
if (updateBoundsCallback != null) {
updateBoundsCallback.accept(toBounds);
}
@@ -307,7 +309,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
t.apply();
scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- direction, animationDurationMs, null /* updateBoundsCallback */);
+ null /* sourceHintRect */, direction, animationDurationMs,
+ null /* updateBoundsCallback */);
mInPip = false;
}
});
@@ -380,7 +383,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
- scheduleAnimateResizePip(currentBounds, destinationBounds,
+ final Rect sourceHintRect = getValidSourceHintRect(info, currentBounds);
+ scheduleAnimateResizePip(currentBounds, destinationBounds, sourceHintRect,
TRANSITION_DIRECTION_TO_PIP, mEnterExitAnimationDuration,
null /* updateBoundsCallback */);
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -391,6 +395,21 @@ public class PipTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Returns the source hint rect if it is valid (if provided and is contained by the current
+ * task bounds).
+ */
+ private Rect getValidSourceHintRect(ActivityManager.RunningTaskInfo info, Rect sourceBounds) {
+ final Rect sourceHintRect = info.pictureInPictureParams != null
+ && info.pictureInPictureParams.hasSourceBoundsHint()
+ ? info.pictureInPictureParams.getSourceRectHint()
+ : null;
+ if (sourceHintRect != null && sourceBounds.contains(sourceHintRect)) {
+ return sourceHintRect;
+ }
+ return null;
+ }
+
private void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
// If we are fading the PIP in, then we should move the pip to the final location as
// soon as possible, but set the alpha immediately since the transaction can take a
@@ -611,13 +630,13 @@ public class PipTaskOrganizer extends TaskOrganizer implements
Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
return;
}
- scheduleAnimateResizePip(mLastReportedBounds, toBounds,
+ scheduleAnimateResizePip(mLastReportedBounds, toBounds, null /* sourceHintRect */,
TRANSITION_DIRECTION_NONE, duration, updateBoundsCallback);
}
private void scheduleAnimateResizePip(Rect currentBounds, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction, int durationMs,
- Consumer<Rect> updateBoundsCallback) {
+ Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction,
+ int durationMs, Consumer<Rect> updateBoundsCallback) {
if (!mInPip) {
// TODO: tend to use shouldBlockResizeRequest here as well but need to consider
// the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
@@ -629,6 +648,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements
args.arg1 = updateBoundsCallback;
args.arg2 = currentBounds;
args.arg3 = destinationBounds;
+ args.arg4 = sourceHintRect;
args.argi1 = direction;
args.argi2 = durationMs;
mUpdateHandler.sendMessage(mUpdateHandler.obtainMessage(MSG_RESIZE_ANIMATE, args));
@@ -732,7 +752,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
}
final Rect destinationBounds = new Rect(originalBounds);
destinationBounds.offset(xOffset, yOffset);
- animateResizePip(originalBounds, destinationBounds, TRANSITION_DIRECTION_SAME, durationMs);
+ animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
+ TRANSITION_DIRECTION_SAME, durationMs);
}
private void resizePip(Rect destinationBounds) {
@@ -838,7 +859,8 @@ public class PipTaskOrganizer extends TaskOrganizer implements
return WINDOWING_MODE_UNDEFINED;
}
- private void animateResizePip(Rect currentBounds, Rect destinationBounds,
+
+ private void animateResizePip(Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
@PipAnimationController.TransitionDirection int direction, int durationMs) {
if (Looper.myLooper() != mUpdateHandler.getLooper()) {
throw new RuntimeException("Callers should call scheduleAnimateResizePip() instead of "
@@ -850,7 +872,7 @@ public class PipTaskOrganizer extends TaskOrganizer implements
return;
}
mPipAnimationController
- .getAnimator(mLeash, currentBounds, destinationBounds)
+ .getAnimator(mLeash, currentBounds, destinationBounds, sourceHintRect)
.setTransitionDirection(direction)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(durationMs)
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 40a86b78d3ad..7d35416a8d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -36,7 +36,6 @@ import android.util.Log;
import android.util.Pair;
import android.view.DisplayInfo;
import android.view.IPinnedStackController;
-import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
import com.android.systemui.Dependency;
@@ -96,7 +95,9 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
if (!mPipTaskOrganizer.isInPip() || mPipTaskOrganizer.isDeferringEnterPipAnimation()) {
- // Skip if we aren't in PIP or haven't actually entered PIP yet
+ // Skip if we aren't in PIP or haven't actually entered PIP yet. We still need to update
+ // the display layout in the bounds handler in this case.
+ mPipBoundsHandler.onDisplayRotationChangedNotInPip(toRotation);
return;
}
// If there is an animation running (ie. from a shelf offset), then ensure that we calculate
@@ -174,7 +175,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+ if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index a4edacecfd91..1ca53f907994 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -55,7 +55,9 @@ import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.pip.PipTaskOrganizer;
import com.android.systemui.util.DeviceConfigProxy;
+import java.io.PrintWriter;
import java.util.concurrent.Executor;
+import java.util.function.Function;
import java.util.function.Supplier;
/**
@@ -94,7 +96,7 @@ public class PipResizeGestureHandler {
private final Rect mTmpBottomLeftCorner = new Rect();
private final Rect mTmpBottomRightCorner = new Rect();
private final Rect mDisplayBounds = new Rect();
- private final Supplier<Rect> mMovementBoundsSupplier;
+ private final Function<Rect, Rect> mMovementBoundsSupplier;
private final Runnable mUpdateMovementBoundsRunnable;
private int mDelta;
@@ -113,7 +115,7 @@ public class PipResizeGestureHandler {
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
- PipTaskOrganizer pipTaskOrganizer, Supplier<Rect> movementBoundsSupplier,
+ PipTaskOrganizer pipTaskOrganizer, Function<Rect, Rect> movementBoundsSupplier,
Runnable updateMovementBoundsRunnable, SysUiState sysUiState) {
mContext = context;
mDisplayId = context.getDisplayId();
@@ -244,10 +246,15 @@ public class PipResizeGestureHandler {
return mTmpRegion.contains(x, y);
}
+ public boolean willStartResizeGesture(MotionEvent ev) {
+ return mEnableUserResize && isInValidSysUiState()
+ && isWithinTouchRegion((int) ev.getRawX(), (int) ev.getRawY());
+ }
+
private void setCtrlType(int x, int y) {
final Rect currentPipBounds = mMotionHelper.getBounds();
- Rect movementBounds = mMovementBoundsSupplier.get();
+ Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);
mDisplayBounds.set(movementBounds.left,
movementBounds.top,
movementBounds.right + currentPipBounds.width(),
@@ -353,6 +360,16 @@ public class PipResizeGestureHandler {
mMinSize.set(minX, minY);
}
+ public void dump(PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture);
+ pw.println(innerPrefix + "mIsAttached=" + mIsAttached);
+ pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled);
+ pw.println(innerPrefix + "mEnableUserResize=" + mEnableUserResize);
+ pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed);
+ }
+
class SysUiInputEventReceiver extends BatchedInputEventReceiver {
SysUiInputEventReceiver(InputChannel channel, Looper looper) {
super(channel, looper, Choreographer.getSfInstance());
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 79f99f459ace..b6e4e1628c20 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -303,8 +303,11 @@ public class PipTouchHandler {
hideDismissTarget();
});
- MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext,
- PipUtils.getTopPipActivity(mContext, mActivityManager));
+ Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext,
+ mActivityManager);
+ if (topPipActivity.first != null) {
+ MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, topPipActivity);
+ }
}
});
@@ -641,12 +644,12 @@ public class PipTouchHandler {
}
MotionEvent ev = (MotionEvent) inputEvent;
- if (!mTouchState.isDragging()
- && !mMagnetizedPip.getObjectStuckToTarget()
- && !mMotionHelper.isAnimating()
- && mPipResizeGestureHandler.isWithinTouchRegion(
- (int) ev.getRawX(), (int) ev.getRawY())) {
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
+ && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
+ // Initialize the touch state for the gesture, but immediately reset to invalidate the
+ // gesture
mTouchState.onTouchEvent(ev);
+ mTouchState.reset();
return true;
}
@@ -1029,8 +1032,11 @@ public class PipTouchHandler {
isMenuExpanded && willResizeMenu() ? mExpandedShortestEdgeSize : 0);
}
- private Rect getMovementBounds() {
- return mMovementBounds;
+ private Rect getMovementBounds(Rect curBounds) {
+ Rect movementBounds = new Rect();
+ mSnapAlgorithm.getMovementBounds(curBounds, mInsetBounds,
+ movementBounds, mIsImeShowing ? mImeHeight : 0);
+ return movementBounds;
}
/**
@@ -1062,6 +1068,9 @@ public class PipTouchHandler {
pw.println(innerPrefix + "mMovementBoundsExtraOffsets=" + mMovementBoundsExtraOffsets);
mTouchState.dump(pw, innerPrefix);
mMotionHelper.dump(pw, innerPrefix);
+ if (mPipResizeGestureHandler != null) {
+ mPipResizeGestureHandler.dump(pw, innerPrefix);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 628223630af7..4b2c27321035 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -716,7 +716,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+ if (task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index b5afe771926c..b07b1a9561ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -44,6 +44,7 @@ import android.view.DisplayCutout;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@@ -146,6 +147,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
private boolean mHasTopCutout = false;
+ private int mStatusBarPaddingTop = 0;
private int mRoundedCornerPadding = 0;
private int mContentMarginStart;
private int mContentMarginEnd;
@@ -339,6 +341,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mRoundedCornerPadding = resources.getDimensionPixelSize(
R.dimen.rounded_corner_content_padding);
+ mStatusBarPaddingTop = resources.getDimensionPixelSize(R.dimen.status_bar_padding_top);
// Update height for a few views, especially due to landscape mode restricting space.
mHeaderTextContainerView.getLayoutParams().height =
@@ -460,6 +463,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private void updateClockPadding() {
int clockPaddingLeft = 0;
int clockPaddingRight = 0;
+
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ int leftMargin = lp.leftMargin;
+ int rightMargin = lp.rightMargin;
+
// The clock might collide with cutouts, let's shift it out of the way.
// We only do that if the inset is bigger than our own padding, since it's nicer to
// align with
@@ -467,16 +475,19 @@ public class QuickStatusBarHeader extends RelativeLayout implements
// if there's a cutout, let's use at least the rounded corner inset
int cutoutPadding = Math.max(mCutOutPaddingLeft, mRoundedCornerPadding);
int contentMarginLeft = isLayoutRtl() ? mContentMarginEnd : mContentMarginStart;
- clockPaddingLeft = Math.max(cutoutPadding - contentMarginLeft, 0);
+ clockPaddingLeft = Math.max(cutoutPadding - contentMarginLeft - leftMargin, 0);
}
if (mCutOutPaddingRight > 0) {
// if there's a cutout, let's use at least the rounded corner inset
int cutoutPadding = Math.max(mCutOutPaddingRight, mRoundedCornerPadding);
int contentMarginRight = isLayoutRtl() ? mContentMarginStart : mContentMarginEnd;
- clockPaddingRight = Math.max(cutoutPadding - contentMarginRight, 0);
+ clockPaddingRight = Math.max(cutoutPadding - contentMarginRight - rightMargin, 0);
}
- mSystemIconsView.setPadding(clockPaddingLeft, mWaterfallTopInset, clockPaddingRight, 0);
+ mSystemIconsView.setPadding(clockPaddingLeft,
+ mWaterfallTopInset + mStatusBarPaddingTop,
+ clockPaddingRight,
+ 0);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 8e878ddc6da1..d6e1a16bc69e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
@@ -72,6 +73,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateInterpolator;
@@ -87,6 +89,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.ArrayList;
@@ -220,6 +223,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private MediaActionSound mCameraSound;
+ private int mNavMode;
+ private int mLeftInset;
+ private int mRightInset;
+
// standard material ease
private final Interpolator mFastOutSlowIn;
@@ -301,6 +308,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mDismissButton.getBoundsOnScreen(dismissRect);
touchRegion.op(dismissRect, Region.Op.UNION);
+ if (QuickStepContract.isGesturalMode(mNavMode)) {
+ // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
+ Rect inset = new Rect(0, 0, mLeftInset, mDisplayMetrics.heightPixels);
+ touchRegion.op(inset, Region.Op.UNION);
+ inset.set(mDisplayMetrics.widthPixels - mRightInset, 0, mDisplayMetrics.widthPixels,
+ mDisplayMetrics.heightPixels);
+ touchRegion.op(inset, Region.Op.UNION);
+ }
+
inoutInfo.touchableRegion.set(touchRegion);
}
@@ -356,6 +372,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (needsUpdate) {
reloadAssets();
}
+
+ mNavMode = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
}
/**
@@ -370,6 +389,25 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
// Inflate the screenshot layout
mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+ // TODO(159460485): Remove this when focus is handled properly in the system
+ mScreenshotLayout.setOnTouchListener((v, event) -> {
+ if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
+ // Once the user touches outside, stop listening for input
+ setWindowFocusable(false);
+ }
+ return false;
+ });
+ mScreenshotLayout.setOnApplyWindowInsetsListener((v, insets) -> {
+ if (QuickStepContract.isGesturalMode(mNavMode)) {
+ Insets gestureInsets = insets.getInsets(
+ WindowInsets.Type.systemGestures());
+ mLeftInset = gestureInsets.left;
+ mRightInset = gestureInsets.right;
+ } else {
+ mLeftInset = mRightInset = 0;
+ }
+ return mScreenshotLayout.onApplyWindowInsets(insets);
+ });
mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -432,6 +470,21 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
}
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+ }
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
+ }
+ }
/**
* Creates a new worker thread and saves the screenshot to the media store.
@@ -500,6 +553,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
mDismissAnimation.cancel();
}
+
+ // The window is focusable by default
+ setWindowFocusable(true);
+
// Start the post-screenshot animation
startAnimation(finisher, screenRect, screenInsets, showFlash);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index b846aa08c33b..eca4c8082dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -316,7 +316,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* of the timer and should be removed externally.
* @return true if the notification is sticky
*/
- protected boolean isSticky() {
+ public boolean isSticky() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5628a24f40ef..739d30c2a707 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -100,12 +100,7 @@ public class NotificationMediaManager implements Dumpable {
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_PAUSED);
PAUSED_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
- }
- private static final HashSet<Integer> INACTIVE_MEDIA_STATES = new HashSet<>();
- static {
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_NONE);
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_STOPPED);
- INACTIVE_MEDIA_STATES.add(PlaybackState.STATE_ERROR);
+ PAUSED_MEDIA_STATES.add(PlaybackState.STATE_CONNECTING);
}
private final NotificationEntryManager mEntryManager;
@@ -262,15 +257,6 @@ public class NotificationMediaManager implements Dumpable {
return !PAUSED_MEDIA_STATES.contains(state);
}
- /**
- * Check if a state should be considered active (playing or paused)
- * @param state a PlaybackState
- * @return true if active
- */
- public static boolean isActiveState(int state) {
- return !INACTIVE_MEDIA_STATES.contains(state);
- }
-
public void setUpWithPresenter(NotificationPresenter presenter) {
mPresenter = presenter;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index c1acfbadef45..285cf7abce20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -382,9 +382,8 @@ public class NotifCollection implements Dumpable {
final NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
- crashIfNotInitializing(
- new IllegalStateException("No notification to remove with key "
- + sbn.getKey()));
+ // TODO (b/160008901): Throw an exception here
+ mLogger.logNoNotificationToRemoveWithKey(sbn.getKey());
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 76751eaaecb1..f8a778d6b1d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -121,6 +121,14 @@ class NotifCollectionLogger @Inject constructor(
})
}
+ fun logNoNotificationToRemoveWithKey(key: String) {
+ buffer.log(TAG, ERROR, {
+ str1 = key
+ }, {
+ "No notification to remove with key $str1"
+ })
+ }
+
fun logRankingMissing(key: String, rankingMap: RankingMap) {
buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index 52f7c2cfee96..7bd192d850c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -124,6 +124,9 @@ public class NotificationInlineImageResolver implements ImageResolver {
*/
Drawable resolveImage(Uri uri) throws IOException {
BitmapDrawable image = resolveImageInternal(uri);
+ if (image == null || image.getBitmap() == null) {
+ throw new IOException("resolveImageInternal returned null for uri: " + uri);
+ }
Bitmap bitmap = image.getBitmap();
image.setBitmap(Icon.scaleDownIfNecessary(bitmap, mMaxImageWidth, mMaxImageHeight));
return image;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 3dcf7ed674c7..e05ba12781c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -428,7 +428,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
@Override
- protected boolean isSticky() {
+ public boolean isSticky() {
return super.isSticky() || mMenuShownPinned;
}
@@ -568,6 +568,17 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
}
mKeysToRemoveWhenLeavingKeyguard.clear();
}
+ if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
+ ArrayList<String> keysToRemove = new ArrayList<>();
+ for (AlertEntry entry : mAlertEntries.values()) {
+ if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
+ keysToRemove.add(entry.mEntry.getKey());
+ }
+ }
+ for (String key : keysToRemove) {
+ removeAlertEntry(key);
+ }
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 39949c82661f..b6a284c5e3c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -26,7 +26,6 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import android.util.MathUtils;
-import android.util.Slog;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -169,7 +168,7 @@ public class KeyguardBouncer {
// This condition may indicate an error on Android, so log it.
if (!allowDismissKeyguard) {
- Slog.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " + keyguardUserId);
+ Log.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " + keyguardUserId);
}
mShowingSoon = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c32133119386..375af6b099c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -435,6 +435,11 @@ public class NotificationPanelViewController extends PanelViewController {
private Runnable mExpandAfterLayoutRunnable;
/**
+ * Is this a collapse that started on the panel where we should allow the panel to intercept
+ */
+ private boolean mIsPanelCollapseOnQQS;
+
+ /**
* If face auth with bypass is running for the first time after you turn on the screen.
* (From aod or screen off)
*/
@@ -1064,7 +1069,11 @@ public class NotificationPanelViewController extends PanelViewController {
mInitialTouchX = x;
initVelocityTracker();
trackMovement(event);
- if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+ if (mKeyguardShowing
+ && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+ // Dragging down on the lockscreen statusbar should prohibit other interactions
+ // immediately, otherwise we'll wait on the touchslop. This is to allow
+ // dragging down to expanded quick settings directly on the lockscreen.
mView.getParent().requestDisallowInterceptTouchEvent(true);
}
if (mQsExpansionAnimator != null) {
@@ -1097,9 +1106,10 @@ public class NotificationPanelViewController extends PanelViewController {
trackMovement(event);
return true;
}
- if (Math.abs(h) > getTouchSlop(event)
+ if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded))
&& Math.abs(h) > Math.abs(x - mInitialTouchX)
&& shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
+ mView.getParent().requestDisallowInterceptTouchEvent(true);
mQsTracking = true;
onQsExpansionStarted();
notifyExpandingFinished();
@@ -1139,6 +1149,7 @@ public class NotificationPanelViewController extends PanelViewController {
mDownX = event.getX();
mDownY = event.getY();
mCollapsedOnDown = isFullyCollapsed();
+ mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY);
mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
@@ -1154,6 +1165,24 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
+ /**
+ * Can the panel collapse in this motion because it was started on QQS?
+ *
+ * @param downX the x location where the touch started
+ * @param downY the y location where the touch started
+ *
+ * @return true if the panel could be collapsed because it stared on QQS
+ */
+ private boolean canPanelCollapseOnQQS(float downX, float downY) {
+ if (mCollapsedOnDown || mKeyguardShowing || mQsExpanded) {
+ return false;
+ }
+ View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
+ && downY <= header.getBottom();
+
+ }
+
private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
float vel = getCurrentQSVelocity();
final boolean expandsQs = flingExpandsQs(vel);
@@ -1903,10 +1932,11 @@ public class NotificationPanelViewController extends PanelViewController {
}
@Override
- protected boolean isScrolledToBottom() {
+ protected boolean canCollapsePanelOnTouch() {
if (!isInSettings()) {
return mBarState == StatusBarState.KEYGUARD
- || mNotificationStackScroller.isScrolledToBottom();
+ || mNotificationStackScroller.isScrolledToBottom()
+ || mIsPanelCollapseOnQQS;
} else {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index caddc4a874f4..732f25f90eb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -460,7 +460,7 @@ public abstract class PanelViewController {
}
}
- protected boolean isScrolledToBottom() {
+ protected boolean canCollapsePanelOnTouch() {
return true;
}
@@ -1081,7 +1081,7 @@ public abstract class PanelViewController {
* upwards. This allows closing the shade from anywhere inside the panel.
*
* We only do this if the current content is scrolled to the bottom,
- * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling
+ * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling
* gesture
* possible.
*/
@@ -1092,7 +1092,7 @@ public abstract class PanelViewController {
}
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
- boolean scrolledToBottom = isScrolledToBottom();
+ boolean canCollapsePanel = canCollapsePanelOnTouch();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -1139,7 +1139,7 @@ public abstract class PanelViewController {
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
addMovement(event);
- if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
float hAbs = Math.abs(h);
float touchSlop = getTouchSlop(event);
if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 0d5a14960850..07de388598b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -368,7 +368,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
protected boolean expanded;
@Override
- protected boolean isSticky() {
+ public boolean isSticky() {
return (mEntry.isRowPinned() && expanded)
|| remoteInputActive || hasFullScreenIntent(mEntry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 19c6b806bb59..3347cf6ca2a4 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -20,9 +20,11 @@ import android.content.Context
import android.graphics.Canvas
import android.graphics.PointF
import android.graphics.Rect
+import android.text.Layout
import android.util.AttributeSet
import android.view.View
import android.view.ViewTreeObserver
+import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.statusbar.CrossFadeHelper
@@ -45,12 +47,29 @@ class TransitionLayout @JvmOverloads constructor(
private var currentState: TransitionViewState = TransitionViewState()
private var updateScheduled = false
+ private var desiredMeasureWidth = 0
+ private var desiredMeasureHeight = 0
/**
* The measured state of this view which is the one we will lay ourselves out with. This
* may differ from the currentState if there is an external animation or transition running.
* This state will not be used to measure the widgets, where the current state is preferred.
*/
var measureState: TransitionViewState = TransitionViewState()
+ set(value) {
+ val newWidth = value.width
+ val newHeight = value.height
+ if (newWidth != desiredMeasureWidth || newHeight != desiredMeasureHeight) {
+ desiredMeasureWidth = newWidth
+ desiredMeasureHeight = newHeight
+ // We need to make sure next time we're measured that our onMeasure will be called.
+ // Otherwise our parent thinks we still have the same height
+ if (isInLayout()) {
+ forceLayout()
+ } else {
+ requestLayout()
+ }
+ }
+ }
private val preDrawApplicator = object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
updateScheduled = false
@@ -85,6 +104,23 @@ class TransitionLayout @JvmOverloads constructor(
for (i in 0 until childCount) {
val child = getChildAt(i)
val widgetState = currentState.widgetStates.get(child.id) ?: continue
+
+ // TextViews which are measured and sized differently should be handled with a
+ // "clip mode", which means we clip explicitly rather than implicitly by passing
+ // different sizes to measure/layout than setLeftTopRightBottom.
+ // Then to accommodate RTL text, we need a "clip shift" which allows us to have the
+ // clipBounds be attached to the right side of the view instead of the left.
+ val clipModeShift =
+ if (child is TextView && widgetState.width < widgetState.measureWidth) {
+ if (child.layout.getParagraphDirection(0) == Layout.DIR_RIGHT_TO_LEFT) {
+ widgetState.measureWidth - widgetState.width
+ } else {
+ 0
+ }
+ } else {
+ null
+ }
+
if (child.measuredWidth != widgetState.measureWidth ||
child.measuredHeight != widgetState.measureHeight) {
val measureWidthSpec = MeasureSpec.makeMeasureSpec(widgetState.measureWidth,
@@ -94,14 +130,17 @@ class TransitionLayout @JvmOverloads constructor(
child.measure(measureWidthSpec, measureHeightSpec)
child.layout(0, 0, child.measuredWidth, child.measuredHeight)
}
- val left = widgetState.x.toInt() + contentTranslationX
+ val clipShift = clipModeShift ?: 0
+ val left = widgetState.x.toInt() + contentTranslationX - clipShift
val top = widgetState.y.toInt() + contentTranslationY
- child.setLeftTopRightBottom(left, top, left + widgetState.width,
- top + widgetState.height)
+ val clipMode = clipModeShift != null
+ val boundsWidth = if (clipMode) widgetState.measureWidth else widgetState.width
+ val boundsHeight = if (clipMode) widgetState.measureHeight else widgetState.height
+ child.setLeftTopRightBottom(left, top, left + boundsWidth, top + boundsHeight)
child.scaleX = widgetState.scale
child.scaleY = widgetState.scale
val clipBounds = child.clipBounds ?: Rect()
- clipBounds.set(0, 0, widgetState.width, widgetState.height)
+ clipBounds.set(clipShift, 0, widgetState.width + clipShift, widgetState.height)
child.clipBounds = clipBounds
CrossFadeHelper.fadeIn(child, widgetState.alpha)
child.visibility = if (widgetState.gone || widgetState.alpha == 0.0f) {
@@ -136,7 +175,7 @@ class TransitionLayout @JvmOverloads constructor(
MeasureSpec.EXACTLY)
child.measure(measureWidthSpec, measureHeightSpec)
}
- setMeasuredDimension(measureState.width, measureState.height)
+ setMeasuredDimension(desiredMeasureWidth, desiredMeasureHeight)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index ed4e6865e508..315caeebe0e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -107,6 +107,9 @@ public class BubbleDataTest extends SysuiTestCase {
@Mock
private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
+ @Mock
+ private BubbleController.PendingIntentCanceledListener mPendingIntentCanceledListener;
+
@Before
public void setUp() throws Exception {
mNotificationTestHelper = new NotificationTestHelper(
@@ -127,20 +130,20 @@ public class BubbleDataTest extends SysuiTestCase {
modifyRanking(mEntryInterruptive)
.setVisuallyInterruptive(true)
.build();
- mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener);
+ mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null);
ExpandableNotificationRow row = mNotificationTestHelper.createBubble();
mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d");
mEntryDismissed.setRow(row);
- mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener);
+ mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null);
- mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener);
- mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener);
- mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener);
- mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener);
- mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener);
- mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener);
- mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener);
+ mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener);
+ mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener);
mBubbleData = new BubbleData(getContext());
@@ -847,14 +850,6 @@ public class BubbleDataTest extends SysuiTestCase {
when(entry.getSbn().getPostTime()).thenReturn(postTime);
}
- private void setOngoing(NotificationEntry entry, boolean ongoing) {
- if (ongoing) {
- entry.getSbn().getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
- } else {
- entry.getSbn().getNotification().flags &= ~Notification.FLAG_FOREGROUND_SERVICE;
- }
- }
-
/**
* No ExpandableNotificationRow is required to test BubbleData. This setup is all that is
* required for BubbleData functionality and verification. NotificationTestHelper is used only
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 be03923e7264..2bcc22c4b99e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleTest.java
@@ -71,7 +71,7 @@ public class BubbleTest extends SysuiTestCase {
.setNotification(mNotif)
.build();
- mBubble = new Bubble(mEntry, mSuppressionListener);
+ mBubble = new Bubble(mEntry, mSuppressionListener, null);
Intent target = new Intent(mContext, BubblesTestActivity.class);
Notification.BubbleMetadata metadata = new Notification.BubbleMetadata.Builder(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index b7f317b38743..c63781cb110a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -64,6 +64,7 @@ private const val DEVICE_NAME = "DEVICE_NAME"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val USER_ID = 0
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -180,7 +181,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindWhenUnattached() {
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, null, null, device, true, null)
player.bind(state)
assertThat(player.isPlaying()).isFalse()
@@ -189,7 +190,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindText() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
assertThat(appName.getText()).isEqualTo(APP)
@@ -200,7 +201,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindBackgroundColor() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
val list = ArgumentCaptor.forClass(ColorStateList::class.java)
@@ -211,7 +212,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDevice() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null)
player.bind(state)
assertThat(seamlessText.getText()).isEqualTo(DEVICE_NAME)
@@ -223,7 +224,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
seamless.id = 1
seamlessFallback.id = 2
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
player.bind(state)
verify(expandedSet).setVisibility(seamless.id, View.GONE)
@@ -235,7 +236,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindNullDevice() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
player.bind(state)
assertThat(seamless.isEnabled()).isTrue()
@@ -246,7 +247,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDeviceResumptionPlayer() {
player.attach(holder)
- val state = MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, device, true, null,
resumption = true)
player.bind(state)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 9fdd9ad744ff..dbc5596d9f4e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -39,6 +39,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import java.util.ArrayList;
+import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -52,6 +53,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
private static final String ARTIST = "ARTIST";
private static final String TITLE = "TITLE";
private static final String DEVICE_NAME = "DEVICE_NAME";
+ private static final int USER_ID = 0;
private MediaDataCombineLatest mManager;
@@ -78,7 +80,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
mManager.addListener(mListener);
- mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ mMediaData = new MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, true, null, false,
KEY, false);
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
@@ -158,6 +160,18 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
verify(mListener).onMediaDataLoaded(eq("NEW_KEY"), any(), captor.capture());
}
+ @Test
+ public void getDataIncludesDevice() {
+ // GIVEN that device and media events have been received
+ mDeviceListener.onMediaDeviceChanged(KEY, mDeviceData);
+ mDataListener.onMediaDataLoaded(KEY, null, mMediaData);
+
+ // THEN the result of getData includes device info
+ Map<String, MediaData> results = mManager.getData();
+ assertThat(results.get(KEY)).isNotNull();
+ assertThat(results.get(KEY).getDevice()).isEqualTo(mDeviceData);
+ }
+
private MediaDataManager.Listener captureDataListener() {
ArgumentCaptor<MediaDataManager.Listener> captor = ArgumentCaptor.forClass(
MediaDataManager.Listener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
new file mode 100644
index 000000000000..afb64a7649b4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media
+
+import android.graphics.Color
+import androidx.test.filters.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+
+private const val KEY = "TEST_KEY"
+private const val KEY_ALT = "TEST_KEY_2"
+private const val USER_MAIN = 0
+private const val USER_GUEST = 10
+private const val APP = "APP"
+private const val BG_COLOR = Color.RED
+private const val PACKAGE = "PKG"
+private const val ARTIST = "ARTIST"
+private const val TITLE = "TITLE"
+private const val DEVICE_NAME = "DEVICE_NAME"
+
+private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
+private fun <T> any(): T = Mockito.any()
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class MediaDataFilterTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var combineLatest: MediaDataCombineLatest
+ @Mock
+ private lateinit var listener: MediaDataManager.Listener
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var mediaResumeListener: MediaResumeListener
+ @Mock
+ private lateinit var mediaDataManager: MediaDataManager
+ @Mock
+ private lateinit var lockscreenUserManager: NotificationLockscreenUserManager
+ @Mock
+ private lateinit var executor: Executor
+
+ private lateinit var mediaDataFilter: MediaDataFilter
+ private lateinit var dataMain: MediaData
+ private lateinit var dataGuest: MediaData
+ private val device = MediaDeviceData(true, null, DEVICE_NAME)
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mediaDataFilter = MediaDataFilter(combineLatest, broadcastDispatcher, mediaResumeListener,
+ mediaDataManager, lockscreenUserManager, executor)
+ mediaDataFilter.addListener(listener)
+
+ // Start all tests as main user
+ setUser(USER_MAIN)
+
+ // Set up test media data
+ dataMain = MediaData(USER_MAIN, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
+ emptyList(), PACKAGE, null, null, device, true, null)
+
+ dataGuest = MediaData(USER_GUEST, true, BG_COLOR, APP, null, ARTIST, TITLE, null,
+ emptyList(), emptyList(), PACKAGE, null, null, device, true, null)
+ }
+
+ private fun setUser(id: Int) {
+ `when`(lockscreenUserManager.isCurrentProfile(anyInt())).thenReturn(false)
+ `when`(lockscreenUserManager.isCurrentProfile(eq(id))).thenReturn(true)
+ mediaDataFilter.handleUserSwitched(id)
+ }
+
+ @Test
+ fun testOnDataLoadedForCurrentUser_callsListener() {
+ // GIVEN a media for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ // THEN we should tell the listener
+ verify(listener).onMediaDataLoaded(eq(KEY), eq(null), eq(dataMain))
+ }
+
+ @Test
+ fun testOnDataLoadedForGuest_doesNotCallListener() {
+ // GIVEN a media for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+
+ // THEN we should NOT tell the listener
+ verify(listener, never()).onMediaDataLoaded(any(), any(), any())
+ }
+
+ @Test
+ fun testOnRemovedForCurrent_callsListener() {
+ // GIVEN a media was removed for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ // THEN we should tell the listener
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnRemovedForGuest_doesNotCallListener() {
+ // GIVEN a media was removed for guest user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataGuest)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+
+ // THEN we should NOT tell the listener
+ verify(listener, never()).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnUserSwitched_removesOldUserControls() {
+ // GIVEN that we have a media loaded for main user
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should remove the main user's media
+ verify(listener).onMediaDataRemoved(eq(KEY))
+ }
+
+ @Test
+ fun testOnUserSwitched_addsNewUserControls() {
+ // GIVEN that we had some media for both users
+ val dataMap = mapOf(KEY to dataMain, KEY_ALT to dataGuest)
+ `when`(combineLatest.getData()).thenReturn(dataMap)
+
+ // and we switch to guest user
+ setUser(USER_GUEST)
+
+ // THEN we should add back the guest user media
+ verify(listener).onMediaDataLoaded(eq(KEY_ALT), eq(null), eq(dataGuest))
+
+ // but not the main user's
+ verify(listener, never()).onMediaDataLoaded(eq(KEY), any(), eq(dataMain))
+ }
+
+ @Test
+ fun testHasAnyMedia() {
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+ assertThat(mediaDataFilter.hasAnyMedia()).isTrue()
+ }
+
+ @Test
+ fun testHasActiveMedia() {
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ val data = dataMain.copy(active = true)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ assertThat(mediaDataFilter.hasActiveMedia()).isTrue()
+ }
+
+ @Test
+ fun testHasAnyMedia_onlyCurrentUser() {
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataGuest)
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+ }
+
+ @Test
+ fun testHasActiveMedia_onlyCurrentUser() {
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ val data = dataGuest.copy(active = true)
+
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = data)
+ assertThat(mediaDataFilter.hasActiveMedia()).isFalse()
+ }
+
+ @Test
+ fun testOnNotificationRemoved_doesntHaveMedia() {
+ mediaDataFilter.onMediaDataLoaded(KEY, oldKey = null, data = dataMain)
+ mediaDataFilter.onMediaDataRemoved(KEY)
+ assertThat(mediaDataFilter.hasAnyMedia()).isFalse()
+ }
+
+ @Test
+ fun testOnSwipeToDismiss_setsTimedOut() {
+ mediaDataFilter.onMediaDataLoaded(KEY, null, dataMain)
+ mediaDataFilter.onSwipeToDismiss()
+
+ verify(mediaDataManager).setTimedOut(eq(KEY), eq(true))
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index e56bbabfdc0b..6761b282b26a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -35,6 +35,7 @@ private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "SystemUI"
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
+private const val USER_ID = 0
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -91,28 +92,15 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testHasActiveMedia() {
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
- val data = mock(MediaData::class.java)
-
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
-
- whenever(data.active).thenReturn(true)
- assertThat(mediaDataManager.hasActiveMedia()).isTrue()
- }
-
- @Test
- fun testOnSwipeToDismiss_deactivatesMedia() {
- val data = MediaData(initialized = true, backgroundColor = 0, app = null, appIcon = null,
- artist = null, song = null, artwork = null, actions = emptyList(),
+ fun testSetTimedOut_deactivatesMedia() {
+ val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
+ appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
clickIntent = null, device = null, active = true, resumeAction = null)
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- mediaDataManager.onSwipeToDismiss()
+ mediaDataManager.setTimedOut(KEY, timedOut = true)
assertThat(data.active).isFalse()
}
@@ -141,37 +129,6 @@ class MediaDataManagerTest : SysuiTestCase() {
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
assertThat(listener.data!!.active).isTrue()
-
- // Swiping away makes the notification not active
- mediaDataManager.onSwipeToDismiss()
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
-
- // And when a notification is updated
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
- assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
-
- // MediaData should still be inactive
- assertThat(mediaDataManager.hasActiveMedia()).isFalse()
- }
-
- @Test
- fun testHasAnyMedia_whenAddingMedia() {
- assertThat(mediaDataManager.hasAnyMedia()).isFalse()
- val data = mock(MediaData::class.java)
-
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- assertThat(mediaDataManager.hasAnyMedia()).isTrue()
- }
-
- @Test
- fun testOnNotificationRemoved_doesntHaveMedia() {
- val data = mock(MediaData::class.java)
- mediaDataManager.onNotificationAdded(KEY, mediaNotification)
- mediaDataManager.onMediaDataLoaded(KEY, oldKey = null, data = data)
- mediaDataManager.onNotificationRemoved(KEY)
- assertThat(mediaDataManager.hasAnyMedia()).isFalse()
}
@Test
@@ -212,8 +169,8 @@ class MediaDataManagerTest : SysuiTestCase() {
setTitle(SESSION_TITLE)
build()
}
- mediaDataManager.addResumptionControls(desc, Runnable {}, session.sessionToken, APP_NAME,
- pendingIntent, PACKAGE_NAME)
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
assertThat(backgroundExecutor.runAllReady()).isEqualTo(1)
assertThat(foregroundExecutor.runAllReady()).isEqualTo(1)
// THEN the media data indicates that it is for resumption
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 6c7f2e8d7925..fc22eeb3ea68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -58,6 +58,7 @@ private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DEVICE_NAME = "DEVICE_NAME"
+private const val USER_ID = 0
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
@@ -118,7 +119,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
setSmallIcon(android.R.drawable.ic_media_pause)
setStyle(Notification.MediaStyle().setMediaSession(session.getSessionToken()))
}
- mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 916fd0fe11b7..7a8e4f7e9b85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -32,7 +32,9 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyLong
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
@@ -48,6 +50,7 @@ private const val PACKAGE = "PKG"
private const val SESSION_KEY = "SESSION_KEY"
private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
+private const val USER_ID = 0
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
private fun <T> anyObject(): T {
@@ -93,7 +96,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
setPlaybackState(playbackBuilder.build())
}
session.setActive(true)
- mediaData = MediaData(true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
}
@@ -118,6 +121,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
}
@Test
@@ -133,6 +137,24 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
@Test
+ fun testOnMediaDataLoaded_migratesKeys() {
+ // From not playing
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ clearInvocations(mediaController)
+
+ // To playing
+ val playingState = mock(android.media.session.PlaybackState::class.java)
+ `when`(playingState.state).thenReturn(PlaybackState.STATE_PLAYING)
+ `when`(mediaController.playbackState).thenReturn(playingState)
+ mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)
+ verify(mediaController).unregisterCallback(anyObject())
+ verify(mediaController).registerCallback(anyObject())
+
+ // Enqueues callback
+ verify(executor).execute(anyObject())
+ }
+
+ @Test
fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
// Assuming we're registered
testOnMediaDataLoaded_registersPlaybackListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
index b7a2633d0d36..536cae4380c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/pip/PipAnimationControllerTest.java
@@ -82,7 +82,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
@Test
public void getAnimator_withBounds_returnBoundsAnimator() {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, new Rect(), new Rect());
+ .getAnimator(mLeash, new Rect(), new Rect(), null);
assertEquals("Expect ANIM_TYPE_BOUNDS animation",
animator.getAnimationType(), PipAnimationController.ANIM_TYPE_BOUNDS);
@@ -94,12 +94,12 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1, null);
oldAnimator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue2);
+ .getAnimator(mLeash, startValue, endValue2, null);
assertEquals("getAnimator with same type returns same animator",
oldAnimator, newAnimator);
@@ -129,7 +129,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect endValue1 = new Rect(100, 100, 200, 200);
final Rect endValue2 = new Rect(200, 200, 300, 300);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue1);
+ .getAnimator(mLeash, startValue, endValue1, null);
animator.updateEndValue(endValue2);
@@ -141,7 +141,7 @@ public class PipAnimationControllerTest extends SysuiTestCase {
final Rect startValue = new Rect(0, 0, 100, 100);
final Rect endValue = new Rect(100, 100, 200, 200);
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
- .getAnimator(mLeash, startValue, endValue);
+ .getAnimator(mLeash, startValue, endValue, null);
animator.setSurfaceControlTransactionFactory(DummySurfaceControlTx::new);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 363fe95aae18..359faba48f08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1273,8 +1273,8 @@ public class NotifCollectionTest extends SysuiTestCase {
verify(mInterceptor3, never()).shouldInterceptDismissal(clearable);
}
- @Test(expected = IllegalStateException.class)
- public void testClearNotificationThrowsIfMissing() {
+ @Test
+ public void testClearNotificationDoesntThrowIfMissing() {
// GIVEN that enough time has passed that we're beyond the forgiveness window
mClock.advanceTime(5001);
@@ -1287,7 +1287,8 @@ public class NotifCollectionTest extends SysuiTestCase {
container.getSbn(),
new RankingMap(new Ranking[]{ container.getRanking() }));
- // THEN an exception is thrown
+ // THEN the event is ignored
+ verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt());
}
@Test
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index 2d3108a0bff1..591861f5b837 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -3,7 +3,7 @@
# If there are files in that filegroup that are not covered below, the classes in the
# module will be overwritten by the ones in the framework.
rule com.android.internal.util.** com.android.networkstack.tethering.util.@1
-rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index 1ea56cdf1a3d..ec2d2b02004e 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -8,4 +8,4 @@ rule com.android.internal.util.State* com.android.networkstack.tethering.util.St
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
-rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+rule android.util.LocalLog* com.android.networkstack.tethering.util.LocalLog@1
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d2b1bd1a6008..499a2711d8e6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -251,7 +251,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
//TODO: Remove this hack
private boolean mInitialized;
- private Point mTempPoint;
+ private Point mTempPoint = new Point();
private boolean mIsAccessibilityButtonShown;
private AccessibilityUserState getCurrentUserStateLocked() {
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index 373d47ed366b..6f2e6263b937 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -309,14 +309,9 @@ public class TouchExplorer extends BaseEventStreamTransformation
@Override
public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
- // Try to use the standard accessibility API to long click
- if (!mAms.performActionOnAccessibilityFocusedItem(
- AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) {
- Slog.e(LOG_TAG, "ACTION_LONG_CLICK failed.");
- if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
- sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
- mState.startDelegating();
- }
+ if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) {
+ sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
+ mState.startDelegating();
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 9b3d075e3f2c..7ab4369b338a 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -73,6 +73,7 @@ import android.service.autofill.FieldClassificationUserData;
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
+import android.service.autofill.InlinePresentation;
import android.service.autofill.InternalSanitizer;
import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
@@ -1437,7 +1438,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
mClientState = newClientState;
}
final Dataset dataset = (Dataset) result;
- authenticatedResponse.getDatasets().set(datasetIdx, dataset);
+ final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
+ if (!isPinnedDataset(oldDataset)) {
+ authenticatedResponse.getDatasets().set(datasetIdx, dataset);
+ }
autoFill(requestId, datasetIdx, dataset, false);
} else {
Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id "
@@ -1455,6 +1459,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
}
+ /**
+ * A dataset can potentially have multiple fields, and it's possible that some of the fields'
+ * has inline presentation and some don't. It's also possible that some of the fields'
+ * inline presentation is pinned and some isn't. So the concept of whether a dataset is
+ * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a
+ * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient
+ * for most of the cases.
+ */
+ private static boolean isPinnedDataset(@Nullable Dataset dataset) {
+ if (dataset != null && dataset.getFieldIds() != null) {
+ final int numOfFields = dataset.getFieldIds().size();
+ for (int i = 0; i < numOfFields; i++) {
+ final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(i);
+ if (inlinePresentation != null && inlinePresentation.isPinned()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mLock")
void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) {
final Dataset dataset = (data == null) ? null :
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index ef81d7159c42..29bb5428dd84 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -34,6 +34,7 @@ import android.hardware.usb.UsbManager;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -509,6 +510,14 @@ public class AdbService extends IAdbManager.Stub {
}
@Override
+ public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out,
+ ParcelFileDescriptor err, String[] args) {
+ return new AdbShellCommand(this).exec(
+ this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(),
+ args);
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java
new file mode 100644
index 000000000000..76918529d071
--- /dev/null
+++ b/services/core/java/com/android/server/adb/AdbShellCommand.java
@@ -0,0 +1,68 @@
+/*
+ * 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.adb;
+
+import android.os.BasicShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/**
+ * Interprets and executes 'adb shell cmd adb [args]'.
+ */
+class AdbShellCommand extends BasicShellCommandHandler {
+
+ private final AdbService mService;
+
+ AdbShellCommand(AdbService service) {
+ mService = Objects.requireNonNull(service);
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(null);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ switch (cmd) {
+ case "is-wifi-supported": {
+ pw.println(Boolean.toString(mService.isAdbWifiSupported()));
+ return 0;
+ }
+ case "is-wifi-qr-supported": {
+ pw.println(Boolean.toString(mService.isAdbWifiQrSupported()));
+ return 0;
+ }
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Adb service commands:");
+ pw.println(" help or -h");
+ pw.println(" Print this help text.");
+ pw.println(" is-wifi-supported");
+ pw.println(" Returns \"true\" if adb over wifi is supported.");
+ pw.println(" is-wifi-qr-supported");
+ pw.println(" Returns \"true\" if adb over wifi + QR pairing is supported.");
+ pw.println();
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c9dbacda368c..a38d42b92600 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18284,11 +18284,15 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- // Now safely dispatch changes to device idle controller.
- for (int i = 0; i < N; i++) {
- PendingTempWhitelist ptw = list[i];
- mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid,
- ptw.duration, true, ptw.tag);
+ // Now safely dispatch changes to device idle controller. Skip this if we're early
+ // in boot and the controller hasn't yet been brought online: we do not apply
+ // device idle policy anyway at this phase.
+ if (mLocalDeviceIdleController != null) {
+ for (int i = 0; i < N; i++) {
+ PendingTempWhitelist ptw = list[i];
+ mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid,
+ ptw.duration, true, ptw.tag);
+ }
}
// And now we can safely remove them from the map.
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 1cc41b22838e..5124c4a4797e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -904,6 +904,10 @@ public final class BroadcastQueue {
} else if (r.intent.getData() != null) {
b.append(r.intent.getData());
}
+ if (DEBUG_BROADCAST) {
+ Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration
+ + " : " + b.toString());
+ }
mService.tempWhitelistUidLocked(uid, duration, b.toString());
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index b5c173c91a53..6eab0221b7ab 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1597,7 +1597,8 @@ public class AppOpsService extends IAppOpsService.Stub {
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
packageUpdateFilter.addDataScheme("package");
- mContext.registerReceiver(mOnPackageUpdatedReceiver, packageUpdateFilter);
+ mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
+ packageUpdateFilter, null, null);
synchronized (this) {
for (int uidNum = mUidStates.size() - 1; uidNum >= 0; uidNum--) {
@@ -1640,7 +1641,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final IntentFilter packageSuspendFilter = new IntentFilter();
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
packageSuspendFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
- mContext.registerReceiver(new BroadcastReceiver() {
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final int[] changedUids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST);
@@ -1664,7 +1665,7 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
}
- }, packageSuspendFilter);
+ }, UserHandle.ALL, packageSuspendFilter, null, null);
final IntentFilter packageAddedFilter = new IntentFilter();
packageAddedFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 9acb47538043..15e8a92ba0e4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4047,7 +4047,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// Send it to window manager to hide IME from IME target window.
// TODO(b/139861270): send to mCurClient.client once IMMS is aware of
// actual IME target.
- mWindowManagerInternal.hideIme(mHideRequestWindowMap.get(windowToken));
+ mWindowManagerInternal.hideIme(
+ mHideRequestWindowMap.get(windowToken),
+ mCurClient.selfReportedDisplayId);
}
} else {
// Send to window manager to show IME after IME layout finishes.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 1a8de9771451..90370ddd21dd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1616,6 +1616,7 @@ public class LockSettingsService extends ILockSettings.Stub {
synchronized (mSeparateChallengeLock) {
if (!setLockCredentialInternal(credential, savedCredential,
userId, /* isLockTiedToParent= */ false)) {
+ scheduleGc();
return false;
}
setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null);
@@ -1626,6 +1627,7 @@ public class LockSettingsService extends ILockSettings.Stub {
setDeviceUnlockedForUser(userId);
}
notifySeparateProfileChallengeChanged(userId);
+ scheduleGc();
return true;
}
@@ -1965,7 +1967,11 @@ public class LockSettingsService extends ILockSettings.Stub {
public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId,
ICheckCredentialProgressCallback progressCallback) {
checkPasswordReadPermission(userId);
- return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
+ try {
+ return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback);
+ } finally {
+ scheduleGc();
+ }
}
@Override
@@ -1978,8 +1984,12 @@ public class LockSettingsService extends ILockSettings.Stub {
challengeType = CHALLENGE_NONE;
}
- return doVerifyCredential(credential, challengeType, challenge, userId,
- null /* progressCallback */);
+ try {
+ return doVerifyCredential(credential, challengeType, challenge, userId,
+ null /* progressCallback */);
+ } finally {
+ scheduleGc();
+ }
}
private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential,
@@ -2070,6 +2080,8 @@ public class LockSettingsService extends ILockSettings.Stub {
| BadPaddingException | CertificateException | IOException e) {
Slog.e(TAG, "Failed to decrypt child profile key", e);
throw new IllegalStateException("Unable to get tied profile token");
+ } finally {
+ scheduleGc();
}
}
@@ -2983,27 +2995,31 @@ public class LockSettingsService extends ILockSettings.Stub {
@Override
public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) {
checkPasswordReadPermission(userId);
- if (isManagedProfileWithUnifiedLock(userId)) {
- try {
- currentCredential = getDecryptedPasswordForTiedProfile(userId);
- } catch (Exception e) {
- Slog.e(TAG, "Failed to get work profile credential", e);
- return null;
- }
- }
- synchronized (mSpManager) {
- if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
- Slog.w(TAG, "Synthetic password not enabled");
- return null;
+ try {
+ if (isManagedProfileWithUnifiedLock(userId)) {
+ try {
+ currentCredential = getDecryptedPasswordForTiedProfile(userId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get work profile credential", e);
+ return null;
+ }
}
- long handle = getSyntheticPasswordHandleLocked(userId);
- AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
- getGateKeeperService(), handle, currentCredential, userId, null);
- if (auth.authToken == null) {
- Slog.w(TAG, "Current credential is incorrect");
- return null;
+ synchronized (mSpManager) {
+ if (!isSyntheticPasswordBasedCredentialLocked(userId)) {
+ Slog.w(TAG, "Synthetic password not enabled");
+ return null;
+ }
+ long handle = getSyntheticPasswordHandleLocked(userId);
+ AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword(
+ getGateKeeperService(), handle, currentCredential, userId, null);
+ if (auth.authToken == null) {
+ Slog.w(TAG, "Current credential is incorrect");
+ return null;
+ }
+ return auth.authToken.derivePasswordHashFactor();
}
- return auth.authToken.derivePasswordHashFactor();
+ } finally {
+ scheduleGc();
}
}
@@ -3287,6 +3303,22 @@ public class LockSettingsService extends ILockSettings.Stub {
}
}
+ /**
+ * Schedules garbage collection to sanitize lockscreen credential remnants in memory.
+ *
+ * One source of leftover lockscreen credentials is the unmarshalled binder method arguments.
+ * Since this method will be called within the binder implementation method, a small delay is
+ * added before the GC operation to allow the enclosing binder proxy code to complete and
+ * release references to the argument.
+ */
+ private void scheduleGc() {
+ mHandler.postDelayed(() -> {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }, 2000);
+ }
+
private class DeviceProvisionedObserver extends ContentObserver {
private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor(
Settings.Global.DEVICE_PROVISIONED);
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 25bbfa02fa05..3a4dfaf9bfcd 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -45,6 +45,7 @@ import com.android.internal.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -318,16 +319,6 @@ class BluetoothRouteProvider {
btRoute.route = builder.build();
}
- private void clearActiveRoutes() {
- if (DEBUG) {
- Log.d(TAG, "Clearing active routes");
- }
- for (BluetoothRouteInfo btRoute : mActiveRoutes) {
- setRouteConnectionState(btRoute, STATE_DISCONNECTED);
- }
- mActiveRoutes.clear();
- }
-
private void addActiveRoute(BluetoothRouteInfo btRoute) {
if (DEBUG) {
Log.d(TAG, "Adding active route: " + btRoute.route);
@@ -348,18 +339,34 @@ class BluetoothRouteProvider {
}
}
- private void findAndSetActiveHearingAidDevices() {
+ private void clearActiveRoutesWithType(int type) {
if (DEBUG) {
- Log.d(TAG, "Setting active hearing aid devices");
+ Log.d(TAG, "Clearing active routes with type. type=" + type);
+ }
+ Iterator<BluetoothRouteInfo> iter = mActiveRoutes.iterator();
+ while (iter.hasNext()) {
+ BluetoothRouteInfo btRoute = iter.next();
+ if (btRoute.route.getType() == type) {
+ iter.remove();
+ setRouteConnectionState(btRoute, STATE_DISCONNECTED);
+ }
}
+ }
- BluetoothHearingAid hearingAidProfile = mHearingAidProfile;
- if (hearingAidProfile == null) {
- return;
+ private void addActiveHearingAidDevices(BluetoothDevice device) {
+ if (DEBUG) {
+ Log.d(TAG, "Setting active hearing aid devices. device=" + device);
}
- List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices();
+
+ // Let the given device be the first active device
+ BluetoothRouteInfo activeBtRoute = mBluetoothRoutes.get(device.getAddress());
+ addActiveRoute(activeBtRoute);
+
+ // A bluetooth route with the same route ID should be added.
for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
- if (activeDevices.contains(btRoute.btDevice)) {
+ if (TextUtils.equals(btRoute.route.getId(), activeBtRoute.route.getId())
+ && !TextUtils.equals(btRoute.btDevice.getAddress(),
+ activeBtRoute.btDevice.getAddress())) {
addActiveRoute(btRoute);
}
}
@@ -465,16 +472,16 @@ class BluetoothRouteProvider {
public void onReceive(Context context, Intent intent, BluetoothDevice device) {
switch (intent.getAction()) {
case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
- clearActiveRoutes();
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
if (device != null) {
addActiveRoute(mBluetoothRoutes.get(device.getAddress()));
}
notifyBluetoothRoutesUpdated();
break;
case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
- clearActiveDevices();
+ clearActiveRoutesWithType(MediaRoute2Info.TYPE_HEARING_AID);
if (device != null) {
- findAndSetActiveHearingAidDevices();
+ addActiveHearingAidDevices(device);
}
notifyBluetoothRoutesUpdated();
break;
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 94b690a4dfce..9c3d6d352c89 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -102,9 +102,13 @@ public class ShortcutHelper {
HashMap<String, String> shortcutBubbles = mActiveShortcutBubbles.get(packageName);
ArrayList<String> bubbleKeysToRemove = new ArrayList<>();
if (shortcutBubbles != null) {
+ // Copy to avoid a concurrent modification exception when we remove bubbles from
+ // shortcutBubbles.
+ final Set<String> shortcutIds = new HashSet<>(shortcutBubbles.keySet());
+
// If we can't find one of our bubbles in the shortcut list, that bubble needs
// to be removed.
- for (String shortcutId : shortcutBubbles.keySet()) {
+ for (String shortcutId : shortcutIds) {
boolean foundShortcut = false;
for (int i = 0; i < shortcuts.size(); i++) {
if (shortcuts.get(i).getId().equals(shortcutId)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f827721be3b7..91b2ea1853fa 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -522,7 +522,9 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
-
+ // adb installs can override the installingPackageName, but not the
+ // initiatingPackageName
+ installerPackageName = null;
} else {
if (callingUid != Process.SYSTEM_UID) {
// The supplied installerPackageName must always belong to the calling app.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 088c5daf30a4..f533eb2cf2be 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -11083,6 +11083,21 @@ public class PackageManagerService extends IPackageManager.Stub
pkgSetting.forceQueryableOverride = true;
}
+ // If this is part of a standard install, set the initiating package name, else rely on
+ // previous device state.
+ if (reconciledPkg.installArgs != null) {
+ InstallSource installSource = reconciledPkg.installArgs.installSource;
+ if (installSource.initiatingPackageName != null) {
+ final PackageSetting ips = mSettings.mPackages.get(
+ installSource.initiatingPackageName);
+ if (ips != null) {
+ installSource = installSource.setInitiatingPackageSignatures(
+ ips.signatures);
+ }
+ }
+ pkgSetting.setInstallSource(installSource);
+ }
+
// TODO(toddke): Consider a method specifically for modifying the Package object
// post scan; or, moving this stuff out of the Package object since it has nothing
// to do with the package on disk.
@@ -15173,8 +15188,13 @@ public class PackageManagerService extends IPackageManager.Stub
idleController.addPowerSaveTempWhitelistAppDirect(Process.myUid(),
idleDuration,
false, "integrity component");
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(idleDuration);
+
mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM,
/* receiverPermission= */ null,
+ /* appOp= */ AppOpsManager.OP_NONE,
+ /* options= */ options.toBundle(),
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -15274,6 +15294,8 @@ public class PackageManagerService extends IPackageManager.Stub
DeviceIdleInternal idleController =
mInjector.getLocalDeviceIdleController();
final long idleDuration = getVerificationTimeout();
+ final BroadcastOptions options = BroadcastOptions.makeBasic();
+ options.setTemporaryAppWhitelistDuration(idleDuration);
/*
* If any sufficient verifiers were listed in the package
@@ -15293,7 +15315,9 @@ public class PackageManagerService extends IPackageManager.Stub
final Intent sufficientIntent = new Intent(verification);
sufficientIntent.setComponent(verifierComponent);
- mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
+ mContext.sendBroadcastAsUser(sufficientIntent, verifierUser,
+ /* receiverPermission= */ null,
+ options.toBundle());
}
}
}
@@ -15312,6 +15336,8 @@ public class PackageManagerService extends IPackageManager.Stub
verifierUser.getIdentifier(), false, "package verifier");
mContext.sendOrderedBroadcastAsUser(verification, verifierUser,
android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ /* appOp= */ AppOpsManager.OP_NONE,
+ /* options= */ options.toBundle(),
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -16078,16 +16104,7 @@ public class PackageManagerService extends IPackageManager.Stub
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
}
- if (installSource.initiatingPackageName != null) {
- final PackageSetting ips = mSettings.mPackages.get(
- installSource.initiatingPackageName);
- if (ips != null) {
- installSource = installSource.setInitiatingPackageSignatures(
- ips.signatures);
- }
- }
- ps.setInstallSource(installSource);
- mSettings.addInstallerPackageNames(installSource);
+ mSettings.addInstallerPackageNames(ps.installSource);
// When replacing an existing package, preserve the original install reason for all
// users that had the package installed before. Similarly for uninstall reasons.
@@ -19148,9 +19165,7 @@ public class PackageManagerService extends IPackageManager.Stub
final boolean systemApp = isSystemApp(ps);
final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
- if (ps.getPermissionsState().hasPermission(Manifest.permission.SUSPEND_APPS, userId)) {
- unsuspendForSuspendingPackage(packageName, userId);
- }
+
if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
&& userId != UserHandle.USER_ALL) {
// The caller is asking that the package only be deleted for a single
@@ -19208,6 +19223,20 @@ public class PackageManagerService extends IPackageManager.Stub
outInfo, writeSettings);
}
+ // If the package removed had SUSPEND_APPS, unset any restrictions that might have been in
+ // place for all affected users.
+ int[] affectedUserIds = (outInfo != null) ? outInfo.removedUsers : null;
+ if (affectedUserIds == null) {
+ affectedUserIds = resolveUserIds(userId);
+ }
+ for (final int affectedUserId : affectedUserIds) {
+ if (ps.getPermissionsState().hasPermission(Manifest.permission.SUSPEND_APPS,
+ affectedUserId)) {
+ unsuspendForSuspendingPackage(packageName, affectedUserId);
+ removeAllDistractingPackageRestrictions(affectedUserId);
+ }
+ }
+
// Take a note whether we deleted the package for all users
if (outInfo != null) {
outInfo.removedForAllUsers = mPackages.get(ps.name) == null;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1a7490e8b327..8bbe9cc01ada 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -125,7 +125,6 @@ import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -291,7 +290,8 @@ class PackageManagerShellCommand extends ShellCommand {
case "get-stagedsessions":
return runListStagedSessions();
case "uninstall-system-updates":
- return uninstallSystemUpdates();
+ String packageName = getNextArg();
+ return uninstallSystemUpdates(packageName);
case "rollback-app":
return runRollbackApp();
case "get-moduleinfo":
@@ -409,15 +409,22 @@ class PackageManagerShellCommand extends ShellCommand {
}
}
- private int uninstallSystemUpdates() {
+ private int uninstallSystemUpdates(String packageName) {
final PrintWriter pw = getOutPrintWriter();
- List<String> failedUninstalls = new LinkedList<>();
+ boolean failedUninstalls = false;
try {
- final ParceledListSlice<ApplicationInfo> packages =
- mInterface.getInstalledApplications(
- PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
final IPackageInstaller installer = mInterface.getPackageInstaller();
- List<ApplicationInfo> list = packages.getList();
+ final List<ApplicationInfo> list;
+ if (packageName == null) {
+ final ParceledListSlice<ApplicationInfo> packages =
+ mInterface.getInstalledApplications(
+ PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
+ list = packages.getList();
+ } else {
+ list = new ArrayList<>(1);
+ list.add(mInterface.getApplicationInfo(packageName,
+ PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM));
+ }
for (ApplicationInfo info : list) {
if (info.isUpdatedSystemApp()) {
pw.println("Uninstalling updates to " + info.packageName + "...");
@@ -430,7 +437,8 @@ class PackageManagerShellCommand extends ShellCommand {
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status != PackageInstaller.STATUS_SUCCESS) {
- failedUninstalls.add(info.packageName);
+ failedUninstalls = true;
+ pw.println("Couldn't uninstall package: " + info.packageName);
}
}
}
@@ -440,10 +448,7 @@ class PackageManagerShellCommand extends ShellCommand {
+ e.getMessage() + "]");
return 0;
}
- if (!failedUninstalls.isEmpty()) {
- pw.println("Failure [Couldn't uninstall packages: "
- + TextUtils.join(", ", failedUninstalls)
- + "]");
+ if (failedUninstalls) {
return 0;
}
pw.println("Success");
@@ -3824,9 +3829,10 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" get-harmful-app-warning [--user <USER_ID>] <PACKAGE>");
pw.println(" Return the harmful app warning message for the given app, if present");
pw.println();
- pw.println(" uninstall-system-updates");
- pw.println(" Remove updates to all system applications and fall back to their /system " +
- "version.");
+ pw.println(" uninstall-system-updates [<PACKAGE>]");
+ pw.println(" Removes updates to the given system application and falls back to its");
+ pw.println(" /system version. Does nothing if the given package is not a system app.");
+ pw.println(" If no package is specified, removes updates to all system applications.");
pw.println("");
pw.println(" get-moduleinfo [--all | --installed] [module-name]");
pw.println(" Displays module info. If module-name is specified only that info is shown");
diff --git a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
index 9b3176d9df67..183e920e4620 100644
--- a/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
+++ b/services/core/java/com/android/server/textclassifier/IconsContentProvider.java
@@ -27,6 +27,7 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
@@ -34,6 +35,7 @@ import com.android.server.textclassifier.IconsUriHelper.ResourceInfo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Arrays;
/**
* A content provider that is used to access icons returned from the TextClassifier service.
@@ -46,32 +48,40 @@ import java.io.OutputStream;
public final class IconsContentProvider extends ContentProvider {
private static final String TAG = "IconsContentProvider";
+ private static final String MIME_TYPE = "image/png";
+
+ private final PipeDataWriter<Pair<ResourceInfo, Integer>> mWriter =
+ (writeSide, uri, mimeType, bundle, args) -> {
+ try (OutputStream out = new AutoCloseOutputStream(writeSide)) {
+ final ResourceInfo res = args.first;
+ final int userId = args.second;
+ final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
+ .loadDrawableAsUser(getContext(), userId);
+ getBitmap(drawable).compress(Bitmap.CompressFormat.PNG, 100, out);
+ } catch (Exception e) {
+ Log.e(TAG, "Error retrieving icon for uri: " + uri, e);
+ }
+ };
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode) {
+ final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
+ if (res == null) {
+ Log.e(TAG, "No icon found for uri: " + uri);
+ return null;
+ }
+
try {
- final ResourceInfo res = IconsUriHelper.getInstance().getResourceInfo(uri);
- final Drawable drawable = Icon.createWithResource(res.packageName, res.id)
- .loadDrawableAsUser(getContext(), UserHandle.getCallingUserId());
- final byte[] data = getBitmapData(drawable);
- final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
- final ParcelFileDescriptor readSide = pipe[0];
- final ParcelFileDescriptor writeSide = pipe[1];
- try (OutputStream out = new AutoCloseOutputStream(writeSide)) {
- out.write(data);
- return readSide;
- }
- } catch (IOException | RuntimeException e) {
- Log.e(TAG, "Error retrieving icon for uri: " + uri, e);
+ final Pair<ResourceInfo, Integer> args = new Pair(res, UserHandle.getCallingUserId());
+ return openPipeHelper(uri, MIME_TYPE, /* bundle= */ null, args, mWriter);
+ } catch (IOException e) {
+ Log.e(TAG, "Error opening pipe helper for icon at uri: " + uri, e);
}
+
return null;
}
- /**
- * Returns the bitmap data for the specified drawable.
- */
- @VisibleForTesting
- public static byte[] getBitmapData(Drawable drawable) {
+ private static Bitmap getBitmap(Drawable drawable) {
if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
throw new IllegalStateException("The icon is zero-sized");
}
@@ -85,16 +95,24 @@ public final class IconsContentProvider extends ContentProvider {
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
- final ByteArrayOutputStream stream = new ByteArrayOutputStream();
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
- final byte[] byteArray = stream.toByteArray();
- bitmap.recycle();
- return byteArray;
+ return bitmap;
+ }
+
+ /**
+ * Returns true if the drawables are considered the same.
+ */
+ @VisibleForTesting
+ public static boolean sameIcon(Drawable one, Drawable two) {
+ final ByteArrayOutputStream stream1 = new ByteArrayOutputStream();
+ getBitmap(one).compress(Bitmap.CompressFormat.PNG, 100, stream1);
+ final ByteArrayOutputStream stream2 = new ByteArrayOutputStream();
+ getBitmap(two).compress(Bitmap.CompressFormat.PNG, 100, stream2);
+ return Arrays.equals(stream1.toByteArray(), stream2.toByteArray());
}
@Override
public String getType(Uri uri) {
- return "image/png";
+ return MIME_TYPE;
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c4663ca19f87..2e9f70448488 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3146,11 +3146,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
@Override
- boolean checkCompleteDeferredRemoval() {
+ boolean handleCompleteDeferredRemoval() {
if (mIsExiting) {
removeIfPossible();
}
- return super.checkCompleteDeferredRemoval();
+ return super.handleCompleteDeferredRemoval();
}
void onRemovedFromDisplay() {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index b4bc0f5b3a32..8f0de7312ee5 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -86,7 +86,6 @@ import static com.android.server.wm.TaskProto.ACTIVITY_TYPE;
import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS;
import static com.android.server.wm.TaskProto.BOUNDS;
import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER;
-import static com.android.server.wm.TaskProto.DEFER_REMOVAL;
import static com.android.server.wm.TaskProto.DISPLAY_ID;
import static com.android.server.wm.TaskProto.FILLS_PARENT;
import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS;
@@ -248,11 +247,6 @@ class ActivityStack extends Task {
private Rect mTmpRect = new Rect();
private Rect mTmpRect2 = new Rect();
- /** Detach this stack from its display when animation completes. */
- // TODO: maybe tie this to WindowContainer#removeChild some how...
- // TODO: This is no longer set. Okay to remove or was the set removed by accident?
- private boolean mDeferRemoval;
-
// If this is true, we are in the bounds animating mode. The task will be down or upscaled to
// perfectly fit the region it would have been cropped to. We may also avoid certain logic we
// would otherwise apply while resizing, while resizing in the bounds animating mode.
@@ -3232,9 +3226,6 @@ class ActivityStack extends Task {
@Override
void dump(PrintWriter pw, String prefix, boolean dumpAll) {
- if (mDeferRemoval) {
- pw.println(prefix + "mDeferRemoval=true");
- }
super.dump(pw, prefix, dumpAll);
if (!mExitingActivities.isEmpty()) {
pw.println();
@@ -3304,15 +3295,12 @@ class ActivityStack extends Task {
}
/** Returns true if a removal action is still being deferred. */
- boolean checkCompleteDeferredRemoval() {
+ boolean handleCompleteDeferredRemoval() {
if (isAnimating(TRANSITION | CHILDREN)) {
return true;
}
- if (mDeferRemoval) {
- removeImmediately();
- }
- return super.checkCompleteDeferredRemoval();
+ return super.handleCompleteDeferredRemoval();
}
public DisplayInfo getDisplayInfo() {
@@ -3380,7 +3368,6 @@ class ActivityStack extends Task {
mLastNonFullscreenBounds.dumpDebug(proto, LAST_NON_FULLSCREEN_BOUNDS);
}
- proto.write(DEFER_REMOVAL, mDeferRemoval);
proto.write(ANIMATING_BOUNDS, mBoundsAnimating);
if (mSurfaceControl != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 407b9fcbca74..6bfcf0c75b83 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1504,12 +1504,21 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
}
void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
- task.removeTaskActivitiesLocked(reason);
- cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
- mService.getLockTaskController().clearLockedTask(task);
- mService.getTaskChangeNotificationController().notifyTaskStackChanged();
- if (task.isPersistable) {
- mService.notifyTaskPersisterLocked(null, true);
+ if (task.mInRemoveTask) {
+ // Prevent recursion.
+ return;
+ }
+ task.mInRemoveTask = true;
+ try {
+ task.performClearTask(reason);
+ cleanUpRemovedTaskLocked(task, killProcess, removeFromRecents);
+ mService.getLockTaskController().clearLockedTask(task);
+ mService.getTaskChangeNotificationController().notifyTaskStackChanged();
+ if (task.isPersistable) {
+ mService.notifyTaskPersisterLocked(null, true);
+ }
+ } finally {
+ task.mInRemoveTask = false;
}
}
@@ -2177,7 +2186,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks {
// split-screen in split-screen.
mService.getTaskChangeNotificationController()
.notifyActivityDismissingDockedStack();
- taskDisplayArea.onSplitScreenModeDismissed(task.getStack());
+ taskDisplayArea.onSplitScreenModeDismissed((ActivityStack) task);
taskDisplayArea.mDisplayContent.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS,
true /* notifyClients */);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 241de2e0e068..c56440785bba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -2739,6 +2739,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
@Override
void removeImmediately() {
mRemovingDisplay = true;
+ mDeferredRemoval = false;
try {
if (mParentWindow != null) {
mParentWindow.removeEmbeddedDisplayContent(this);
@@ -2771,31 +2772,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/** Returns true if a removal action is still being deferred. */
@Override
- boolean checkCompleteDeferredRemoval() {
- boolean stillDeferringRemoval = false;
-
- for (int i = getChildCount() - 1; i >= 0; --i) {
- final DisplayChildWindowContainer child = getChildAt(i);
- stillDeferringRemoval |= child.checkCompleteDeferredRemoval();
- if (getChildCount() == 0) {
- // If this display is pending to be removed because it contains an activity with
- // {@link ActivityRecord#mIsExiting} is true, this display may be removed when
- // completing the removal of the last activity from
- // {@link ActivityRecord#checkCompleteDeferredRemoval}.
- return false;
- }
- }
+ boolean handleCompleteDeferredRemoval() {
+ final boolean stillDeferringRemoval = super.handleCompleteDeferredRemoval();
if (!stillDeferringRemoval && mDeferredRemoval) {
removeImmediately();
return false;
}
- return true;
- }
-
- /** @return 'true' if removal of this display content is deferred due to active animation. */
- boolean isRemovalDeferred() {
- return mDeferredRemoval;
+ return stillDeferringRemoval;
}
void adjustForImeIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8734b5efa45d..3e88566449fe 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -19,10 +19,13 @@ package com.android.server.wm;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.INPUT_CONSUMER_PIP;
import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.INPUT_CONSUMER_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS;
@@ -477,12 +480,11 @@ final class InputMonitor {
mService.getRecentsAnimationController();
final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
- if (inputChannel == null || inputWindowHandle == null || w.mRemoved
- || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
+ if (inputWindowHandle == null || w.mRemoved) {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
- mInvalidInputWindow);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ mInvalidInputWindow);
}
// Skip this window because it cannot possibly receive input.
return;
@@ -491,9 +493,23 @@ final class InputMonitor {
final int flags = w.mAttrs.flags;
final int privateFlags = w.mAttrs.privateFlags;
final int type = w.mAttrs.type;
- final boolean hasFocus = w.isFocused();
final boolean isVisible = w.isVisibleLw();
+ // Assign an InputInfo with type to the overlay window which can't receive input event.
+ // This is used to omit Surfaces from occlusion detection.
+ if (inputChannel == null
+ || (w.cantReceiveTouchInput() && !shouldApplyRecentsInputConsumer)) {
+ if (!w.mWinAnimator.hasSurface()) {
+ return;
+ }
+ populateOverlayInputInfo(inputWindowHandle, w.getName(), type, isVisible);
+ mInputTransaction.setInputWindowInfo(
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ inputWindowHandle);
+ return;
+ }
+
+ final boolean hasFocus = w.isFocused();
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
@@ -555,6 +571,28 @@ final class InputMonitor {
}
}
+ // This would reset InputWindowHandle fields to prevent it could be found by input event.
+ // We need to check if any new field of InputWindowHandle could impact the result.
+ private static void populateOverlayInputInfo(final InputWindowHandle inputWindowHandle,
+ final String name, final int type, final boolean isVisible) {
+ inputWindowHandle.name = name;
+ inputWindowHandle.layoutParamsType = type;
+ inputWindowHandle.dispatchingTimeoutNanos =
+ WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+ inputWindowHandle.visible = isVisible;
+ inputWindowHandle.canReceiveKeys = false;
+ inputWindowHandle.hasFocus = false;
+ inputWindowHandle.ownerPid = myPid();
+ inputWindowHandle.ownerUid = myUid();
+ inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
+ inputWindowHandle.scaleFactor = 1;
+ inputWindowHandle.layoutParamsFlags =
+ FLAG_NOT_TOUCH_MODAL | FLAG_NOT_TOUCHABLE | FLAG_NOT_FOCUSABLE;
+ inputWindowHandle.portalToDisplayId = INVALID_DISPLAY;
+ inputWindowHandle.touchableRegion.setEmpty();
+ inputWindowHandle.setTouchableRegionCrop(null);
+ }
+
/**
* Helper function to generate an InputInfo with type SECURE_SYSTEM_OVERLAY. This input
* info will not have an input channel or be touchable, but is used to omit Surfaces
@@ -564,16 +602,7 @@ final class InputMonitor {
static void setTrustedOverlayInputInfo(SurfaceControl sc, SurfaceControl.Transaction t,
int displayId, String name) {
InputWindowHandle inputWindowHandle = new InputWindowHandle(null, displayId);
- inputWindowHandle.name = name;
- inputWindowHandle.layoutParamsType = TYPE_SECURE_SYSTEM_OVERLAY;
- inputWindowHandle.dispatchingTimeoutNanos = -1;
- inputWindowHandle.visible = true;
- inputWindowHandle.canReceiveKeys = false;
- inputWindowHandle.hasFocus = false;
- inputWindowHandle.ownerPid = myPid();
- inputWindowHandle.ownerUid = myUid();
- inputWindowHandle.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL;
- inputWindowHandle.scaleFactor = 1;
+ populateOverlayInputInfo(inputWindowHandle, name, TYPE_SECURE_SYSTEM_OVERLAY, true);
t.setInputWindowInfo(sc, inputWindowHandle);
}
}
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1b58fc1d2d3e..851b533a550d 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1426,8 +1426,8 @@ class RecentTasks {
private void removeUnreachableHiddenTasks(int windowingMode) {
for (int i = mHiddenTasks.size() - 1; i >= 0; i--) {
final Task hiddenTask = mHiddenTasks.get(i);
- if (!hiddenTask.hasChild()) {
- // The task was removed by other path.
+ if (!hiddenTask.hasChild() || hiddenTask.inRecents) {
+ // The task was removed by other path or it became reachable (added to recents).
mHiddenTasks.remove(i);
continue;
}
@@ -1449,6 +1449,9 @@ class RecentTasks {
* of task as the given one.
*/
private void removeForAddTask(Task task) {
+ // The adding task will be in recents so it is not hidden.
+ mHiddenTasks.remove(task);
+
final int removeIndex = findRemoveIndexForAddTask(task);
if (removeIndex == -1) {
// Nothing to trim
@@ -1460,8 +1463,6 @@ class RecentTasks {
// callbacks here.
final Task removedTask = mTasks.remove(removeIndex);
if (removedTask != task) {
- // The added task is in recents so it is not hidden.
- mHiddenTasks.remove(task);
if (removedTask.hasChild()) {
// A non-empty task is replaced by a new task. Because the removed task is no longer
// managed by the recent tasks list, add it to the hidden list to prevent the task
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2d30b737b274..119b188dfa00 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -990,9 +990,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
// Remove all deferred displays stacks, tasks, and activities.
- for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
- mChildren.get(displayNdx).checkCompleteDeferredRemoval();
- }
+ handleCompleteDeferredRemoval();
forAllDisplays(dc -> {
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 748244e1a5c2..c664a841fc1f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -435,6 +435,13 @@ class Task extends WindowContainer<WindowContainer> {
static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
private int mForceHiddenFlags = 0;
+ // TODO(b/160201781): Revisit double invocation issue in Task#removeChild.
+ /**
+ * Skip {@link ActivityStackSupervisor#removeTask(Task, boolean, boolean, String)} execution if
+ * {@code true} to prevent double traversal of {@link #mChildren} in a loop.
+ */
+ boolean mInRemoveTask;
+
// When non-null, this is a transaction that will get applied on the next frame returned after
// a relayout is requested from the client. While this is only valid on a leaf task; since the
// transaction can effect an ancestor task, this also needs to keep track of the ancestor task
@@ -1496,11 +1503,8 @@ class Task extends WindowContainer<WindowContainer> {
return autoRemoveRecents || (!hasChild() && !getHasBeenVisible());
}
- /**
- * Completely remove all activities associated with an existing
- * task starting at a specified index.
- */
- private void performClearTaskAtIndexLocked(String reason) {
+ /** Completely remove all activities associated with an existing task. */
+ void performClearTask(String reason) {
// Broken down into to cases to avoid object create due to capturing mStack.
if (getStack() == null) {
forAllActivities((r) -> {
@@ -1524,7 +1528,7 @@ class Task extends WindowContainer<WindowContainer> {
*/
void performClearTaskLocked() {
mReuseTask = true;
- performClearTaskAtIndexLocked("clear-task-all");
+ performClearTask("clear-task-all");
mReuseTask = false;
}
@@ -1585,11 +1589,6 @@ class Task extends WindowContainer<WindowContainer> {
return false;
}
- void removeTaskActivitiesLocked(String reason) {
- // Just remove the entire task.
- performClearTaskAtIndexLocked(reason);
- }
-
String lockTaskAuthToString() {
switch (mLockTaskAuth) {
case LOCK_TASK_AUTH_DONT_LOCK: return "LOCK_TASK_AUTH_DONT_LOCK";
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3360951a8f3f..205a8d2aeeac 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1746,10 +1746,15 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> {
void ensureActivitiesVisible(ActivityRecord starting, int configChanges,
boolean preserveWindows, boolean notifyClients) {
- for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = getStackAt(stackNdx);
- stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
- notifyClients);
+ mAtmService.mStackSupervisor.beginActivityVisibilityUpdate();
+ try {
+ for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = getStackAt(stackNdx);
+ stack.ensureActivitiesVisible(starting, configChanges, preserveWindows,
+ notifyClients);
+ }
+ } finally {
+ mAtmService.mStackSupervisor.endActivityVisibilityUpdate();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 2977968f5754..5a73fabaf9d0 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1044,13 +1044,25 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return mChildren.peekLast();
}
- /** Returns true if there is still a removal being deferred */
- boolean checkCompleteDeferredRemoval() {
+ /**
+ * Removes the containers which were deferred.
+ *
+ * @return {@code true} if there is still a removal being deferred.
+ */
+ boolean handleCompleteDeferredRemoval() {
boolean stillDeferringRemoval = false;
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
- stillDeferringRemoval |= wc.checkCompleteDeferredRemoval();
+ stillDeferringRemoval |= wc.handleCompleteDeferredRemoval();
+ if (!hasChild()) {
+ // All child containers of current level could be removed from a removal of
+ // descendant. E.g. if a display is pending to be removed because it contains an
+ // activity with {@link ActivityRecord#mIsExiting} is true, the display may be
+ // removed when completing the removal of the last activity from
+ // {@link ActivityRecord#checkCompleteDeferredRemoval}.
+ return false;
+ }
}
return stillDeferringRemoval;
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c605e3e1ea60..315014c1b248 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -528,8 +528,9 @@ public abstract class WindowManagerInternal {
* Hide IME using imeTargetWindow when requested.
*
* @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden.
+ * @param displayId the id of the display the IME is on.
*/
- public abstract void hideIme(IBinder imeTargetWindowToken);
+ public abstract void hideIme(IBinder imeTargetWindowToken, int displayId);
/**
* Tell window manager about a package that should not be running with high refresh rate
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b1756b07ad83..0b50c1cd496a 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -90,6 +90,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_BOOT;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
@@ -5606,17 +5607,28 @@ public class WindowManagerService extends IWindowManager.Stub
}
final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
- final boolean waitingForConfig = displayContent != null && displayContent.mWaitingForConfig;
- final int numOpeningApps = displayContent != null ? displayContent.mOpeningApps.size() : 0;
- if (waitingForConfig || mAppsFreezingScreen > 0
+ final int numOpeningApps;
+ final boolean waitingForConfig;
+ final boolean waitingForRemoteRotation;
+ if (displayContent != null) {
+ numOpeningApps = displayContent.mOpeningApps.size();
+ waitingForConfig = displayContent.mWaitingForConfig;
+ waitingForRemoteRotation =
+ displayContent.getDisplayRotation().isWaitingForRemoteRotation();
+ } else {
+ waitingForConfig = waitingForRemoteRotation = false;
+ numOpeningApps = 0;
+ }
+ if (waitingForConfig || waitingForRemoteRotation || mAppsFreezingScreen > 0
|| mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
|| mClientFreezingScreen || numOpeningApps > 0) {
- ProtoLog.d(WM_DEBUG_ORIENTATION,
- "stopFreezingDisplayLocked: Returning mWaitingForConfig=%b, "
- + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
- + "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
- waitingForConfig, mAppsFreezingScreen, mWindowsFreezingScreen,
- mClientFreezingScreen, numOpeningApps);
+ ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
+ + "waitingForConfig=%b, waitingForRemoteRotation=%b, "
+ + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
+ + "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+ waitingForConfig, waitingForRemoteRotation,
+ mAppsFreezingScreen, mWindowsFreezingScreen,
+ mClientFreezingScreen, numOpeningApps);
return;
}
@@ -5627,7 +5639,6 @@ public class WindowManagerService extends IWindowManager.Stub
// We must make a local copy of the displayId as it can be potentially overwritten later on
// in this method. For example, {@link startFreezingDisplayLocked} may be called as a result
// of update rotation, but we reference the frozen display after that call in this method.
- final int displayId = mFrozenDisplayId;
mFrozenDisplayId = INVALID_DISPLAY;
mDisplayFrozen = false;
mInputManagerCallback.thawInputDispatchingLw();
@@ -7606,24 +7617,26 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void hideIme(IBinder imeTargetWindowToken) {
+ public void hideIme(IBinder imeTargetWindowToken, int displayId) {
synchronized (mGlobalLock) {
WindowState imeTarget = mWindowMap.get(imeTargetWindowToken);
- if (imeTarget == null) {
- // The target window no longer exists.
- return;
+ ProtoLog.d(WM_DEBUG_IME, "hideIme target: %s ", imeTarget);
+ DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (imeTarget != null) {
+ imeTarget = imeTarget.getImeControlTarget().getWindow();
+ if (imeTarget != null) {
+ dc = imeTarget.getDisplayContent();
+ }
+ // If there was a pending IME show(), reset it as IME has been
+ // requested to be hidden.
+ dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
}
- imeTarget = imeTarget.getImeControlTarget().getWindow();
- final DisplayContent dc = imeTarget != null
- ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked();
- // If there was a pending IME show(), reset it as IME has been
- // requested to be hidden.
- dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
- if (dc.mInputMethodControlTarget == null) {
- return;
+ if (dc != null && dc.mInputMethodControlTarget != null) {
+ ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ",
+ dc.mInputMethodControlTarget);
+ dc.mInputMethodControlTarget.hideInsets(
+ WindowInsets.Type.ime(), true /* fromIme */);
}
- dc.mInputMethodControlTarget.hideInsets(
- WindowInsets.Type.ime(), true /* fromIme */);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f1acee5031d8..d4d2f4d7a492 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -81,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
@@ -2372,6 +2373,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return false;
}
+ if (mAttrs.type == TYPE_SCREENSHOT) {
+ // Disallow screenshot windows from being IME targets
+ return false;
+ }
+
final boolean windowsAreFocusable = mActivityRecord == null || mActivityRecord.windowsAreFocusable();
if (!windowsAreFocusable) {
// This window can't be an IME target if the app's windows should not be focusable.
diff --git a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
index 72580a3b98c2..a787c321fc66 100644
--- a/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/textclassifier/IconsContentProviderTest.java
@@ -50,8 +50,7 @@ public final class IconsContentProviderTest {
final Drawable actual = Icon.createWithContentUri(uri).loadDrawable(context);
assertThat(actual).isNotNull();
- assertThat(IconsContentProvider.getBitmapData(actual))
- .isEqualTo(IconsContentProvider.getBitmapData(expected));
+ assertThat(IconsContentProvider.sameIcon(actual, expected)).isTrue();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 30af1d34f558..ddb186a1d2da 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1328,9 +1328,10 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayRotation dr = dc.getDisplayRotation();
doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean());
- Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt());
+ // Rotate 180 degree so the display doesn't have configuration change. This condition is
+ // used for the later verification of stop-freezing (without setting mWaitingForConfig).
+ doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
final boolean[] continued = new boolean[1];
- // TODO(display-merge): Remove cast
doAnswer(
invocation -> {
continued[0] = true;
@@ -1356,9 +1357,16 @@ public class DisplayContentTests extends WindowTestsBase {
dc.setRotationAnimation(null);
mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */);
+ // If remote rotation is not finished, the display should not be able to unfreeze.
+ mWm.stopFreezingDisplayLocked();
+ assertTrue(mWm.mDisplayFrozen);
+
assertTrue(called[0]);
waitUntilHandlersIdle();
assertTrue(continued[0]);
+
+ mWm.stopFreezingDisplayLocked();
+ assertFalse(mWm.mDisplayFrozen);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index 5005c07832ab..fd169018782b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -464,15 +464,19 @@ public class RecentTasksTest extends ActivityTestsBase {
mRecentTasks.add(task1);
final Task task2 = taskBuilder.apply(true /* visible */);
mRecentTasks.add(task2);
- // Only the last task is kept in recents and the previous 2 tasks will becomes untracked
+ final Task task3 = createTaskBuilder(className).build();
+ mRecentTasks.add(task3);
+ // Only the last added task is kept in recents and the previous 2 tasks will become hidden
// tasks because their intents are identical.
- mRecentTasks.add(createTaskBuilder(className).build());
+ mRecentTasks.add(task1);
// Go home to trigger the removal of untracked tasks.
mRecentTasks.add(createTaskBuilder(".Home").setStack(mTaskContainer.getRootHomeTask())
.build());
+ // The task was added into recents again so it is not hidden and shouldn't be removed.
+ assertNotNull(task1.getTopNonFinishingActivity());
// All activities in the invisible task should be finishing or removed.
- assertNull(task1.getTopNonFinishingActivity());
+ assertNull(task3.getTopNonFinishingActivity());
// The visible task should not be affected.
assertNotNull(task2.getTopNonFinishingActivity());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 1415c506a1c9..9d88ada5a90c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -28,6 +28,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.clearInvocations;
import android.graphics.Point;
@@ -158,4 +160,30 @@ public class TaskTests extends WindowTestsBase {
assertEquals(activity1, task1.isInTask(activity1));
assertNull(task1.isInTask(activity2));
}
+
+ @Test
+ public void testRemoveChildForOverlayTask() {
+ final Task task = createTaskStackOnDisplay(mDisplayContent);
+ final int taskId = task.mTaskId;
+ final ActivityRecord activity1 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ final ActivityRecord activity2 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ final ActivityRecord activity3 =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ activity1.setTaskOverlay(true);
+ activity2.setTaskOverlay(true);
+ activity3.setTaskOverlay(true);
+
+ assertEquals(3, task.getChildCount());
+ assertTrue(task.onlyHasTaskOverlayActivities(true));
+
+ task.removeChild(activity1);
+
+ verify(task.mStackSupervisor).removeTask(any(), anyBoolean(), anyBoolean(), anyString());
+ assertEquals(2, task.getChildCount());
+ task.forAllActivities((r) -> {
+ assertTrue(r.finishing);
+ });
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 87485eac3412..efc03df877b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -27,6 +27,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -825,6 +826,31 @@ public class WindowContainerTests extends WindowTestsBase {
}
@Test
+ public void testHandleCompleteDeferredRemoval() {
+ final DisplayContent displayContent = createNewDisplay();
+ // Do not reparent activity to default display when removing the display.
+ doReturn(true).when(displayContent).shouldDestroyContentOnRemove();
+ final ActivityRecord r = new ActivityTestsBase.StackBuilder(mWm.mRoot)
+ .setDisplay(displayContent).build().getTopMostActivity();
+ // Add a window and make the activity animating so the removal of activity is deferred.
+ createWindow(null, TYPE_BASE_APPLICATION, r, "win");
+ doReturn(true).when(r).isAnimating(anyInt(), anyInt());
+
+ displayContent.remove();
+ // Ensure that ActivityRecord#onRemovedFromDisplay is called.
+ r.destroyed("test");
+ // The removal is deferred, so the activity is still in the display.
+ assertEquals(r, displayContent.getTopMostActivity());
+
+ // Assume the animation is done so the deferred removal can continue.
+ doReturn(false).when(r).isAnimating(anyInt(), anyInt());
+
+ assertFalse(displayContent.handleCompleteDeferredRemoval());
+ assertFalse(displayContent.hasChild());
+ assertFalse(r.hasChild());
+ }
+
+ @Test
public void testTaskCanApplyAnimation() {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task = createTaskInStack(stack, 0 /* userId */);
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 42b0c608822e..314e95229d29 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -61,11 +61,12 @@ public class NetworkStagedRollbackTest {
private static final TestApp NETWORK_STACK = new TestApp("NetworkStack",
getNetworkStackPackageName(), -1, false, findNetworkStackApk());
- private static File findNetworkStackApk() {
+ private static File[] findNetworkStackApk() {
for (String name : NETWORK_STACK_APK_NAMES) {
final File apk = new File("/system/priv-app/" + name + "/" + name + ".apk");
if (apk.isFile()) {
- return apk;
+ final File dir = new File("/system/priv-app/" + name);
+ return dir.listFiles((d, f) -> f.startsWith(name));
}
}
throw new RuntimeException("Can't find NetworkStackApk");