Merge "Use openPipeHelper() for streaming IconsContentProvider data." into rvc-dev
diff --git a/Android.bp b/Android.bp
index ec77404..14a2bff 100644
--- a/Android.bp
+++ b/Android.bp
@@ -534,6 +534,8 @@
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 ecc78ce..113f8fe 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 @@
};
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 @@
*
* @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 @@
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 @@
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 9c1acaf..39f7526 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 @@
* @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 @@
* @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 79cd1b1..bb9f13f 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.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 @@
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 @@
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 @@
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 @@
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 520e8bb..d37dfde 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 @@
"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/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 4bd7b05..591a714 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -65,6 +65,13 @@
/**
* 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 08aa534..2d99c41 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -312,7 +312,12 @@
* 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 9cf1b87..91416948 100644
--- a/core/java/android/service/autofill/InlinePresentation.java
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -50,7 +50,11 @@
/**
* 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 @@
* 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 @@
/**
* 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 @@
};
@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 bb0de12..f937bc9 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -471,6 +471,13 @@
}
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 @@
+ "finishedDrawing");
}
- if (mDeferredDestroySurfaceControl != null) {
- synchronized (mSurfaceControlLock) {
- mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply();
- mDeferredDestroySurfaceControl = null;
- }
- }
-
runOnUiThread(this::performDrawFinished);
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 8c2358d..14cf258 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2040,6 +2040,9 @@
if (!isUserRunning(userHandle)) {
return false;
}
+ if (!isUserUnlocked(userHandle)) {
+ return false;
+ }
if (isQuietModeEnabled(userHandle)) {
return false;
}
@@ -2892,6 +2895,12 @@
}
@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 fba4675a..233231c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1279,13 +1279,17 @@
}
private void safelyStartActivityInternal(TargetInfo cti) {
- if (mPersonalPackageMonitor != null) {
- mPersonalPackageMonitor.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;
}
- if (mWorkPackageMonitor != null) {
- mWorkPackageMonitor.unregister();
- }
- 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 889a615..3b6b407 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 766fcfa..26fb6d8 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 0bf10cb..090645f 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 @@
}
@Test
- public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() {
+ public void testWorkTab_selectingWorkTabWithNotRunningWorkUser_directShareTargetsNotQueried() {
// enable the work tab feature flag
ResolverActivity.ENABLE_TABBED_VIEW = true;
markWorkProfileUserAvailable();
@@ -2035,6 +2035,70 @@
}
@Test
+ public void testWorkTab_workUserNotRunning_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.isWorkProfileUserRunning = 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());
+ }
+
+ @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;
@@ -2046,7 +2110,7 @@
setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
Intent sendIntent = createSendTextIntent();
sendIntent.setType("TestType");
- sOverrides.isWorkProfileUserRunning = false;
+ sOverrides.isWorkProfileUserUnlocked = false;
final ChooserWrapperActivity activity =
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
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 b7d6c61..d3d5caf 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 @@
@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 boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
+ public boolean isWorkProfileUserUnlocked;
public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector;
public PackageManager packageManager;
@@ -281,6 +293,7 @@
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 8520fff..c710bed 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -424,6 +424,8 @@
<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 bfc623f..33ab16d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1663,12 +1663,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 +1723,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 +1951,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/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b64a9e7..570c2ab3 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 da4cb24..3c641af 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -207,8 +207,8 @@
<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/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 87e1499..eb8758c 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 7009cf9..ee958f2 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -19,8 +19,8 @@
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"
app:layout_constraintTop_toTopOf="@id/app_name"
app:layout_constraintBottom_toBottomOf="@id/app_name"
@@ -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"
/>
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index dc275ba..d5a02c2 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -19,8 +19,8 @@
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"
app:layout_constraintTop_toTopOf="@id/app_name"
app:layout_constraintBottom_toBottomOf="@id/app_name"
@@ -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"
/>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index c170ee2..ec4304f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -476,6 +476,9 @@
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 +486,7 @@
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/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index cd16924..1a730c3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -299,6 +299,7 @@
if (numPages == 1) {
pageIndicator.setLocation(0f)
}
+ updatePageIndicatorAlpha()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index 07a7e61..570de63 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
@@ -101,7 +100,7 @@
}
// 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)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt
index f90af2a..d3954b7 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 @@
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 @@
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 @@
* 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 @@
}
}
}
+ carouselSizes[location] = result
return result
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt
index 033a42a..83cfdd5 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 @@
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()
@@ -304,8 +314,8 @@
// 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 +325,8 @@
}
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 +361,22 @@
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.
diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java
index 1842564..68b6785 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 @@
// 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 @@
@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 @@
@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 @@
@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 2980f11..ead1786 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 @@
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 @@
|| direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
}
- private final Interpolator mFastOutSlowInInterpolator;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private PipTransitionAnimator mCurrentAnimator;
@@ -90,8 +89,6 @@
@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 @@
}
@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 @@
} else {
mCurrentAnimator.cancel();
mCurrentAnimator = setupPipTransitionAnimator(
- PipTransitionAnimator.ofBounds(leash, startBounds, endBounds));
+ PipTransitionAnimator.ofBounds(leash, startBounds, endBounds, sourceHintRect));
}
return mCurrentAnimator;
}
@@ -142,7 +140,7 @@
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 @@
@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 @@
}
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 @@
@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 0d3a16e..8bbd15b 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 @@
}
/**
+ * 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 fc41d2e..65ea887 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 @@
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 @@
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 c8a1ca0..0141dee 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 @@
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 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 @@
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 @@
}
}
+ /**
+ * 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 @@
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 @@
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 @@
}
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 @@
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 @@
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 40a86b7..75d3d04 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -96,7 +96,9 @@
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
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 a4edace..1ca53f9 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.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 @@
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 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 @@
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 @@
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 79f99f4..b6e4e16 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 @@
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 @@
}
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 @@
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 @@
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/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 8e878dd..d6e1a16 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 @@
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.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.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 @@
private MediaActionSound mCameraSound;
+ private int mNavMode;
+ private int mLeftInset;
+ private int mRightInset;
+
// standard material ease
private final Interpolator mFastOutSlowIn;
@@ -301,6 +308,15 @@
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 @@
if (needsUpdate) {
reloadAssets();
}
+
+ mNavMode = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
}
/**
@@ -370,6 +389,25 @@
// 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 @@
}
}
+ /**
+ * 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 @@
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 b846aa0..eca4c80 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 @@
* 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/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index c1acfbad..285cf7a 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 @@
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 76751ea..f8a778d 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 @@
})
}
+ 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 52f7c2c..7bd192d 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 @@
*/
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 3dcf7ed..e05ba12 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 @@
@Override
- protected boolean isSticky() {
+ public boolean isSticky() {
return super.isSticky() || mMenuShownPinned;
}
@@ -568,6 +568,17 @@
}
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/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index c321331..375af6b 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 @@
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 @@
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 @@
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 @@
mDownX = event.getX();
mDownY = event.getY();
mCollapsedOnDown = isFullyCollapsed();
+ mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY);
mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
@@ -1154,6 +1165,24 @@
}
}
+ /**
+ * 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 @@
}
@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 caddc4a..732f25f 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 @@
}
}
- protected boolean isScrolledToBottom() {
+ protected boolean canCollapsePanelOnTouch() {
return true;
}
@@ -1081,7 +1081,7 @@
* 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 @@
}
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 @@
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 0d5a149..07de388 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 @@
protected boolean expanded;
@Override
- protected boolean isSticky() {
+ public boolean isSticky() {
return (mEntry.isRowPinned() && expanded)
|| remoteInputActive || hasFullScreenIntent(mEntry);
}
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 b7a2633..536cae4 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 @@
@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 @@
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 @@
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 @@
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 363fe95..359faba 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 @@
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 @@
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/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index d2b1bd1..499a271 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 @@
//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 373d47e..6f2e626 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 @@
@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 9b3d075..7ab4369 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.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 @@
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 @@
}
}
+ /**
+ * 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 ef81d71..29bb542 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.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 @@
}
@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 0000000..7691852
--- /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 c9dbacd..a38d42b 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 @@
}
}
- // 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 1cc41b2..5124c4a 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 @@
} 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/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 088c5da..670b88e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -15173,8 +15173,13 @@
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 +15279,8 @@
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 +15300,9 @@
final Intent sufficientIntent = new Intent(verification);
sufficientIntent.setComponent(verifierComponent);
- mContext.sendBroadcastAsUser(sufficientIntent, verifierUser);
+ mContext.sendBroadcastAsUser(sufficientIntent, verifierUser,
+ /* receiverPermission= */ null,
+ options.toBundle());
}
}
}
@@ -15312,6 +15321,8 @@
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) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c4663ca..2e9f704 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 @@
}
@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 b4bc0f5..8f0de73 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.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 @@
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 @@
@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 @@
}
/** 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 @@
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/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 241de2e..c564407 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 @@
@Override
void removeImmediately() {
mRemovingDisplay = true;
+ mDeferredRemoval = false;
try {
if (mParentWindow != null) {
mParentWindow.removeEmbeddedDisplayContent(this);
@@ -2771,31 +2772,14 @@
/** 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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1b58fc1..851b533 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 @@
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 @@
* 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 @@
// 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 2d30b73..119b188 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 @@
}
// 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/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 3360951..205a8d2 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 @@
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 2977968..5a73fab 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 @@
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b1756b0..f4281fc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5606,17 +5606,28 @@
}
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 +5638,6 @@
// 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();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f1acee5..d4d2f4d 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_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 @@
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/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 30af1d3..ddb186a 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 @@
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 @@
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 5005c07..fd16901 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 @@
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/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 87485ea..efc03df 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 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 @@
}
@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 */);