diff options
61 files changed, 741 insertions, 233 deletions
diff --git a/Android.bp b/Android.bp index ec7740437c2d..14a2bff8ad13 100644 --- a/Android.bp +++ b/Android.bp @@ -534,6 +534,8 @@ java_library { static_libs: [ "exoplayer2-extractor", "android.hardware.wifi-V1.0-java-constants", + // Additional dependencies needed to build the ike API classes. + "ike-internals", ], apex_available: ["//apex_available:platform"], visibility: [ diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index ecc78ce7cf34..113f8fe9e248 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -51,6 +51,7 @@ public final class BlobHandle implements Parcelable { }; private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters + private static final int LIMIT_BLOB_LABEL_LENGTH = 100; // characters /** * Cyrptographically secure hash algorithm used to generate hash of the blob this handle is @@ -128,6 +129,9 @@ public final class BlobHandle implements Parcelable { * * @param digest the SHA-256 hash of the blob this is representing. * @param label a label indicating what the blob is, that can be surfaced to the user. + * The length of the label cannot be more than 100 characters. It is recommended + * to keep this brief. This may be truncated and ellipsized if it is too long + * to be displayed to the user. * @param expiryTimeMillis the time in secs after which the blob should be invalidated and not * allowed to be accessed by any other app, * in {@link System#currentTimeMillis()} timebase or {@code 0} to @@ -205,9 +209,9 @@ public final class BlobHandle implements Parcelable { final BlobHandle other = (BlobHandle) obj; return this.algorithm.equals(other.algorithm) && Arrays.equals(this.digest, other.digest) - && this.label.equals(other.label) + && this.label.toString().equals(other.label.toString()) && this.expiryTimeMillis == other.expiryTimeMillis - && this.tag.equals(tag); + && this.tag.equals(other.tag); } @Override @@ -233,6 +237,7 @@ public final class BlobHandle implements Parcelable { Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm); Preconditions.checkByteArrayNotEmpty(digest, "digest"); Preconditions.checkStringNotEmpty(label, "label must not be null"); + Preconditions.checkArgument(label.length() <= LIMIT_BLOB_LABEL_LENGTH, "label too long"); Preconditions.checkArgumentNonnegative(expiryTimeMillis, "expiryTimeMillis must not be negative"); Preconditions.checkStringNotEmpty(tag, "tag must not be null"); diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 9c1acafa800d..39f7526560a9 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -347,7 +347,9 @@ public class BlobStoreManager { * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param description a short description string that can be surfaced - * to the user explaining what the blob is used for. + * to the user explaining what the blob is used for. It is recommended to + * keep this description brief. This may be truncated and ellipsized + * if it is too long to be displayed to the user. * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be * automatically released, in {@link System#currentTimeMillis()} * timebase. If its value is {@code 0}, then the behavior of this @@ -458,7 +460,9 @@ public class BlobStoreManager { * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to * acquire a lease for. * @param description a short description string that can be surfaced - * to the user explaining what the blob is used for. + * to the user explaining what the blob is used for. It is recommended to + * keep this description brief. This may be truncated and + * ellipsized if it is too long to be displayed to the user. * * @throws IOException when there is an I/O error while acquiring a lease to the blob. * @throws SecurityException when the blob represented by the {@code blobHandle} does not diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index 79cd1b17a5b5..bb9f13f1712c 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -25,6 +25,7 @@ import android.content.Context; import android.os.Environment; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; +import android.text.TextUtils; import android.util.DataUnit; import android.util.Log; import android.util.Slog; @@ -171,6 +172,13 @@ class BlobStoreConfig { public static int MAX_BLOB_ACCESS_PERMITTED_PACKAGES = DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES; + /** + * Denotes the maximum number of characters that a lease description can have. + */ + public static final String KEY_LEASE_DESC_CHAR_LIMIT = "lease_desc_char_limit"; + public static int DEFAULT_LEASE_DESC_CHAR_LIMIT = 300; + public static int LEASE_DESC_CHAR_LIMIT = DEFAULT_LEASE_DESC_CHAR_LIMIT; + static void refresh(Properties properties) { if (!NAMESPACE_BLOBSTORE.equals(properties.getNamespace())) { return; @@ -221,6 +229,10 @@ class BlobStoreConfig { MAX_BLOB_ACCESS_PERMITTED_PACKAGES = properties.getInt(key, DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES); break; + case KEY_LEASE_DESC_CHAR_LIMIT: + LEASE_DESC_CHAR_LIMIT = properties.getInt(key, + DEFAULT_LEASE_DESC_CHAR_LIMIT); + break; default: Slog.wtf(TAG, "Unknown key in device config properties: " + key); } @@ -262,6 +274,8 @@ class BlobStoreConfig { fout.println(String.format(dumpFormat, KEY_MAX_BLOB_ACCESS_PERMITTED_PACKAGES, MAX_BLOB_ACCESS_PERMITTED_PACKAGES, DEFAULT_MAX_BLOB_ACCESS_PERMITTED_PACKAGES)); + fout.println(String.format(dumpFormat, KEY_LEASE_DESC_CHAR_LIMIT, + LEASE_DESC_CHAR_LIMIT, DEFAULT_LEASE_DESC_CHAR_LIMIT)); } } @@ -368,6 +382,18 @@ class BlobStoreConfig { return DeviceConfigProperties.MAX_BLOB_ACCESS_PERMITTED_PACKAGES; } + /** + * Returns the lease description truncated to + * {@link DeviceConfigProperties#LEASE_DESC_CHAR_LIMIT} characters. + */ + public static CharSequence getTruncatedLeaseDescription(CharSequence description) { + if (TextUtils.isEmpty(description)) { + return description; + } + return TextUtils.trimToLengthWithEllipsis(description, + DeviceConfigProperties.LEASE_DESC_CHAR_LIMIT); + } + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index 520e8bbf9f93..d37dfdeaa583 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -1500,6 +1500,8 @@ public class BlobStoreManagerService extends SystemService { "leaseExpiryTimeMillis must not be negative"); Objects.requireNonNull(packageName, "packageName must not be null"); + description = BlobStoreConfig.getTruncatedLeaseDescription(description); + final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index 4bd7b059dfaa..591a714bfb93 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -65,6 +65,13 @@ public final class CompanionDeviceManager { /** * A device, returned in the activity result of the {@link IntentSender} received in * {@link Callback#onDeviceFound} + * + * Type is: + * <ul> + * <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li> + * <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li> + * <li>for WiFi - {@link android.net.wifi.ScanResult}</li> + * </ul> */ public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE"; diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java index 08aa534be152..2d99c413cc89 100644 --- a/core/java/android/service/autofill/Dataset.java +++ b/core/java/android/service/autofill/Dataset.java @@ -312,7 +312,12 @@ public final class Dataset implements Parcelable { * setting it to the {@link * android.view.autofill.AutofillManager#EXTRA_AUTHENTICATION_RESULT} extra. If you * provide a dataset in the result, it will replace the authenticated dataset and - * will be immediately filled in. If you provide a response, it will replace the + * will be immediately filled in. An exception to this behavior is if the original + * dataset represents a pinned inline suggestion (i.e. any of the field in the dataset + * has a pinned inline presentation, see {@link InlinePresentation#isPinned()}), then + * the original dataset will not be replaced, + * so that it can be triggered as a pending intent again. + * If you provide a response, it will replace the * current response and the UI will be refreshed. For example, if you provided * credit card information without the CVV for the data set in the {@link FillResponse * response} then the returned data set should contain the CVV entry. diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java index 9cf1b87f7eab..914169485979 100644 --- a/core/java/android/service/autofill/InlinePresentation.java +++ b/core/java/android/service/autofill/InlinePresentation.java @@ -50,7 +50,11 @@ public final class InlinePresentation implements Parcelable { /** * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ private final boolean mPinned; @@ -90,7 +94,11 @@ public final class InlinePresentation implements Parcelable { * Specifies the UI specification for the inline suggestion. * @param pinned * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ @DataClass.Generated.Member public InlinePresentation( @@ -126,7 +134,11 @@ public final class InlinePresentation implements Parcelable { /** * Indicates whether the UI should be pinned, hence non-scrollable and non-filterable, in the - * host. + * host. However, it's eventually up to the host whether the UI is pinned or not. + * + * <p> Also a {@link Dataset} with a pinned inline presentation will not be replaced by the + * new data set returned from authentication intent. See + * {@link Dataset.Builder#setAuthentication(android.content.IntentSender)} for more information. */ @DataClass.Generated.Member public boolean isPinned() { @@ -232,7 +244,7 @@ public final class InlinePresentation implements Parcelable { }; @DataClass.Generated( - time = 1586992400667L, + time = 1593131904745L, codegenVersion = "1.0.15", sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java", inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.widget.inline.InlinePresentationSpec mInlinePresentationSpec\nprivate final boolean mPinned\npublic @android.annotation.NonNull @android.annotation.Size(min=0L) java.lang.String[] getAutofillHints()\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)") diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index bb0de120dedc..f937bc9e84a9 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -471,6 +471,13 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } private void performDrawFinished() { + if (mDeferredDestroySurfaceControl != null) { + synchronized (mSurfaceControlLock) { + mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); + mDeferredDestroySurfaceControl = null; + } + } + if (mPendingReportDraws > 0) { mDrawFinished = true; if (mAttachedToWindow) { @@ -1194,13 +1201,6 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall + "finishedDrawing"); } - if (mDeferredDestroySurfaceControl != null) { - synchronized (mSurfaceControlLock) { - mTmpTransaction.remove(mDeferredDestroySurfaceControl).apply(); - mDeferredDestroySurfaceControl = null; - } - } - runOnUiThread(this::performDrawFinished); } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 8c2358d253be..14cf258f18ab 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -2040,6 +2040,9 @@ public class ChooserActivity extends ResolverActivity implements if (!isUserRunning(userHandle)) { return false; } + if (!isUserUnlocked(userHandle)) { + return false; + } if (isQuietModeEnabled(userHandle)) { return false; } @@ -2892,6 +2895,12 @@ public class ChooserActivity extends ResolverActivity implements } @VisibleForTesting + protected boolean isUserUnlocked(UserHandle userHandle) { + UserManager userManager = getSystemService(UserManager.class); + return userManager.isUserUnlocked(userHandle); + } + + @VisibleForTesting protected boolean isQuietModeEnabled(UserHandle userHandle) { UserManager userManager = getSystemService(UserManager.class); return userManager.isQuietModeEnabled(userHandle); diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index fba4675a8c9f..233231cfcfdf 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1279,13 +1279,17 @@ public class ResolverActivity extends Activity implements } private void safelyStartActivityInternal(TargetInfo cti) { - if (mPersonalPackageMonitor != null) { - mPersonalPackageMonitor.unregister(); - } - if (mWorkPackageMonitor != null) { - mWorkPackageMonitor.unregister(); + // If the target is suspended, the activity will not be successfully launched. + // Do not unregister from package manager updates in this case + if (!cti.isSuspended()) { + if (mPersonalPackageMonitor != null) { + mPersonalPackageMonitor.unregister(); + } + if (mWorkPackageMonitor != null) { + mWorkPackageMonitor.unregister(); + } + mRegistered = false; } - mRegistered = false; // If needed, show that intent is forwarded // from managed profile to owner or other way around. if (mProfileSwitchMessageId != -1) { diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index 0bf10cb710cb..090645f6f7a8 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1996,7 +1996,7 @@ public class ChooserActivityTest { } @Test - public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() { + public void testWorkTab_selectingWorkTabWithNotRunningWorkUser_directShareTargetsNotQueried() { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); @@ -2035,7 +2035,7 @@ public class ChooserActivityTest { } @Test - public void testWorkTab_workUserLocked_workTargetsShown() { + public void testWorkTab_workUserNotRunning_workTargetsShown() { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); @@ -2059,6 +2059,70 @@ public class ChooserActivityTest { assertEquals(3, activity.getWorkListAdapter().getCount()); } + @Test + public void testWorkTab_selectingWorkTabWithLockedWorkUser_directShareTargetsNotQueried() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(3); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + sOverrides.isWorkProfileUserUnlocked = false; + boolean[] isQueryDirectShareCalledOnWorkProfile = new boolean[] { false }; + sOverrides.onQueryDirectShareTargets = chooserListAdapter -> { + isQueryDirectShareCalledOnWorkProfile[0] = + (chooserListAdapter.getUserHandle().getIdentifier() == 10); + return null; + }; + boolean[] isQueryTargetServicesCalledOnWorkProfile = new boolean[] { false }; + sOverrides.onQueryTargetServices = chooserListAdapter -> { + isQueryTargetServicesCalledOnWorkProfile[0] = + (chooserListAdapter.getUserHandle().getIdentifier() == 10); + return null; + }; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + assertFalse("Direct share targets were queried on a locked work profile user", + isQueryDirectShareCalledOnWorkProfile[0]); + assertFalse("Target services were queried on a locked work profile user", + isQueryTargetServicesCalledOnWorkProfile[0]); + } + + @Test + public void testWorkTab_workUserLocked_workTargetsShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(3); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + sOverrides.isWorkProfileUserUnlocked = false; + + final ChooserWrapperActivity activity = + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + assertEquals(3, activity.getWorkListAdapter().getCount()); + } + private Intent createChooserIntent(Intent intent, Intent[] initialIntents) { Intent chooserIntent = new Intent(); chooserIntent.setAction(Intent.ACTION_CHOOSER); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index b7d6c6196495..d3d5caf3f7e2 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -229,9 +229,20 @@ public class ChooserWrapperActivity extends ChooserActivity { @Override protected boolean isUserRunning(UserHandle userHandle) { + if (userHandle.equals(UserHandle.SYSTEM)) { + return super.isUserRunning(userHandle); + } return sOverrides.isWorkProfileUserRunning; } + @Override + protected boolean isUserUnlocked(UserHandle userHandle) { + if (userHandle.equals(UserHandle.SYSTEM)) { + return super.isUserUnlocked(userHandle); + } + return sOverrides.isWorkProfileUserUnlocked; + } + /** * We cannot directly mock the activity created since instrumentation creates it. * <p> @@ -258,6 +269,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public boolean hasCrossProfileIntents; public boolean isQuietModeEnabled; public boolean isWorkProfileUserRunning; + public boolean isWorkProfileUserUnlocked; public AbstractMultiProfilePagerAdapter.Injector multiPagerAdapterInjector; public PackageManager packageManager; @@ -281,6 +293,7 @@ public class ChooserWrapperActivity extends ChooserActivity { hasCrossProfileIntents = true; isQuietModeEnabled = false; isWorkProfileUserRunning = true; + isWorkProfileUserUnlocked = true; packageManager = null; multiPagerAdapterInjector = new AbstractMultiProfilePagerAdapter.Injector() { @Override diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 8520fffb3444..c710bed29361 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -424,6 +424,8 @@ applications that come with the platform <permission name="android.permission.LOCATION_HARDWARE" /> <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> <permission name="android.permission.CAPTURE_AUDIO_OUTPUT" /> + <!-- Permissions required for CTS test - AdbManagerTest --> + <permission name="android.permission.MANAGE_DEBUGGING" /> </privapp-permissions> <privapp-permissions package="com.android.statementservice"> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index bfc623faeaef..33ab16de7905 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/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml index e57d1cc11a20..908e2fbbff5b 100644 --- a/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/RestrictedLockUtils/res/values-pt-rPT/strings.xml @@ -18,5 +18,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="enabled_by_admin" msgid="6630472777476410137">"Ativado pelo administrador"</string> - <string name="disabled_by_admin" msgid="4023569940620832713">"Desativada pelo administrador"</string> + <string name="disabled_by_admin" msgid="4023569940620832713">"Desativado pelo administrador"</string> </resources> diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b64a9e7a25bf..570c2ab3efd1 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -314,6 +314,9 @@ <!-- Permissions required for GTS test - GtsDialerAudioTestCases --> <uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" /> + <!-- Permissions required for CTS test - AdbManagerTest --> + <uses-permission android:name="android.permission.MANAGE_DEBUGGING" /> + <application android:label="@string/app_label" android:theme="@android:style/Theme.DeviceDefault.DayNight" android:defaultToDeviceProtectedStorage="true" diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml index 8441282bd46d..3c641afea0d6 100644 --- a/packages/SystemUI/res/layout/media_view.xml +++ b/packages/SystemUI/res/layout/media_view.xml @@ -65,25 +65,16 @@ <!-- Actions must be ordered left-to-right even in RTL layout. However, they appear in a chain with the album art and the title, and must as a group appear at the end of that chain. This is - accomplished by having the guidebox (an invisible view that is positioned around all 5 actions) - in the chain with the album art and the title. The actions are in a LTR chain bounded by that - guidebox, and the ambiguity of how wide the guidebox should be is resolved by using a barrier - which forces it's starting edge to be as far to the end as possible while fitting the actions. - --> + accomplished by having all actions appear in a LTR chain within the parent, and then biasing it + to the right side, then this barrier is used to bound the text views. --> <androidx.constraintlayout.widget.Barrier android:id="@+id/media_action_barrier" android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" + app:layout_constraintTop_toTopOf="parent" app:barrierDirection="start" - /> - - <View - android:id="@+id/media_action_guidebox" - android:layout_width="0dp" - android:layout_height="48dp" - android:layout_marginTop="16dp" - android:visibility="invisible" + app:constraint_referenced_ids="action0,action1,action2,action3,action4" /> <ImageButton @@ -201,7 +192,6 @@ android:fontFamily="@*android:string/config_headlineFontFamilyMedium" android:singleLine="true" android:textColor="@color/media_primary_text" - android:textDirection="locale" android:textSize="16sp" /> <!-- Artist name --> @@ -212,14 +202,13 @@ android:fontFamily="@*android:string/config_headlineFontFamily" android:singleLine="true" android:textColor="@color/media_secondary_text" - android:textDirection="locale" android:textSize="14sp" /> <com.android.internal.widget.CachingIconView android:id="@+id/icon" android:tint="@color/media_primary_text" - android:layout_width="20dp" - android:layout_height="20dp" /> + android:layout_width="@dimen/qs_media_icon_size" + android:layout_height="@dimen/qs_media_icon_size" /> <!-- Buttons to remove this view when no longer needed --> <include diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 87e1499713bd..eb8758c0d921 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1269,10 +1269,10 @@ <dimen name="qs_media_padding">16dp</dimen> <dimen name="qs_media_panel_outer_padding">16dp</dimen> <dimen name="qs_media_album_size">52dp</dimen> + <dimen name="qs_media_icon_size">16dp</dimen> <dimen name="qs_center_guideline_padding">10dp</dimen> - <dimen name="qs_seamless_icon_size">20dp</dimen> - <dimen name="qs_seamless_fallback_icon_size">20dp</dimen> - <dimen name="qs_seamless_fallback_top_margin">18dp</dimen> + <dimen name="qs_seamless_icon_size">@dimen/qs_media_icon_size</dimen> + <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen> <dimen name="qs_seamless_fallback_end_margin">16dp</dimen> <dimen name="qqs_media_spacing">16dp</dimen> <dimen name="qs_footer_horizontal_margin">22dp</dimen> diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml index 5ac9f386dd47..ee958f28c51b 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" /> @@ -85,10 +85,11 @@ <!-- Song name --> <Constraint android:id="@+id/header_title" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="17dp" android:layout_marginStart="16dp" + app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/app_name" app:layout_constraintBottom_toTopOf="@id/header_artist" app:layout_constraintStart_toEndOf="@id/album_art" @@ -98,10 +99,11 @@ <!-- Artist name --> <Constraint android:id="@+id/header_artist" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="3dp" android:layout_marginBottom="24dp" + app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/header_title" app:layout_constraintStart_toStartOf="@id/header_title" app:layout_constraintEnd_toStartOf="@id/media_action_barrier" @@ -135,27 +137,6 @@ /> <Constraint - android:id="@+id/media_action_barrier" - android:layout_width="0dp" - android:layout_height="0dp" - android:orientation="vertical" - app:layout_constraintTop_toTopOf="parent" - app:barrierDirection="start" - app:constraint_referenced_ids="media_action_guidebox,action0,action1,action2,action3,action4" - /> - - <Constraint - android:id="@+id/media_action_guidebox" - android:layout_width="0dp" - android:layout_height="48dp" - android:layout_marginTop="18dp" - android:visibility="invisible" - app:layout_constraintTop_toBottomOf="@id/app_name" - app:layout_constraintStart_toEndOf="@id/header_title" - app:layout_constraintEnd_toEndOf="parent" - /> - - <Constraint android:id="@+id/action0" android:layout_width="48dp" android:layout_height="48dp" @@ -165,8 +146,9 @@ android:visibility="gone" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/app_name" - app:layout_constraintLeft_toLeftOf="@id/media_action_guidebox" + app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/action1" + app:layout_constraintHorizontal_bias="1" > </Constraint> @@ -220,7 +202,8 @@ app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintTop_toBottomOf="@id/app_name" app:layout_constraintLeft_toRightOf="@id/action3" - app:layout_constraintRight_toRightOf="@id/media_action_guidebox" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintHorizontal_bias="0" > </Constraint> </ConstraintSet> diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml index 46c9f97f5c87..d5a02c2d39d5 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" /> @@ -83,11 +83,12 @@ <!-- Song name --> <Constraint android:id="@+id/header_title" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/qs_media_panel_outer_padding" android:layout_marginTop="17dp" android:layout_marginStart="16dp" + app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@+id/app_name" app:layout_constraintStart_toEndOf="@id/album_art" app:layout_constraintEnd_toEndOf="parent" @@ -96,10 +97,11 @@ <!-- Artist name --> <Constraint android:id="@+id/header_artist" - android:layout_width="0dp" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="@dimen/qs_media_panel_outer_padding" android:layout_marginTop="3dp" + app:layout_constrainedWidth="true" app:layout_constraintTop_toBottomOf="@id/header_title" app:layout_constraintStart_toStartOf="@id/header_title" app:layout_constraintEnd_toEndOf="parent" diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index c170ee271e1d..ec4304f184f9 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 @@ public class BubbleData { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "Cancel overflow bubble: " + b); } + if (b != null) { + b.stopInflation(); + } mLogger.logOverflowRemove(b, reason); mStateChange.bubbleRemoved(b, reason); mOverflowBubbles.remove(b); @@ -483,6 +486,7 @@ public class BubbleData { return; } Bubble bubbleToRemove = mBubbles.get(indexToRemove); + bubbleToRemove.stopInflation(); if (mBubbles.size() == 1) { // Going to become empty, handle specially. setExpandedInternal(false); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 7c09accae649..1a730c39dcd3 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 @@ class MediaCarouselController @Inject constructor( if (numPages == 1) { pageIndicator.setLocation(0f) } + updatePageIndicatorAlpha() } /** @@ -464,7 +465,7 @@ class MediaCarouselController @Inject constructor( val width = desiredHostState?.measurementInput?.width ?: 0 val height = desiredHostState?.measurementInput?.height ?: 0 if (width != carouselMeasureWidth && width != 0 || - height != carouselMeasureWidth && height != 0) { + height != carouselMeasureHeight && height != 0) { carouselMeasureWidth = width carouselMeasureHeight = height val playerWidthPlusPadding = carouselMeasureWidth + diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt index ef2f71100e1a..3096908aca21 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt @@ -243,7 +243,7 @@ class MediaCarouselScrollHandler( } val rotation = (1.0f - settingsOffset) * 50 settingsButton.rotation = rotation * -Math.signum(contentTranslation) - val alpha = MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset) + val alpha = MathUtils.saturate(MathUtils.map(0.5f, 1.0f, 0.0f, 1.0f, settingsOffset)) settingsButton.alpha = alpha settingsButton.visibility = if (alpha != 0.0f) View.VISIBLE else View.INVISIBLE settingsButton.translationX = newTranslationX diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt index 07a7e618b301..570de635ea1d 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 @@ class MediaHost @Inject constructor( } // This will trigger a state change that ensures that we now have a state available state.measurementInput = input - return mediaHostStatesManager.getPlayerDimensions(state) + return mediaHostStatesManager.updateCarouselDimensions(location, state) } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt index f90af2a01de0..d3954b70ca71 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaHostStatesManager.kt @@ -32,6 +32,12 @@ class MediaHostStatesManager @Inject constructor() { private val controllers: MutableSet<MediaViewController> = mutableSetOf() /** + * The overall sizes of the carousel. This is needed to make sure all players in the carousel + * have equal size. + */ + val carouselSizes: MutableMap<Int, MeasurementOutput> = mutableMapOf() + + /** * A map with all media states of all locations. */ val mediaHostStates: MutableMap<Int, MediaHostState> = mutableMapOf() @@ -45,6 +51,7 @@ class MediaHostStatesManager @Inject constructor() { if (!hostState.equals(currentState)) { val newState = hostState.copy() mediaHostStates.put(location, newState) + updateCarouselDimensions(location, hostState) // First update all the controllers to ensure they get the chance to measure for (controller in controllers) { controller.stateCallback.onHostStateChanged(location, newState) @@ -61,7 +68,10 @@ class MediaHostStatesManager @Inject constructor() { * Get the dimensions of all players combined, which determines the overall height of the * media carousel and the media hosts. */ - fun getPlayerDimensions(hostState: MediaHostState): MeasurementOutput { + fun updateCarouselDimensions( + @MediaLocation location: Int, + hostState: MediaHostState + ): MeasurementOutput { val result = MeasurementOutput(0, 0) for (controller in controllers) { val measurement = controller.getMeasurementsForState(hostState) @@ -74,6 +84,7 @@ class MediaHostStatesManager @Inject constructor() { } } } + carouselSizes[location] = result return result } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 033a42a03240..83cfdd5f2699 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -79,6 +79,16 @@ class MediaViewController @Inject constructor( private val tmpState = TransitionViewState() /** + * A temporary state used to store intermediate measurements. + */ + private val tmpState2 = TransitionViewState() + + /** + * A temporary state used to store intermediate measurements. + */ + private val tmpState3 = TransitionViewState() + + /** * A temporary cache key to be used to look up cache entries */ private val tmpKey = CacheKey() @@ -304,8 +314,8 @@ class MediaViewController @Inject constructor( // Obtain the view state that we'd want to be at the end // The view might not be bound yet or has never been measured and in that case will be // reset once the state is fully available - val endViewState = obtainViewState(endHostState) ?: return - + var endViewState = obtainViewState(endHostState) ?: return + endViewState = updateViewStateToCarouselSize(endViewState, endLocation, tmpState2)!! layoutController.setMeasureState(endViewState) // If the view isn't bound, we can drop the animation, otherwise we'll execute it @@ -315,7 +325,8 @@ class MediaViewController @Inject constructor( } val result: TransitionViewState - val startViewState = obtainViewState(startHostState) + var startViewState = obtainViewState(startHostState) + startViewState = updateViewStateToCarouselSize(startViewState, startLocation, tmpState3) if (!endHostState.visible) { // Let's handle the case where the end is gone first. In this case we take the @@ -350,6 +361,22 @@ class MediaViewController @Inject constructor( animationDelay) } + private fun updateViewStateToCarouselSize( + viewState: TransitionViewState?, + location: Int, + outState: TransitionViewState + ) : TransitionViewState? { + val result = viewState?.copy(outState) ?: return null + val overrideSize = mediaHostStatesManager.carouselSizes[location] + overrideSize?.let { + // To be safe we're using a maximum here. The override size should always be set + // properly though. + result.height = Math.max(it.measuredHeight, result.height) + result.width = Math.max(it.measuredWidth, result.width) + } + return result + } + /** * Retrieves the [TransitionViewState] and [MediaHostState] of a [@MediaLocation]. * In the event of [location] not being visible, [locationWhenHidden] will be used instead. diff --git a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java index 1842564a4574..68b6785849aa 100644 --- a/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java +++ b/packages/SystemUI/src/com/android/systemui/media/ResumeMediaBrowser.java @@ -94,7 +94,7 @@ public class ResumeMediaBrowser { // a request with EXTRA_RECENT; if they don't, no resume controls MediaBrowser.MediaItem child = children.get(0); MediaDescription desc = child.getDescription(); - if (child.isPlayable()) { + if (child.isPlayable() && mMediaBrowser != null) { mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), ResumeMediaBrowser.this); } else { @@ -129,7 +129,7 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "Service connected for " + mComponentName); - if (mMediaBrowser.isConnected()) { + if (mMediaBrowser != null && mMediaBrowser.isConnected()) { String root = mMediaBrowser.getRoot(); if (!TextUtils.isEmpty(root)) { mCallback.onConnected(); @@ -187,7 +187,7 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected()); - if (!mMediaBrowser.isConnected()) { + if (mMediaBrowser == null || !mMediaBrowser.isConnected()) { mCallback.onError(); return; } @@ -246,7 +246,7 @@ public class ResumeMediaBrowser { @Override public void onConnected() { Log.d(TAG, "connected"); - if (!mMediaBrowser.isConnected() + if (mMediaBrowser == null || !mMediaBrowser.isConnected() || TextUtils.isEmpty(mMediaBrowser.getRoot())) { mCallback.onError(); } else { diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java index 79f99f459ace..3e06c1eb1ca3 100644 --- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java @@ -303,8 +303,11 @@ public class PipTouchHandler { hideDismissTarget(); }); - MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, - PipUtils.getTopPipActivity(mContext, mActivityManager)); + Pair<ComponentName, Integer> topPipActivity = PipUtils.getTopPipActivity(mContext, + mActivityManager); + if (topPipActivity.first != null) { + MetricsLoggerWrapper.logPictureInPictureDismissByDrag(mContext, topPipActivity); + } } }); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 8e878ddc6da1..d6e1a16bc69e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -18,6 +18,7 @@ package com.android.systemui.screenshot; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT; @@ -72,6 +73,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; @@ -87,6 +89,7 @@ import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.statusbar.phone.StatusBar; import java.util.ArrayList; @@ -220,6 +223,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private MediaActionSound mCameraSound; + private int mNavMode; + private int mLeftInset; + private int mRightInset; + // standard material ease private final Interpolator mFastOutSlowIn; @@ -301,6 +308,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mDismissButton.getBoundsOnScreen(dismissRect); touchRegion.op(dismissRect, Region.Op.UNION); + if (QuickStepContract.isGesturalMode(mNavMode)) { + // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE + Rect inset = new Rect(0, 0, mLeftInset, mDisplayMetrics.heightPixels); + touchRegion.op(inset, Region.Op.UNION); + inset.set(mDisplayMetrics.widthPixels - mRightInset, 0, mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels); + touchRegion.op(inset, Region.Op.UNION); + } + inoutInfo.touchableRegion.set(touchRegion); } @@ -356,6 +372,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (needsUpdate) { reloadAssets(); } + + mNavMode = mContext.getResources().getInteger( + com.android.internal.R.integer.config_navBarInteractionMode); } /** @@ -370,6 +389,25 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset // Inflate the screenshot layout mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null); + // TODO(159460485): Remove this when focus is handled properly in the system + mScreenshotLayout.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) { + // Once the user touches outside, stop listening for input + setWindowFocusable(false); + } + return false; + }); + mScreenshotLayout.setOnApplyWindowInsetsListener((v, insets) -> { + if (QuickStepContract.isGesturalMode(mNavMode)) { + Insets gestureInsets = insets.getInsets( + WindowInsets.Type.systemGestures()); + mLeftInset = gestureInsets.left; + mRightInset = gestureInsets.right; + } else { + mLeftInset = mRightInset = 0; + } + return mScreenshotLayout.onApplyWindowInsets(insets); + }); mScreenshotLayout.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { @@ -432,6 +470,21 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } } + /** + * Updates the window focusability. If the window is already showing, then it updates the + * window immediately, otherwise the layout params will be applied when the window is next + * shown. + */ + private void setWindowFocusable(boolean focusable) { + if (focusable) { + mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE; + } else { + mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE; + } + if (mScreenshotLayout.isAttachedToWindow()) { + mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams); + } + } /** * Creates a new worker thread and saves the screenshot to the media store. @@ -500,6 +553,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (mDismissAnimation != null && mDismissAnimation.isRunning()) { mDismissAnimation.cancel(); } + + // The window is focusable by default + setWindowFocusable(true); + // Start the post-screenshot animation startAnimation(finisher, screenRect, screenInsets, showFlash); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index b846aa08c33b..eca4c8082dfe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -316,7 +316,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim * of the timer and should be removed externally. * @return true if the notification is sticky */ - protected boolean isSticky() { + public boolean isSticky() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index c1acfbadef45..285cf7abce20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -382,9 +382,8 @@ public class NotifCollection implements Dumpable { final NotificationEntry entry = mNotificationSet.get(sbn.getKey()); if (entry == null) { - crashIfNotInitializing( - new IllegalStateException("No notification to remove with key " - + sbn.getKey())); + // TODO (b/160008901): Throw an exception here + mLogger.logNoNotificationToRemoveWithKey(sbn.getKey()); return; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 76751eaaecb1..f8a778d6b1d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -121,6 +121,14 @@ class NotifCollectionLogger @Inject constructor( }) } + fun logNoNotificationToRemoveWithKey(key: String) { + buffer.log(TAG, ERROR, { + str1 = key + }, { + "No notification to remove with key $str1" + }) + } + fun logRankingMissing(key: String, rankingMap: RankingMap) { buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" }) buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" }) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java index 52f7c2cfee96..7bd192d850c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java @@ -124,6 +124,9 @@ public class NotificationInlineImageResolver implements ImageResolver { */ Drawable resolveImage(Uri uri) throws IOException { BitmapDrawable image = resolveImageInternal(uri); + if (image == null || image.getBitmap() == null) { + throw new IOException("resolveImageInternal returned null for uri: " + uri); + } Bitmap bitmap = image.getBitmap(); image.setBitmap(Icon.scaleDownIfNecessary(bitmap, mMaxImageWidth, mMaxImageHeight)); return image; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 3dcf7ed674c7..e05ba12781c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -428,7 +428,7 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, @Override - protected boolean isSticky() { + public boolean isSticky() { return super.isSticky() || mMenuShownPinned; } @@ -568,6 +568,17 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } mKeysToRemoveWhenLeavingKeyguard.clear(); } + if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) { + ArrayList<String> keysToRemove = new ArrayList<>(); + for (AlertEntry entry : mAlertEntries.values()) { + if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) { + keysToRemove.add(entry.mEntry.getKey()); + } + } + for (String key : keysToRemove) { + removeAlertEntry(key); + } + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index c32133119386..375af6b099c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -435,6 +435,11 @@ public class NotificationPanelViewController extends PanelViewController { private Runnable mExpandAfterLayoutRunnable; /** + * Is this a collapse that started on the panel where we should allow the panel to intercept + */ + private boolean mIsPanelCollapseOnQQS; + + /** * If face auth with bypass is running for the first time after you turn on the screen. * (From aod or screen off) */ @@ -1064,7 +1069,11 @@ public class NotificationPanelViewController extends PanelViewController { mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { + if (mKeyguardShowing + && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { + // Dragging down on the lockscreen statusbar should prohibit other interactions + // immediately, otherwise we'll wait on the touchslop. This is to allow + // dragging down to expanded quick settings directly on the lockscreen. mView.getParent().requestDisallowInterceptTouchEvent(true); } if (mQsExpansionAnimator != null) { @@ -1097,9 +1106,10 @@ public class NotificationPanelViewController extends PanelViewController { trackMovement(event); return true; } - if (Math.abs(h) > getTouchSlop(event) + if ((h > getTouchSlop(event) || (h < -getTouchSlop(event) && mQsExpanded)) && Math.abs(h) > Math.abs(x - mInitialTouchX) && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { + mView.getParent().requestDisallowInterceptTouchEvent(true); mQsTracking = true; onQsExpansionStarted(); notifyExpandingFinished(); @@ -1139,6 +1149,7 @@ public class NotificationPanelViewController extends PanelViewController { mDownX = event.getX(); mDownY = event.getY(); mCollapsedOnDown = isFullyCollapsed(); + mIsPanelCollapseOnQQS = canPanelCollapseOnQQS(mDownX, mDownY); mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp(); mAllowExpandForSmallExpansion = mExpectingSynthesizedDown; mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown; @@ -1154,6 +1165,24 @@ public class NotificationPanelViewController extends PanelViewController { } } + /** + * Can the panel collapse in this motion because it was started on QQS? + * + * @param downX the x location where the touch started + * @param downY the y location where the touch started + * + * @return true if the panel could be collapsed because it stared on QQS + */ + private boolean canPanelCollapseOnQQS(float downX, float downY) { + if (mCollapsedOnDown || mKeyguardShowing || mQsExpanded) { + return false; + } + View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader(); + return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth() + && downY <= header.getBottom(); + + } + private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { float vel = getCurrentQSVelocity(); final boolean expandsQs = flingExpandsQs(vel); @@ -1903,10 +1932,11 @@ public class NotificationPanelViewController extends PanelViewController { } @Override - protected boolean isScrolledToBottom() { + protected boolean canCollapsePanelOnTouch() { if (!isInSettings()) { return mBarState == StatusBarState.KEYGUARD - || mNotificationStackScroller.isScrolledToBottom(); + || mNotificationStackScroller.isScrolledToBottom() + || mIsPanelCollapseOnQQS; } else { return true; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java index caddc4a874f4..732f25f90eb5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java @@ -460,7 +460,7 @@ public abstract class PanelViewController { } } - protected boolean isScrolledToBottom() { + protected boolean canCollapsePanelOnTouch() { return true; } @@ -1081,7 +1081,7 @@ public abstract class PanelViewController { * upwards. This allows closing the shade from anywhere inside the panel. * * We only do this if the current content is scrolled to the bottom, - * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling + * i.e canCollapsePanelOnTouch() is true and therefore there is no conflicting scrolling * gesture * possible. */ @@ -1092,7 +1092,7 @@ public abstract class PanelViewController { } final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); - boolean scrolledToBottom = isScrolledToBottom(); + boolean canCollapsePanel = canCollapsePanelOnTouch(); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: @@ -1139,7 +1139,7 @@ public abstract class PanelViewController { case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; addMovement(event); - if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) { + if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) { float hAbs = Math.abs(h); float touchSlop = getTouchSlop(event); if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop)) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java index 0d5a14960850..07de388598b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -368,7 +368,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager { protected boolean expanded; @Override - protected boolean isSticky() { + public boolean isSticky() { return (mEntry.isRowPinned() && expanded) || remoteInputActive || hasFullScreenIntent(mEntry); } diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 19c6b806bb59..3347cf6ca2a4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -20,9 +20,11 @@ import android.content.Context import android.graphics.Canvas import android.graphics.PointF import android.graphics.Rect +import android.text.Layout import android.util.AttributeSet import android.view.View import android.view.ViewTreeObserver +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import com.android.systemui.statusbar.CrossFadeHelper @@ -45,12 +47,29 @@ class TransitionLayout @JvmOverloads constructor( private var currentState: TransitionViewState = TransitionViewState() private var updateScheduled = false + private var desiredMeasureWidth = 0 + private var desiredMeasureHeight = 0 /** * The measured state of this view which is the one we will lay ourselves out with. This * may differ from the currentState if there is an external animation or transition running. * This state will not be used to measure the widgets, where the current state is preferred. */ var measureState: TransitionViewState = TransitionViewState() + set(value) { + val newWidth = value.width + val newHeight = value.height + if (newWidth != desiredMeasureWidth || newHeight != desiredMeasureHeight) { + desiredMeasureWidth = newWidth + desiredMeasureHeight = newHeight + // We need to make sure next time we're measured that our onMeasure will be called. + // Otherwise our parent thinks we still have the same height + if (isInLayout()) { + forceLayout() + } else { + requestLayout() + } + } + } private val preDrawApplicator = object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { updateScheduled = false @@ -85,6 +104,23 @@ class TransitionLayout @JvmOverloads constructor( for (i in 0 until childCount) { val child = getChildAt(i) val widgetState = currentState.widgetStates.get(child.id) ?: continue + + // TextViews which are measured and sized differently should be handled with a + // "clip mode", which means we clip explicitly rather than implicitly by passing + // different sizes to measure/layout than setLeftTopRightBottom. + // Then to accommodate RTL text, we need a "clip shift" which allows us to have the + // clipBounds be attached to the right side of the view instead of the left. + val clipModeShift = + if (child is TextView && widgetState.width < widgetState.measureWidth) { + if (child.layout.getParagraphDirection(0) == Layout.DIR_RIGHT_TO_LEFT) { + widgetState.measureWidth - widgetState.width + } else { + 0 + } + } else { + null + } + if (child.measuredWidth != widgetState.measureWidth || child.measuredHeight != widgetState.measureHeight) { val measureWidthSpec = MeasureSpec.makeMeasureSpec(widgetState.measureWidth, @@ -94,14 +130,17 @@ class TransitionLayout @JvmOverloads constructor( child.measure(measureWidthSpec, measureHeightSpec) child.layout(0, 0, child.measuredWidth, child.measuredHeight) } - val left = widgetState.x.toInt() + contentTranslationX + val clipShift = clipModeShift ?: 0 + val left = widgetState.x.toInt() + contentTranslationX - clipShift val top = widgetState.y.toInt() + contentTranslationY - child.setLeftTopRightBottom(left, top, left + widgetState.width, - top + widgetState.height) + val clipMode = clipModeShift != null + val boundsWidth = if (clipMode) widgetState.measureWidth else widgetState.width + val boundsHeight = if (clipMode) widgetState.measureHeight else widgetState.height + child.setLeftTopRightBottom(left, top, left + boundsWidth, top + boundsHeight) child.scaleX = widgetState.scale child.scaleY = widgetState.scale val clipBounds = child.clipBounds ?: Rect() - clipBounds.set(0, 0, widgetState.width, widgetState.height) + clipBounds.set(clipShift, 0, widgetState.width + clipShift, widgetState.height) child.clipBounds = clipBounds CrossFadeHelper.fadeIn(child, widgetState.alpha) child.visibility = if (widgetState.gone || widgetState.alpha == 0.0f) { @@ -136,7 +175,7 @@ class TransitionLayout @JvmOverloads constructor( MeasureSpec.EXACTLY) child.measure(measureWidthSpec, measureHeightSpec) } - setMeasuredDimension(measureState.width, measureState.height) + setMeasuredDimension(desiredMeasureWidth, desiredMeasureHeight) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 363fe95aae18..359faba48f08 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -1273,8 +1273,8 @@ public class NotifCollectionTest extends SysuiTestCase { verify(mInterceptor3, never()).shouldInterceptDismissal(clearable); } - @Test(expected = IllegalStateException.class) - public void testClearNotificationThrowsIfMissing() { + @Test + public void testClearNotificationDoesntThrowIfMissing() { // GIVEN that enough time has passed that we're beyond the forgiveness window mClock.advanceTime(5001); @@ -1287,7 +1287,8 @@ public class NotifCollectionTest extends SysuiTestCase { container.getSbn(), new RankingMap(new Ranking[]{ container.getRanking() })); - // THEN an exception is thrown + // THEN the event is ignored + verify(mCollectionListener, never()).onEntryRemoved(any(NotificationEntry.class), anyInt()); } @Test diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d2b1bd1a6008..499a2711d8e6 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -251,7 +251,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub //TODO: Remove this hack private boolean mInitialized; - private Point mTempPoint; + private Point mTempPoint = new Point(); private boolean mIsAccessibilityButtonShown; private AccessibilityUserState getCurrentUserStateLocked() { diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 373d47ed366b..6f2e6263b937 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -309,14 +309,9 @@ public class TouchExplorer extends BaseEventStreamTransformation @Override public void onDoubleTapAndHold(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - // Try to use the standard accessibility API to long click - if (!mAms.performActionOnAccessibilityFocusedItem( - AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK)) { - Slog.e(LOG_TAG, "ACTION_LONG_CLICK failed."); - if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) { - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - mState.startDelegating(); - } + if (mDispatcher.longPressWithTouchEvents(event, policyFlags)) { + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); + mState.startDelegating(); } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 9b3d075e3f2c..7ab4369b338a 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -73,6 +73,7 @@ import android.service.autofill.FieldClassificationUserData; import android.service.autofill.FillContext; import android.service.autofill.FillRequest; import android.service.autofill.FillResponse; +import android.service.autofill.InlinePresentation; import android.service.autofill.InternalSanitizer; import android.service.autofill.InternalValidator; import android.service.autofill.SaveInfo; @@ -1437,7 +1438,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClientState = newClientState; } final Dataset dataset = (Dataset) result; - authenticatedResponse.getDatasets().set(datasetIdx, dataset); + final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx); + if (!isPinnedDataset(oldDataset)) { + authenticatedResponse.getDatasets().set(datasetIdx, dataset); + } autoFill(requestId, datasetIdx, dataset, false); } else { Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id " @@ -1455,6 +1459,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + /** + * A dataset can potentially have multiple fields, and it's possible that some of the fields' + * has inline presentation and some don't. It's also possible that some of the fields' + * inline presentation is pinned and some isn't. So the concept of whether a dataset is + * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a + * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient + * for most of the cases. + */ + private static boolean isPinnedDataset(@Nullable Dataset dataset) { + if (dataset != null && dataset.getFieldIds() != null) { + final int numOfFields = dataset.getFieldIds().size(); + for (int i = 0; i < numOfFields; i++) { + final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(i); + if (inlinePresentation != null && inlinePresentation.isPinned()) { + return true; + } + } + } + return false; + } + @GuardedBy("mLock") void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) { final Dataset dataset = (data == null) ? null : diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java index ef81d7159c42..29bb5428dd84 100644 --- a/services/core/java/com/android/server/adb/AdbService.java +++ b/services/core/java/com/android/server/adb/AdbService.java @@ -34,6 +34,7 @@ import android.hardware.usb.UsbManager; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; @@ -509,6 +510,14 @@ public class AdbService extends IAdbManager.Stub { } @Override + public int handleShellCommand(ParcelFileDescriptor in, ParcelFileDescriptor out, + ParcelFileDescriptor err, String[] args) { + return new AdbShellCommand(this).exec( + this, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), + args); + } + + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; diff --git a/services/core/java/com/android/server/adb/AdbShellCommand.java b/services/core/java/com/android/server/adb/AdbShellCommand.java new file mode 100644 index 000000000000..76918529d071 --- /dev/null +++ b/services/core/java/com/android/server/adb/AdbShellCommand.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.adb; + +import android.os.BasicShellCommandHandler; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * Interprets and executes 'adb shell cmd adb [args]'. + */ +class AdbShellCommand extends BasicShellCommandHandler { + + private final AdbService mService; + + AdbShellCommand(AdbService service) { + mService = Objects.requireNonNull(service); + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "is-wifi-supported": { + pw.println(Boolean.toString(mService.isAdbWifiSupported())); + return 0; + } + case "is-wifi-qr-supported": { + pw.println(Boolean.toString(mService.isAdbWifiQrSupported())); + return 0; + } + default: + return handleDefaultCommands(cmd); + } + } + + @Override + public void onHelp() { + PrintWriter pw = getOutPrintWriter(); + pw.println("Adb service commands:"); + pw.println(" help or -h"); + pw.println(" Print this help text."); + pw.println(" is-wifi-supported"); + pw.println(" Returns \"true\" if adb over wifi is supported."); + pw.println(" is-wifi-qr-supported"); + pw.println(" Returns \"true\" if adb over wifi + QR pairing is supported."); + pw.println(); + } +} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index c9dbacda368c..a38d42b92600 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -18284,11 +18284,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } - // Now safely dispatch changes to device idle controller. - for (int i = 0; i < N; i++) { - PendingTempWhitelist ptw = list[i]; - mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, - ptw.duration, true, ptw.tag); + // Now safely dispatch changes to device idle controller. Skip this if we're early + // in boot and the controller hasn't yet been brought online: we do not apply + // device idle policy anyway at this phase. + if (mLocalDeviceIdleController != null) { + for (int i = 0; i < N; i++) { + PendingTempWhitelist ptw = list[i]; + mLocalDeviceIdleController.addPowerSaveTempWhitelistAppDirect(ptw.targetUid, + ptw.duration, true, ptw.tag); + } } // And now we can safely remove them from the map. diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 1cc41b22838e..5124c4a4797e 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -904,6 +904,10 @@ public final class BroadcastQueue { } else if (r.intent.getData() != null) { b.append(r.intent.getData()); } + if (DEBUG_BROADCAST) { + Slog.v(TAG, "Broadcast temp whitelist uid=" + uid + " duration=" + duration + + " : " + b.toString()); + } mService.tempWhitelistUidLocked(uid, duration, b.toString()); } diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 1a8de9771451..90370ddd21dd 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1616,6 +1616,7 @@ public class LockSettingsService extends ILockSettings.Stub { synchronized (mSeparateChallengeLock) { if (!setLockCredentialInternal(credential, savedCredential, userId, /* isLockTiedToParent= */ false)) { + scheduleGc(); return false; } setSeparateProfileChallengeEnabledLocked(userId, true, /* unused */ null); @@ -1626,6 +1627,7 @@ public class LockSettingsService extends ILockSettings.Stub { setDeviceUnlockedForUser(userId); } notifySeparateProfileChallengeChanged(userId); + scheduleGc(); return true; } @@ -1965,7 +1967,11 @@ public class LockSettingsService extends ILockSettings.Stub { public VerifyCredentialResponse checkCredential(LockscreenCredential credential, int userId, ICheckCredentialProgressCallback progressCallback) { checkPasswordReadPermission(userId); - return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback); + try { + return doVerifyCredential(credential, CHALLENGE_NONE, 0, userId, progressCallback); + } finally { + scheduleGc(); + } } @Override @@ -1978,8 +1984,12 @@ public class LockSettingsService extends ILockSettings.Stub { challengeType = CHALLENGE_NONE; } - return doVerifyCredential(credential, challengeType, challenge, userId, - null /* progressCallback */); + try { + return doVerifyCredential(credential, challengeType, challenge, userId, + null /* progressCallback */); + } finally { + scheduleGc(); + } } private VerifyCredentialResponse doVerifyCredential(LockscreenCredential credential, @@ -2070,6 +2080,8 @@ public class LockSettingsService extends ILockSettings.Stub { | BadPaddingException | CertificateException | IOException e) { Slog.e(TAG, "Failed to decrypt child profile key", e); throw new IllegalStateException("Unable to get tied profile token"); + } finally { + scheduleGc(); } } @@ -2983,27 +2995,31 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public byte[] getHashFactor(LockscreenCredential currentCredential, int userId) { checkPasswordReadPermission(userId); - if (isManagedProfileWithUnifiedLock(userId)) { - try { - currentCredential = getDecryptedPasswordForTiedProfile(userId); - } catch (Exception e) { - Slog.e(TAG, "Failed to get work profile credential", e); - return null; - } - } - synchronized (mSpManager) { - if (!isSyntheticPasswordBasedCredentialLocked(userId)) { - Slog.w(TAG, "Synthetic password not enabled"); - return null; + try { + if (isManagedProfileWithUnifiedLock(userId)) { + try { + currentCredential = getDecryptedPasswordForTiedProfile(userId); + } catch (Exception e) { + Slog.e(TAG, "Failed to get work profile credential", e); + return null; + } } - long handle = getSyntheticPasswordHandleLocked(userId); - AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword( - getGateKeeperService(), handle, currentCredential, userId, null); - if (auth.authToken == null) { - Slog.w(TAG, "Current credential is incorrect"); - return null; + synchronized (mSpManager) { + if (!isSyntheticPasswordBasedCredentialLocked(userId)) { + Slog.w(TAG, "Synthetic password not enabled"); + return null; + } + long handle = getSyntheticPasswordHandleLocked(userId); + AuthenticationResult auth = mSpManager.unwrapPasswordBasedSyntheticPassword( + getGateKeeperService(), handle, currentCredential, userId, null); + if (auth.authToken == null) { + Slog.w(TAG, "Current credential is incorrect"); + return null; + } + return auth.authToken.derivePasswordHashFactor(); } - return auth.authToken.derivePasswordHashFactor(); + } finally { + scheduleGc(); } } @@ -3287,6 +3303,22 @@ public class LockSettingsService extends ILockSettings.Stub { } } + /** + * Schedules garbage collection to sanitize lockscreen credential remnants in memory. + * + * One source of leftover lockscreen credentials is the unmarshalled binder method arguments. + * Since this method will be called within the binder implementation method, a small delay is + * added before the GC operation to allow the enclosing binder proxy code to complete and + * release references to the argument. + */ + private void scheduleGc() { + mHandler.postDelayed(() -> { + System.gc(); + System.runFinalization(); + System.gc(); + }, 2000); + } + private class DeviceProvisionedObserver extends ContentObserver { private final Uri mDeviceProvisionedUri = Settings.Global.getUriFor( Settings.Global.DEVICE_PROVISIONED); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 088c5daf30a4..670b88e4a0c9 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 @@ public class PackageManagerService extends IPackageManager.Stub idleController.addPowerSaveTempWhitelistAppDirect(Process.myUid(), idleDuration, false, "integrity component"); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppWhitelistDuration(idleDuration); + mContext.sendOrderedBroadcastAsUser(integrityVerification, UserHandle.SYSTEM, /* receiverPermission= */ null, + /* appOp= */ AppOpsManager.OP_NONE, + /* options= */ options.toBundle(), new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -15274,6 +15279,8 @@ public class PackageManagerService extends IPackageManager.Stub DeviceIdleInternal idleController = mInjector.getLocalDeviceIdleController(); final long idleDuration = getVerificationTimeout(); + final BroadcastOptions options = BroadcastOptions.makeBasic(); + options.setTemporaryAppWhitelistDuration(idleDuration); /* * If any sufficient verifiers were listed in the package @@ -15293,7 +15300,9 @@ public class PackageManagerService extends IPackageManager.Stub final Intent sufficientIntent = new Intent(verification); sufficientIntent.setComponent(verifierComponent); - mContext.sendBroadcastAsUser(sufficientIntent, verifierUser); + mContext.sendBroadcastAsUser(sufficientIntent, verifierUser, + /* receiverPermission= */ null, + options.toBundle()); } } } @@ -15312,6 +15321,8 @@ public class PackageManagerService extends IPackageManager.Stub verifierUser.getIdentifier(), false, "package verifier"); mContext.sendOrderedBroadcastAsUser(verification, verifierUser, android.Manifest.permission.PACKAGE_VERIFICATION_AGENT, + /* appOp= */ AppOpsManager.OP_NONE, + /* options= */ options.toBundle(), new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c4663ca19f87..2e9f70448488 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -3146,11 +3146,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override - boolean checkCompleteDeferredRemoval() { + boolean handleCompleteDeferredRemoval() { if (mIsExiting) { removeIfPossible(); } - return super.checkCompleteDeferredRemoval(); + return super.handleCompleteDeferredRemoval(); } void onRemovedFromDisplay() { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index b4bc0f5b3a32..8f0de7312ee5 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -86,7 +86,6 @@ import static com.android.server.wm.TaskProto.ACTIVITY_TYPE; import static com.android.server.wm.TaskProto.ANIMATING_BOUNDS; import static com.android.server.wm.TaskProto.BOUNDS; import static com.android.server.wm.TaskProto.CREATED_BY_ORGANIZER; -import static com.android.server.wm.TaskProto.DEFER_REMOVAL; import static com.android.server.wm.TaskProto.DISPLAY_ID; import static com.android.server.wm.TaskProto.FILLS_PARENT; import static com.android.server.wm.TaskProto.LAST_NON_FULLSCREEN_BOUNDS; @@ -248,11 +247,6 @@ class ActivityStack extends Task { private Rect mTmpRect = new Rect(); private Rect mTmpRect2 = new Rect(); - /** Detach this stack from its display when animation completes. */ - // TODO: maybe tie this to WindowContainer#removeChild some how... - // TODO: This is no longer set. Okay to remove or was the set removed by accident? - private boolean mDeferRemoval; - // If this is true, we are in the bounds animating mode. The task will be down or upscaled to // perfectly fit the region it would have been cropped to. We may also avoid certain logic we // would otherwise apply while resizing, while resizing in the bounds animating mode. @@ -3232,9 +3226,6 @@ class ActivityStack extends Task { @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { - if (mDeferRemoval) { - pw.println(prefix + "mDeferRemoval=true"); - } super.dump(pw, prefix, dumpAll); if (!mExitingActivities.isEmpty()) { pw.println(); @@ -3304,15 +3295,12 @@ class ActivityStack extends Task { } /** Returns true if a removal action is still being deferred. */ - boolean checkCompleteDeferredRemoval() { + boolean handleCompleteDeferredRemoval() { if (isAnimating(TRANSITION | CHILDREN)) { return true; } - if (mDeferRemoval) { - removeImmediately(); - } - return super.checkCompleteDeferredRemoval(); + return super.handleCompleteDeferredRemoval(); } public DisplayInfo getDisplayInfo() { @@ -3380,7 +3368,6 @@ class ActivityStack extends Task { mLastNonFullscreenBounds.dumpDebug(proto, LAST_NON_FULLSCREEN_BOUNDS); } - proto.write(DEFER_REMOVAL, mDeferRemoval); proto.write(ANIMATING_BOUNDS, mBoundsAnimating); if (mSurfaceControl != null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 241de2e0e068..c56440785bba 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2739,6 +2739,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @Override void removeImmediately() { mRemovingDisplay = true; + mDeferredRemoval = false; try { if (mParentWindow != null) { mParentWindow.removeEmbeddedDisplayContent(this); @@ -2771,31 +2772,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo /** Returns true if a removal action is still being deferred. */ @Override - boolean checkCompleteDeferredRemoval() { - boolean stillDeferringRemoval = false; - - for (int i = getChildCount() - 1; i >= 0; --i) { - final DisplayChildWindowContainer child = getChildAt(i); - stillDeferringRemoval |= child.checkCompleteDeferredRemoval(); - if (getChildCount() == 0) { - // If this display is pending to be removed because it contains an activity with - // {@link ActivityRecord#mIsExiting} is true, this display may be removed when - // completing the removal of the last activity from - // {@link ActivityRecord#checkCompleteDeferredRemoval}. - return false; - } - } + boolean handleCompleteDeferredRemoval() { + final boolean stillDeferringRemoval = super.handleCompleteDeferredRemoval(); if (!stillDeferringRemoval && mDeferredRemoval) { removeImmediately(); return false; } - return true; - } - - /** @return 'true' if removal of this display content is deferred due to active animation. */ - boolean isRemovalDeferred() { - return mDeferredRemoval; + return stillDeferringRemoval; } void adjustForImeIfNeeded() { diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 1b58fc1d2d3e..851b533a550d 100644 --- a/services/core/java/com/android/server/wm/RecentTasks.java +++ b/services/core/java/com/android/server/wm/RecentTasks.java @@ -1426,8 +1426,8 @@ class RecentTasks { private void removeUnreachableHiddenTasks(int windowingMode) { for (int i = mHiddenTasks.size() - 1; i >= 0; i--) { final Task hiddenTask = mHiddenTasks.get(i); - if (!hiddenTask.hasChild()) { - // The task was removed by other path. + if (!hiddenTask.hasChild() || hiddenTask.inRecents) { + // The task was removed by other path or it became reachable (added to recents). mHiddenTasks.remove(i); continue; } @@ -1449,6 +1449,9 @@ class RecentTasks { * of task as the given one. */ private void removeForAddTask(Task task) { + // The adding task will be in recents so it is not hidden. + mHiddenTasks.remove(task); + final int removeIndex = findRemoveIndexForAddTask(task); if (removeIndex == -1) { // Nothing to trim @@ -1460,8 +1463,6 @@ class RecentTasks { // callbacks here. final Task removedTask = mTasks.remove(removeIndex); if (removedTask != task) { - // The added task is in recents so it is not hidden. - mHiddenTasks.remove(task); if (removedTask.hasChild()) { // A non-empty task is replaced by a new task. Because the removed task is no longer // managed by the recent tasks list, add it to the hidden list to prevent the task diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 2d30b737b274..119b188dfa00 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -990,9 +990,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } // Remove all deferred displays stacks, tasks, and activities. - for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) { - mChildren.get(displayNdx).checkCompleteDeferredRemoval(); - } + handleCompleteDeferredRemoval(); forAllDisplays(dc -> { dc.getInputMonitor().updateInputWindowsLw(true /*force*/); diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 3360951a8f3f..205a8d2aeeac 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1746,10 +1746,15 @@ final class TaskDisplayArea extends DisplayArea<ActivityStack> { void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { - for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { - final ActivityStack stack = getStackAt(stackNdx); - stack.ensureActivitiesVisible(starting, configChanges, preserveWindows, - notifyClients); + mAtmService.mStackSupervisor.beginActivityVisibilityUpdate(); + try { + for (int stackNdx = getStackCount() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = getStackAt(stackNdx); + stack.ensureActivitiesVisible(starting, configChanges, preserveWindows, + notifyClients); + } + } finally { + mAtmService.mStackSupervisor.endActivityVisibilityUpdate(); } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 2977968f5754..5a73fabaf9d0 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -1044,13 +1044,25 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< return mChildren.peekLast(); } - /** Returns true if there is still a removal being deferred */ - boolean checkCompleteDeferredRemoval() { + /** + * Removes the containers which were deferred. + * + * @return {@code true} if there is still a removal being deferred. + */ + boolean handleCompleteDeferredRemoval() { boolean stillDeferringRemoval = false; for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); - stillDeferringRemoval |= wc.checkCompleteDeferredRemoval(); + stillDeferringRemoval |= wc.handleCompleteDeferredRemoval(); + if (!hasChild()) { + // All child containers of current level could be removed from a removal of + // descendant. E.g. if a display is pending to be removed because it contains an + // activity with {@link ActivityRecord#mIsExiting} is true, the display may be + // removed when completing the removal of the last activity from + // {@link ActivityRecord#checkCompleteDeferredRemoval}. + return false; + } } return stillDeferringRemoval; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b1756b07ad83..f4281fc0cafd 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 @@ public class WindowManagerService extends IWindowManager.Stub } final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId); - final boolean waitingForConfig = displayContent != null && displayContent.mWaitingForConfig; - final int numOpeningApps = displayContent != null ? displayContent.mOpeningApps.size() : 0; - if (waitingForConfig || mAppsFreezingScreen > 0 + final int numOpeningApps; + final boolean waitingForConfig; + final boolean waitingForRemoteRotation; + if (displayContent != null) { + numOpeningApps = displayContent.mOpeningApps.size(); + waitingForConfig = displayContent.mWaitingForConfig; + waitingForRemoteRotation = + displayContent.getDisplayRotation().isWaitingForRemoteRotation(); + } else { + waitingForConfig = waitingForRemoteRotation = false; + numOpeningApps = 0; + } + if (waitingForConfig || waitingForRemoteRotation || mAppsFreezingScreen > 0 || mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE || mClientFreezingScreen || numOpeningApps > 0) { - ProtoLog.d(WM_DEBUG_ORIENTATION, - "stopFreezingDisplayLocked: Returning mWaitingForConfig=%b, " - + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, " - + "mClientFreezingScreen=%b, mOpeningApps.size()=%d", - waitingForConfig, mAppsFreezingScreen, mWindowsFreezingScreen, - mClientFreezingScreen, numOpeningApps); + ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning " + + "waitingForConfig=%b, waitingForRemoteRotation=%b, " + + "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, " + + "mClientFreezingScreen=%b, mOpeningApps.size()=%d", + waitingForConfig, waitingForRemoteRotation, + mAppsFreezingScreen, mWindowsFreezingScreen, + mClientFreezingScreen, numOpeningApps); return; } @@ -5627,7 +5638,6 @@ public class WindowManagerService extends IWindowManager.Stub // We must make a local copy of the displayId as it can be potentially overwritten later on // in this method. For example, {@link startFreezingDisplayLocked} may be called as a result // of update rotation, but we reference the frozen display after that call in this method. - final int displayId = mFrozenDisplayId; mFrozenDisplayId = INVALID_DISPLAY; mDisplayFrozen = false; mInputManagerCallback.thawInputDispatchingLw(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f1acee5031d8..d4d2f4d7a492 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -81,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_POINTER; import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION; import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE; import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION; +import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; @@ -2372,6 +2373,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + if (mAttrs.type == TYPE_SCREENSHOT) { + // Disallow screenshot windows from being IME targets + return false; + } + final boolean windowsAreFocusable = mActivityRecord == null || mActivityRecord.windowsAreFocusable(); if (!windowsAreFocusable) { // This window can't be an IME target if the app's windows should not be focusable. diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 30af1d34f558..ddb186a1d2da 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1328,9 +1328,10 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayRotation dr = dc.getDisplayRotation(); doCallRealMethod().when(dr).updateRotationUnchecked(anyBoolean()); - Mockito.doReturn(ROTATION_90).when(dr).rotationForOrientation(anyInt(), anyInt()); + // Rotate 180 degree so the display doesn't have configuration change. This condition is + // used for the later verification of stop-freezing (without setting mWaitingForConfig). + doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt()); final boolean[] continued = new boolean[1]; - // TODO(display-merge): Remove cast doAnswer( invocation -> { continued[0] = true; @@ -1356,9 +1357,16 @@ public class DisplayContentTests extends WindowTestsBase { dc.setRotationAnimation(null); mWm.updateRotation(true /* alwaysSendConfiguration */, false /* forceRelayout */); + // If remote rotation is not finished, the display should not be able to unfreeze. + mWm.stopFreezingDisplayLocked(); + assertTrue(mWm.mDisplayFrozen); + assertTrue(called[0]); waitUntilHandlersIdle(); assertTrue(continued[0]); + + mWm.stopFreezingDisplayLocked(); + assertFalse(mWm.mDisplayFrozen); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 5005c07832ab..fd169018782b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -464,15 +464,19 @@ public class RecentTasksTest extends ActivityTestsBase { mRecentTasks.add(task1); final Task task2 = taskBuilder.apply(true /* visible */); mRecentTasks.add(task2); - // Only the last task is kept in recents and the previous 2 tasks will becomes untracked + final Task task3 = createTaskBuilder(className).build(); + mRecentTasks.add(task3); + // Only the last added task is kept in recents and the previous 2 tasks will become hidden // tasks because their intents are identical. - mRecentTasks.add(createTaskBuilder(className).build()); + mRecentTasks.add(task1); // Go home to trigger the removal of untracked tasks. mRecentTasks.add(createTaskBuilder(".Home").setStack(mTaskContainer.getRootHomeTask()) .build()); + // The task was added into recents again so it is not hidden and shouldn't be removed. + assertNotNull(task1.getTopNonFinishingActivity()); // All activities in the invisible task should be finishing or removed. - assertNull(task1.getTopNonFinishingActivity()); + assertNull(task3.getTopNonFinishingActivity()); // The visible task should not be affected. assertNotNull(task2.getTopNonFinishingActivity()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index 87485eac3412..efc03df877b7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -27,6 +27,7 @@ import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; @@ -825,6 +826,31 @@ public class WindowContainerTests extends WindowTestsBase { } @Test + public void testHandleCompleteDeferredRemoval() { + final DisplayContent displayContent = createNewDisplay(); + // Do not reparent activity to default display when removing the display. + doReturn(true).when(displayContent).shouldDestroyContentOnRemove(); + final ActivityRecord r = new ActivityTestsBase.StackBuilder(mWm.mRoot) + .setDisplay(displayContent).build().getTopMostActivity(); + // Add a window and make the activity animating so the removal of activity is deferred. + createWindow(null, TYPE_BASE_APPLICATION, r, "win"); + doReturn(true).when(r).isAnimating(anyInt(), anyInt()); + + displayContent.remove(); + // Ensure that ActivityRecord#onRemovedFromDisplay is called. + r.destroyed("test"); + // The removal is deferred, so the activity is still in the display. + assertEquals(r, displayContent.getTopMostActivity()); + + // Assume the animation is done so the deferred removal can continue. + doReturn(false).when(r).isAnimating(anyInt(), anyInt()); + + assertFalse(displayContent.handleCompleteDeferredRemoval()); + assertFalse(displayContent.hasChild()); + assertFalse(r.hasChild()); + } + + @Test public void testTaskCanApplyAnimation() { final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent); final Task task = createTaskInStack(stack, 0 /* userId */); |