Merge "Serialize PowerStatsExporter sessions" into main
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index a4ca55e..2f515fe 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -201,7 +201,9 @@
* @param exceptionHandler an optional {@link RemoteException} handler
*/
@AnyThread
- @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @RequiresPermission(allOf = {
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void removeImeSurface(int displayId,
@Nullable Consumer<RemoteException> exceptionHandler) {
final IInputMethodManager service = getService();
@@ -441,7 +443,9 @@
}
@AnyThread
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
@@ -469,7 +473,9 @@
}
@AnyThread
- @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @RequiresPermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
static void onImeSwitchButtonClickFromSystem(int displayId) {
final IInputMethodManager service = getService();
if (service == null) {
diff --git a/core/java/android/window/TaskFragmentInfo.java b/core/java/android/window/TaskFragmentInfo.java
index fa51957..23a1224 100644
--- a/core/java/android/window/TaskFragmentInfo.java
+++ b/core/java/android/window/TaskFragmentInfo.java
@@ -102,6 +102,8 @@
@NonNull
private final Point mMinimumDimensions = new Point();
+ private final boolean mIsTopNonFishingChild;
+
/** @hide */
public TaskFragmentInfo(
@NonNull IBinder fragmentToken, @NonNull WindowContainerToken token,
@@ -110,7 +112,7 @@
@NonNull List<IBinder> inRequestedTaskFragmentActivities,
@NonNull Point positionInParent, boolean isTaskClearedForReuse,
boolean isTaskFragmentClearedForPip, boolean isClearedForReorderActivityToFront,
- @NonNull Point minimumDimensions) {
+ @NonNull Point minimumDimensions, boolean isTopNonFinishingChild) {
mFragmentToken = requireNonNull(fragmentToken);
mToken = requireNonNull(token);
mConfiguration.setTo(configuration);
@@ -123,6 +125,7 @@
mIsTaskFragmentClearedForPip = isTaskFragmentClearedForPip;
mIsClearedForReorderActivityToFront = isClearedForReorderActivityToFront;
mMinimumDimensions.set(minimumDimensions);
+ mIsTopNonFishingChild = isTopNonFinishingChild;
}
@NonNull
@@ -212,6 +215,16 @@
}
/**
+ * Indicates that this TaskFragment is the top non-finishing child of its parent container
+ * among all Activities and TaskFragment siblings.
+ *
+ * @hide
+ */
+ public boolean isTopNonFinishingChild() {
+ return mIsTopNonFishingChild;
+ }
+
+ /**
* Returns {@code true} if the parameters that are important for task fragment organizers are
* equal between this {@link TaskFragmentInfo} and {@param that}.
* Note that this method is usually called with
@@ -236,7 +249,8 @@
&& mIsTaskClearedForReuse == that.mIsTaskClearedForReuse
&& mIsTaskFragmentClearedForPip == that.mIsTaskFragmentClearedForPip
&& mIsClearedForReorderActivityToFront == that.mIsClearedForReorderActivityToFront
- && mMinimumDimensions.equals(that.mMinimumDimensions);
+ && mMinimumDimensions.equals(that.mMinimumDimensions)
+ && mIsTopNonFishingChild == that.mIsTopNonFishingChild;
}
private TaskFragmentInfo(Parcel in) {
@@ -252,6 +266,7 @@
mIsTaskFragmentClearedForPip = in.readBoolean();
mIsClearedForReorderActivityToFront = in.readBoolean();
mMinimumDimensions.readFromParcel(in);
+ mIsTopNonFishingChild = in.readBoolean();
}
/** @hide */
@@ -269,6 +284,7 @@
dest.writeBoolean(mIsTaskFragmentClearedForPip);
dest.writeBoolean(mIsClearedForReorderActivityToFront);
mMinimumDimensions.writeToParcel(dest, flags);
+ dest.writeBoolean(mIsTopNonFishingChild);
}
@NonNull
@@ -299,6 +315,7 @@
+ " isTaskFragmentClearedForPip=" + mIsTaskFragmentClearedForPip
+ " mIsClearedForReorderActivityToFront=" + mIsClearedForReorderActivityToFront
+ " minimumDimensions=" + mMinimumDimensions
+ + " isTopNonFinishingChild=" + mIsTopNonFishingChild
+ "}";
}
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 4230641..4c18bbf 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -51,13 +51,6 @@
flag {
namespace: "windowing_sdk"
- name: "embedded_activity_back_nav_flag"
- description: "Refines embedded activity back navigation behavior"
- bug: "293642394"
-}
-
-flag {
- namespace: "windowing_sdk"
name: "cover_display_opt_in"
is_exported: true
description: "Properties to allow apps and activities to opt-in to cover display rendering"
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 3f7ba0a..b51678e 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -125,9 +125,9 @@
void showInputMethodPickerFromClient(in IInputMethodClient client,
int auxiliarySubtypeMode);
- @EnforcePermission("WRITE_SECURE_SETTINGS")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ @EnforcePermission(allOf = {"WRITE_SECURE_SETTINGS", "INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@EnforcePermission("TEST_INPUT_METHOD")
@@ -143,9 +143,9 @@
* @param displayId The ID of the display where the input method picker dialog should be shown.
* @param userId The ID of the user that triggered the click.
*/
- @EnforcePermission("WRITE_SECURE_SETTINGS")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
+ @EnforcePermission(allOf = {"WRITE_SECURE_SETTINGS" ,"INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.WRITE_SECURE_SETTINGS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
oneway void onImeSwitchButtonClickFromSystem(int displayId);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
@@ -168,9 +168,9 @@
oneway void reportPerceptibleAsync(in IBinder windowToken, boolean perceptible);
- @EnforcePermission("INTERNAL_SYSTEM_WINDOW")
- @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
- + "android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
+ @EnforcePermission(allOf = {"INTERNAL_SYSTEM_WINDOW", "INTERACT_ACROSS_USERS_FULL"})
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf = {android.Manifest."
+ + "permission.INTERNAL_SYSTEM_WINDOW, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})")
void removeImeSurface(int displayId);
/** Remove the IME surface. Requires passing the currently focused window. */
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 7c62615..638591f 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2292,7 +2292,7 @@
criteria.mValue.mUsage);
jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
gAudioMixMatchCriterionAttrCstor,
- jMixMatchCriterion, criteria.mRule);
+ jAudioAttributes, criteria.mRule);
break;
case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
jAudioAttributes = env->NewObject(gAudioAttributesClass, gAudioAttributesCstor);
@@ -2300,7 +2300,7 @@
criteria.mValue.mSource);
jMixMatchCriterion = env->NewObject(gAudioMixMatchCriterionClass,
gAudioMixMatchCriterionAttrCstor,
- jMixMatchCriterion, criteria.mRule);
+ jAudioAttributes, criteria.mRule);
break;
}
env->CallBooleanMethod(jAudioMixMatchCriterionList, gArrayListMethods.add,
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ec865f6..e94db2d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6555,4 +6555,7 @@
<string name="keyboard_shortcut_group_applications_maps">Maps</string>
<!-- User visible title for the keyboard shortcut group containing system-wide application launch shortcuts. [CHAR-LIMIT=70] -->
<string name="keyboard_shortcut_group_applications">Applications</string>
+
+ <!-- Fingerprint loe notification string -->
+ <string name="fingerprint_loe_notification_msg">Your fingerprints can no longer be recognized. Set up Fingerprint Unlock again.</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6b58396..cbf3fe7 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5584,4 +5584,7 @@
<java-symbol type="string" name="keyboard_shortcut_group_applications_music" />
<java-symbol type="string" name="keyboard_shortcut_group_applications_sms" />
<java-symbol type="string" name="keyboard_shortcut_group_applications" />
+
+ <!-- Fingerprint loe notification string -->
+ <java-symbol type="string" name="fingerprint_loe_notification_msg" />
</resources>
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index b13bcd1..444ed51 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -25,7 +25,7 @@
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
index 3e45a79..46f22ce 100644
--- a/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
+++ b/core/tests/coretests/src/android/net/NetworkRecommendationProviderTest.java
@@ -26,7 +26,7 @@
import android.Manifest.permission;
import android.content.Context;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java b/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
index bc12e72..7413ede 100644
--- a/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
+++ b/core/tests/coretests/src/android/net/SSLCertificateSocketFactoryTest.java
@@ -19,7 +19,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/net/ScoredNetworkTest.java b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
index d984d86..63eeaa1 100644
--- a/core/tests/coretests/src/android/net/ScoredNetworkTest.java
+++ b/core/tests/coretests/src/android/net/ScoredNetworkTest.java
@@ -26,7 +26,7 @@
import android.os.Bundle;
import android.os.Parcel;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/net/SntpClientTest.java b/core/tests/coretests/src/android/net/SntpClientTest.java
index 267fc2b..024d614 100644
--- a/core/tests/coretests/src/android/net/SntpClientTest.java
+++ b/core/tests/coretests/src/android/net/SntpClientTest.java
@@ -29,7 +29,7 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import libcore.util.HexEncoding;
diff --git a/core/tests/coretests/src/android/net/sntp/Duration64Test.java b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
index b228596..b177e18 100644
--- a/core/tests/coretests/src/android/net/sntp/Duration64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Duration64Test.java
@@ -23,7 +23,7 @@
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
index 200c80e..9f95132 100644
--- a/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
+++ b/core/tests/coretests/src/android/net/sntp/Timestamp64Test.java
@@ -23,7 +23,7 @@
import android.platform.test.annotations.Presubmit;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 8e1fde0..409cde3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -119,7 +119,8 @@
// TODO(b/243518738): Move to WM Extensions if we have requirement of overlay without
// association. It's not set in WM Extensions nor Wm Jetpack library currently.
- private static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
+ @VisibleForTesting
+ static final String KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY =
"androidx.window.extensions.embedding.shouldAssociateWithLaunchingActivity";
@VisibleForTesting
@@ -2742,89 +2743,70 @@
}
final int taskId = getTaskId(launchActivity);
- if (!overlayContainers.isEmpty()) {
- for (final TaskFragmentContainer overlayContainer : overlayContainers) {
- final boolean isTopNonFinishingOverlay = overlayContainer.equals(
- overlayContainer.getTaskContainer().getTopNonFinishingTaskFragmentContainer(
- true /* includePin */, true /* includeOverlay */));
- if (taskId != overlayContainer.getTaskId()) {
- // If there's an overlay container with same tag in a different task,
- // dismiss the overlay container since the tag must be unique per process.
- if (overlayTag.equals(overlayContainer.getOverlayTag())) {
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different task ID:" + overlayContainer.getTaskId() + ". "
- + "The new associated activity is " + launchActivity);
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- }
- continue;
- }
- if (!overlayTag.equals(overlayContainer.getOverlayTag())) {
- // If there's an overlay container with different tag on top in the same
- // task, dismiss the existing overlay container.
- if (isTopNonFinishingOverlay) {
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- }
- continue;
- }
- // The overlay container has the same tag and task ID with the new launching
- // overlay container.
- if (!isTopNonFinishingOverlay) {
- // Dismiss the invisible overlay container regardless of activity
- // association if it collides the tag of new launched overlay container .
- Log.w(TAG, "The invisible overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's a launching overlay container with the same tag."
- + " The new associated activity is " + launchActivity);
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- }
- // Requesting an always-on-top overlay.
- if (!associateLaunchingActivity) {
- if (overlayContainer.isOverlayWithActivityAssociation()) {
- // Dismiss the overlay container since it has associated with an activity.
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different associated launching activity. The overlay container"
- + " doesn't associate with any activity.");
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- } else {
- // The existing overlay container doesn't associate an activity as well.
- // Just update the overlay and return.
- // Note that going to this condition means the tag, task ID matches a
- // visible always-on-top overlay, and won't dismiss any overlay any more.
- mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
- getMinDimensions(intent));
- return overlayContainer;
- }
- }
- if (launchActivity.getActivityToken()
- != overlayContainer.getAssociatedActivityToken()) {
- Log.w(TAG, "The overlay container with tag:"
- + overlayContainer.getOverlayTag() + " is dismissed because"
- + " there's an existing overlay container with the same tag but"
- + " different associated launching activity. The new associated"
- + " activity is " + launchActivity);
- // The associated activity must be the same, or it will be dismissed.
- mPresenter.cleanupContainer(wct, overlayContainer,
- false /* shouldFinishDependant */);
- continue;
- }
- // Reaching here means the launching activity launch an overlay container with the
- // same task ID, tag, while there's a previously launching visible overlay
- // container. We'll regard it as updating the existing overlay container.
+ // Overlay container policy:
+ // 1. Overlay tag must be unique per process.
+ // a. For associated overlay, if a new launched overlay container has the same tag as
+ // an existing one, the existing overlay will be dismissed regardless of its task
+ // and window hierarchy.
+ // b. For always-on-top overlay, if there's an overlay container has the same tag in the
+ // launched task, the overlay container will be re-used, which means the
+ // ActivityStackAttributes will be applied and the launched activity will be positioned
+ // on top of the overlay container.
+ // 2. There must be at most one overlay that partially occludes a visible activity per task.
+ // a. For associated overlay, only the top visible overlay container in the launched task
+ // will be dismissed.
+ // b. Always-on-top overlay is always visible. If there's an overlay with different tags
+ // in the same task, the overlay will be dismissed in case an activity above
+ // the overlay is dismissed and the overlay is shown unexpectedly.
+ for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+ final boolean isTopNonFinishingOverlay = overlayContainer.isTopNonFinishingChild();
+ final boolean areInSameTask = taskId == overlayContainer.getTaskId();
+ final boolean haveSameTag = overlayTag.equals(overlayContainer.getOverlayTag());
+ if (!associateLaunchingActivity && overlayContainer.isAlwaysOnTopOverlay()
+ && haveSameTag && areInSameTask) {
+ // Just launch the activity and update the existing always-on-top overlay
+ // if the requested overlay is an always-on-top overlay with the same tag
+ // as the existing one.
mPresenter.applyActivityStackAttributes(wct, overlayContainer, attrs,
getMinDimensions(intent));
return overlayContainer;
-
}
+ if (haveSameTag) {
+ // For other tag match, we should clean up the existing overlay since the overlay
+ // tag must be unique per process.
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + " because there's an existing overlay container with the same tag.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ if (!areInSameTask) {
+ // Early return here because we won't clean-up or update overlay from different
+ // tasks except tag collision.
+ continue;
+ }
+ if (associateLaunchingActivity) {
+ // For associated overlay, we only dismiss the overlay if it's the top non-finishing
+ // child of its parent container.
+ if (isTopNonFinishingOverlay) {
+ Log.w(TAG, "The on-top overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + "because we only allow one overlay on top.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
+ }
+ continue;
+ }
+ // Otherwise, we should clean up the overlay in the task because we only allow one
+ // overlay when an always-on-top overlay is launched.
+ Log.w(TAG, "The overlay container with tag:"
+ + overlayContainer.getOverlayTag() + " is dismissed with "
+ + " the launching activity=" + launchActivity
+ + "because an always-on-top overlay is launched.");
+ mPresenter.cleanupContainer(wct, overlayContainer,
+ false /* shouldFinishDependant */);
}
// Launch the overlay container to the task with taskId.
return createEmptyContainer(wct, intent, taskId, attrs, launchActivity, overlayTag,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 7173b0c..d0e2c99 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -340,6 +340,13 @@
return mInfo != null && mInfo.isVisible();
}
+ /**
+ * See {@link TaskFragmentInfo#isTopNonFinishingChild()}
+ */
+ boolean isTopNonFinishingChild() {
+ return mInfo != null && mInfo.isTopNonFinishingChild();
+ }
+
/** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
boolean isInIntermediateState() {
if (mInfo == null) {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index d649c6d..7dc78fd 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -163,12 +163,14 @@
}
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity) {
return createMockTaskFragmentInfo(container, activity, true /* isVisible */);
}
/** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity, boolean isVisible) {
return new TaskFragmentInfo(container.getTaskFragmentToken(),
@@ -182,7 +184,27 @@
false /* isTaskClearedForReuse */,
false /* isTaskFragmentClearedForPip */,
false /* isClearedForReorderActivityToFront */,
- new Point());
+ new Point(),
+ false /* isTopChild */);
+ }
+
+ /** Creates a mock TaskFragmentInfo for the given TaskFragment. */
+ @NonNull
+ static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+ @NonNull Activity activity, boolean isVisible, boolean isOnTop) {
+ return new TaskFragmentInfo(container.getTaskFragmentToken(),
+ mock(WindowContainerToken.class),
+ new Configuration(),
+ 1,
+ isVisible,
+ Collections.singletonList(activity.getActivityToken()),
+ new ArrayList<>(),
+ new Point(),
+ false /* isTaskClearedForReuse */,
+ false /* isTaskFragmentClearedForPip */,
+ false /* isClearedForReorderActivityToFront */,
+ new Point(),
+ isOnTop);
}
static ActivityInfo createActivityInfoWithMinDimensions() {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index ad41b18..8911d18 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -114,6 +114,7 @@
mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */,
false /* isVisible */, new ArrayList<>(), new ArrayList<>(), new Point(),
false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */,
- false /* isClearedForReorderActivityToFront */, new Point());
+ false /* isClearedForReorderActivityToFront */, new Point(),
+ false /* isTopChild */);
}
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 1c4c887..475475b 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -30,6 +30,7 @@
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer;
+import static androidx.window.extensions.embedding.SplitController.KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT;
@@ -94,6 +95,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -267,7 +269,7 @@
}
@Test
- public void testCreateOrUpdateOverlay_visibleOverlaySameTagInTask_dismissOverlay() {
+ public void testCreateOrUpdateOverlay_topOverlayInTask_dismissOverlay() {
createExistingOverlayContainers();
final TaskFragmentContainer overlayContainer =
@@ -295,26 +297,6 @@
}
@Test
- public void testCreateOrUpdateOverlay_sameTagTaskAndActivity_updateOverlay() {
- createExistingOverlayContainers();
-
- final Rect bounds = new Rect(0, 0, 100, 100);
- mSplitController.setActivityStackAttributesCalculator(params ->
- new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
- final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
- mOverlayContainer1.getOverlayTag());
-
- assertWithMessage("overlayContainer1 must be updated since the new overlay container"
- + " is launched with the same tag and task")
- .that(mSplitController.getAllNonFinishingOverlayContainers())
- .containsExactly(mOverlayContainer1, mOverlayContainer2);
-
- assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
- verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
- eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
- }
-
- @Test
public void testCreateOrUpdateOverlay_sameTagAndTaskButNotActivity_dismissOverlay() {
createExistingOverlayContainers();
@@ -362,6 +344,43 @@
}
@Test
+ public void testCreateOrUpdateAlwaysOnTopOverlay_dismissMultipleOverlaysInTask() {
+ createExistingOverlayContainers();
+ // Create another overlay in task.
+ final TaskFragmentContainer overlayContainer3 =
+ createTestOverlayContainer(TASK_ID, "test3");
+ assertThat(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer1, mOverlayContainer2, overlayContainer3);
+
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateAlwaysOnTopOverlay("test4");
+
+ assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer2, overlayContainer);
+ }
+
+ @Test
+ public void testCreateOrUpdateAlwaysOnTopOverlay_updateOverlay() {
+ createExistingOverlayContainers();
+ // Create another overlay in task.
+ final TaskFragmentContainer alwaysOnTopOverlay = createTestOverlayContainer(TASK_ID,
+ "test3", true /* isVisible */, false /* associateLaunchingActivity */);
+ final ActivityStackAttributes attrs = new ActivityStackAttributes.Builder()
+ .setRelativeBounds(new Rect(0, 0, 100, 100)).build();
+ mSplitController.setActivityStackAttributesCalculator(params -> attrs);
+
+ Mockito.clearInvocations(mSplitPresenter);
+ final TaskFragmentContainer overlayContainer =
+ createOrUpdateAlwaysOnTopOverlay(alwaysOnTopOverlay.getOverlayTag());
+
+ assertWithMessage("overlayContainer1 and overlayContainer3 must be dismissed")
+ .that(mSplitController.getAllNonFinishingOverlayContainers())
+ .containsExactly(mOverlayContainer2, alwaysOnTopOverlay);
+ assertThat(overlayContainer).isEqualTo(alwaysOnTopOverlay);
+ }
+
+ @Test
public void testCreateOrUpdateOverlay_launchFromSplit_returnNull() {
final Activity primaryActivity = createMockActivity();
final Activity secondaryActivity = createMockActivity();
@@ -381,13 +400,13 @@
}
private void createExistingOverlayContainers() {
- createExistingOverlayContainers(true /* visible */);
+ createExistingOverlayContainers(true /* isOnTop */);
}
- private void createExistingOverlayContainers(boolean visible) {
- mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", visible,
+ private void createExistingOverlayContainers(boolean isOnTop) {
+ mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1", isOnTop,
true /* associatedLaunchingActivity */, mActivity);
- mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", visible);
+ mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2", isOnTop);
List<TaskFragmentContainer> overlayContainers = mSplitController
.getAllNonFinishingOverlayContainers();
assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
@@ -966,6 +985,16 @@
launchOptions, mIntent, activity);
}
+ @Nullable
+ private TaskFragmentContainer createOrUpdateAlwaysOnTopOverlay(
+ @NonNull String tag) {
+ final Bundle launchOptions = new Bundle();
+ launchOptions.putBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, false);
+ launchOptions.putString(KEY_OVERLAY_TAG, tag);
+ return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+ launchOptions, mIntent, createMockActivity());
+ }
+
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
@@ -975,10 +1004,10 @@
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@NonNull
private TaskFragmentContainer createMockTaskFragmentContainer(
- @NonNull Activity activity, boolean isVisible) {
+ @NonNull Activity activity, boolean isOnTop) {
final TaskFragmentContainer container = createTfContainer(mSplitController,
activity.getTaskId(), activity);
- setupTaskFragmentInfo(container, activity, isVisible);
+ setupTaskFragmentInfo(container, activity, isOnTop);
return container;
}
@@ -990,8 +1019,8 @@
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible) {
- return createTestOverlayContainer(taskId, tag, isVisible,
+ boolean isOnTop) {
+ return createTestOverlayContainer(taskId, tag, isOnTop,
true /* associateLaunchingActivity */);
}
@@ -1002,11 +1031,9 @@
null /* launchingActivity */);
}
- // TODO(b/243518738): add more test coverage on overlay container without activity association
- // once we have use cases.
@NonNull
private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag,
- boolean isVisible, boolean associateLaunchingActivity,
+ boolean isOnTop, boolean associateLaunchingActivity,
@Nullable Activity launchingActivity) {
final Activity activity = launchingActivity != null
? launchingActivity : createMockActivity();
@@ -1017,14 +1044,15 @@
.setLaunchOptions(Bundle.EMPTY)
.setAssociatedActivity(associateLaunchingActivity ? activity : null)
.build();
- setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible);
+ setupTaskFragmentInfo(overlayContainer, createMockActivity(), isOnTop);
return overlayContainer;
}
private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
@NonNull Activity activity,
- boolean isVisible) {
- final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isVisible);
+ boolean isOnTop) {
+ final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity, isOnTop,
+ isOnTop);
container.setInfo(mTransaction, info);
mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index ea7e968..06c1e68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -104,6 +104,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -112,7 +113,7 @@
context, shellInit, shellCommandHandler, shellController, displayController,
displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, mainExecutor));
+ pipTransitionState, pipTouchHandler, mainExecutor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 8aa0933..94fe286 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -88,6 +88,7 @@
private final TaskStackListenerImpl mTaskStackListener;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final PipTransitionState mPipTransitionState;
+ private final PipTouchHandler mPipTouchHandler;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
private Consumer<Boolean> mOnIsInPipStateChangedListener;
@@ -130,6 +131,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
ShellExecutor mainExecutor) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
@@ -144,6 +146,7 @@
mShellTaskOrganizer = shellTaskOrganizer;
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
+ mPipTouchHandler = pipTouchHandler;
mMainExecutor = mainExecutor;
mImpl = new PipImpl();
@@ -168,6 +171,7 @@
TaskStackListenerImpl taskStackListener,
ShellTaskOrganizer shellTaskOrganizer,
PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -177,7 +181,7 @@
return new PipController(context, shellInit, shellCommandHandler, shellController,
displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
- pipTransitionState, mainExecutor);
+ pipTransitionState, pipTouchHandler, mainExecutor);
}
public PipImpl getPipImpl() {
@@ -204,7 +208,9 @@
mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
@Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
+ public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
+ mPipTouchHandler.onImeVisibilityChanged(imeVisible, imeHeight);
+ }
});
// Allow other outside processes to bind to PiP controller using the key below.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index e1e072a..83253c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -134,6 +134,8 @@
private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_NO_BOUNCY);
+ @Nullable private Runnable mUpdateMovementBoundsRunnable;
+
private final Consumer<Rect> mUpdateBoundsCallback = (Rect newBounds) -> {
if (mPipBoundsState.getBounds().equals(newBounds)) {
return;
@@ -141,6 +143,7 @@
mMenuController.updateMenuLayout(newBounds);
mPipBoundsState.setBounds(newBounds);
+ maybeUpdateMovementBounds();
};
/**
@@ -566,11 +569,20 @@
+ " callers=\n%s", TAG, originalBounds, offset,
Debug.getCallers(5, " "));
}
+ if (offset == 0) {
+ return;
+ }
+
cancelPhysicsAnimation();
- /*
- mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION,
- mUpdateBoundsCallback);
- */
+
+ Rect adjustedBounds = new Rect(originalBounds);
+ adjustedBounds.offset(0, offset);
+
+ setAnimatingToBounds(adjustedBounds);
+ Bundle extra = new Bundle();
+ extra.putBoolean(ANIMATING_BOUNDS_CHANGE, true);
+ extra.putInt(ANIMATING_BOUNDS_CHANGE_DURATION, SHIFT_DURATION);
+ mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);
}
/**
@@ -585,11 +597,11 @@
/** Set new fling configs whose min/max values respect the given movement bounds. */
private void rebuildFlingConfigs() {
mFlingConfigX = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).left,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).right);
+ mPipBoundsState.getMovementBounds().left,
+ mPipBoundsState.getMovementBounds().right);
mFlingConfigY = new PhysicsAnimator.FlingConfig(DEFAULT_FRICTION,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).top,
- mPipBoundsAlgorithm.getMovementBounds(getBounds()).bottom);
+ mPipBoundsState.getMovementBounds().top,
+ mPipBoundsState.getMovementBounds().bottom);
final Rect insetBounds = mPipBoundsState.getDisplayLayout().stableInsets();
mStashConfigX = new PhysicsAnimator.FlingConfig(
DEFAULT_FRICTION,
@@ -671,6 +683,16 @@
cleanUpHighPerfSessionMaybe();
}
+ void setUpdateMovementBoundsRunnable(Runnable updateMovementBoundsRunnable) {
+ mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
+ }
+
+ private void maybeUpdateMovementBounds() {
+ if (mUpdateMovementBoundsRunnable != null) {
+ mUpdateMovementBoundsRunnable.run();
+ }
+ }
+
/**
* Notifies the floating coordinator that we're moving, and sets the animating to bounds so
* we return these bounds from
@@ -807,8 +829,14 @@
startTx, finishTx, mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
destinationBounds, duration, 0f /* angle */);
animator.setAnimationEndCallback(() -> {
- mPipBoundsState.setBounds(destinationBounds);
- // All motion operations have actually finished, so make bounds cache updates.
+ mUpdateBoundsCallback.accept(destinationBounds);
+
+ // In case an ongoing drag/fling was present before a deterministic resize transition
+ // kicked in, we need to update the update bounds properly before cleaning in-motion
+ // state.
+ mPipBoundsState.getMotionBoundsState().setBoundsInMotion(destinationBounds);
+ settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */);
+
cleanUpHighPerfSessionMaybe();
// Signal that we are done with resize transition
mPipScheduler.scheduleFinishResizePip(true /* configAtEnd */);
@@ -817,7 +845,7 @@
}
private void settlePipBoundsAfterPhysicsAnimation(boolean animatingAfter) {
- if (!animatingAfter) {
+ if (!animatingAfter && mPipBoundsState.getMotionBoundsState().isInMotion()) {
// The physics animation ended, though we may not necessarily be done animating, such as
// when we're still dragging after moving out of the magnetic target. Only set the final
// bounds state and clear motion bounds completely if the whole animation is over.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
index 5b0ca18..d28204a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java
@@ -146,8 +146,8 @@
mUpdateResizeBoundsCallback = (rect) -> {
mUserResizeBounds.set(rect);
// mMotionHelper.synchronizePinnedStackBounds();
- mUpdateMovementBoundsRunnable.run();
mPipBoundsState.setBounds(rect);
+ mUpdateMovementBoundsRunnable.run();
resetState();
};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 53b80e8..f387e72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -199,6 +199,7 @@
mMenuController.addListener(new PipMenuListener());
mGesture = new DefaultPipTouchGesture();
mMotionHelper = pipMotionHelper;
+ mMotionHelper.setUpdateMovementBoundsRunnable(this::updateMovementBounds);
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
@@ -317,6 +318,8 @@
mFloatingContentCoordinator.onContentRemoved(mMotionHelper);
mPipResizeGestureHandler.onActivityUnpinned();
mPipInputConsumer.unregisterInputConsumer();
+ mPipBoundsState.setHasUserMovedPip(false);
+ mPipBoundsState.setHasUserResizedPip(false);
}
void onPinnedStackAnimationEnded(
@@ -346,6 +349,22 @@
void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mIsImeShowing = imeVisible;
mImeHeight = imeHeight;
+
+ // Cache new movement bounds using the new potential IME height.
+ updateMovementBounds();
+
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ int delta = mPipBoundsState.getMovementBounds().bottom
+ - mPipBoundsState.getBounds().top;
+
+ boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+ || mPipBoundsState.hasUserResizedPip());
+ if ((imeVisible && delta < 0) || (!imeVisible && !hasUserInteracted)) {
+ // The policy is to ignore an IME disappearing if user has interacted with PiP.
+ // Otherwise, only offset due to an appearing IME if PiP occludes it.
+ mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+ }
+ });
}
void onShelfVisibilityChanged(boolean shelfVisible, int shelfHeight) {
@@ -1077,6 +1096,7 @@
switch (newState) {
case PipTransitionState.ENTERED_PIP:
onActivityPinned();
+ updateMovementBounds();
mTouchState.setAllowInputEvents(true);
mTouchState.reset();
break;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
index 29272be..a132796f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java
@@ -149,6 +149,12 @@
@Nullable
private SurfaceControl mSwipePipToHomeOverlay;
+ //
+ // Scheduling-related state
+ //
+ @Nullable
+ private Runnable mOnIdlePipTransitionStateRunnable;
+
/**
* An interface to track state updates as we progress through PiP transitions.
*/
@@ -197,6 +203,8 @@
mState = state;
dispatchPipTransitionStateChanged(prevState, mState, extra);
}
+
+ maybeRunOnIdlePipTransitionStateCallback();
}
/**
@@ -231,6 +239,29 @@
}
/**
+ * Schedule a callback to run when in a valid idle PiP state.
+ *
+ * <p>We only allow for one callback to be scheduled to avoid cases with multiple transitions
+ * being scheduled. For instance, if user double taps and IME shows, this would
+ * schedule a bounds change transition for IME appearing. But if some other transition would
+ * want to animate PiP before the scheduled callback executes, we would rather want to replace
+ * the existing callback with a new one, to avoid multiple animations
+ * as soon as we are idle.</p>
+ */
+ public void setOnIdlePipTransitionStateRunnable(
+ @Nullable Runnable onIdlePipTransitionStateRunnable) {
+ mOnIdlePipTransitionStateRunnable = onIdlePipTransitionStateRunnable;
+ maybeRunOnIdlePipTransitionStateCallback();
+ }
+
+ private void maybeRunOnIdlePipTransitionStateCallback() {
+ if (mOnIdlePipTransitionStateRunnable != null && isPipStateIdle()) {
+ mOnIdlePipTransitionStateRunnable.run();
+ mOnIdlePipTransitionStateRunnable = null;
+ }
+ }
+
+ /**
* Adds a {@link PipTransitionStateChangedListener} for future PiP transition state updates.
*/
public void addPipTransitionStateChangedListener(PipTransitionStateChangedListener listener) {
@@ -318,6 +349,11 @@
throw new IllegalStateException("Unknown state: " + state);
}
+ public boolean isPipStateIdle() {
+ // This needs to be a valid in-PiP state that isn't a transient state.
+ return mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS;
+ }
+
@Override
public String toString() {
return String.format("PipTransitionState(mState=%s, mInSwipePipToHomeTransition=%b)",
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 4217562..1024a55 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -578,6 +578,8 @@
});
private AudioAttributes() {
+ mBundle = null;
+ mFormattedTags = "";
}
/**
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 395f81d..0ffab4b 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -1166,10 +1166,11 @@
/**
- * Returns whether the device supports observer mode or not. When observe
- * mode is enabled, the NFC hardware will listen for NFC readers, but not
- * respond to them. When observe mode is disabled, the NFC hardware will
- * resoond to the reader and proceed with the transaction.
+ * Returns whether the device supports observe mode or not. When observe mode is enabled, the
+ * NFC hardware will listen to NFC readers, but not respond to them. While enabled, observed
+ * polling frames will be sent to the APDU service (see {@link #setObserveModeEnabled(boolean)}.
+ * When observe mode is disabled (or if it's not supported), the NFC hardware will automatically
+ * respond to the reader and proceed with the transaction.
* @return true if the mode is supported, false otherwise.
*/
@FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE)
@@ -1193,9 +1194,10 @@
* and simply observe and notify the APDU service of polling loop frames. See
* {@link #isObserveModeSupported()} for a description of observe mode. Only the package of the
* currently preferred service (the service set as preferred by the current foreground
- * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
- * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
- * otherwise a call to this method will fail and return false.
+ * application via {@link android.nfc.cardemulation.CardEmulation#setPreferredService(Activity,
+ * android.content.ComponentName)} or the current Default Wallet Role Holder
+ * {@link android.app.role.RoleManager#ROLE_WALLET}), otherwise a call to this method will fail
+ * and return false.
*
* @param enabled false disables observe mode to allow the transaction to proceed while true
* enables observe mode and does not allow transactions to proceed.
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 92fd9cb..15c8850 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,3 +14,10 @@
description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
bug: "294254230"
}
+
+flag {
+ name: "notify_fingerprint_loe"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether a notification should be sent to notify user when loss of enrollment happens"
+ bug: "351036558"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
index 53e6bdb..27f9cc8 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java
@@ -151,6 +151,43 @@
}
/**
+ * Shows a fingerprint notification for loss of enrollment
+ */
+ public static void showFingerprintLoeNotification(@NonNull Context context) {
+ Slog.d(TAG, "Showing fingerprint LOE notification");
+
+ final String name =
+ context.getString(R.string.device_unlock_notification_name);
+ final String title = context.getString(R.string.fingerprint_dangling_notification_title);
+ final String content = context.getString(R.string.fingerprint_loe_notification_msg);
+
+ // Create "Set up" notification action button.
+ final Intent setupIntent =
+ new Intent(BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH);
+ final PendingIntent setupPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+ setupIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+ final String setupText =
+ context.getString(R.string.biometric_dangling_notification_action_set_up);
+ final Notification.Action setupAction = new Notification.Action.Builder(
+ null, setupText, setupPendingIntent).build();
+
+ // Create "Not now" notification action button.
+ final Intent notNowIntent =
+ new Intent(BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_DISMISS);
+ final PendingIntent notNowPendingIntent = PendingIntent.getBroadcastAsUser(context, 0,
+ notNowIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+ final String notNowText = context.getString(
+ R.string.biometric_dangling_notification_action_not_now);
+ final Notification.Action notNowAction = new Notification.Action.Builder(
+ null, notNowText, notNowPendingIntent).build();
+
+ showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction,
+ notNowAction, Notification.CATEGORY_SYSTEM, FINGERPRINT_RE_ENROLL_CHANNEL,
+ FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false,
+ Notification.FLAG_NO_CLEAR);
+ }
+
+ /**
* Shows a fingerprint bad calibration notification.
*/
public static void showBadCalibrationNotification(@NonNull Context context) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
index 7fb27b6..63678aa 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUserState.java
@@ -57,6 +57,7 @@
protected boolean mInvalidationInProgress;
protected final Context mContext;
protected final File mFile;
+ private boolean mIsInvalidBiometricState = false;
private final Runnable mWriteStateRunnable = this::doWriteStateInternal;
@@ -102,7 +103,7 @@
serializer.endDocument();
destination.finishWrite(out);
} catch (Throwable t) {
- Slog.wtf(TAG, "Failed to write settings, restoring backup", t);
+ Slog.e(TAG, "Failed to write settings, restoring backup", t);
destination.failWrite(out);
throw new IllegalStateException("Failed to write to file: " + mFile.toString(), t);
} finally {
@@ -192,6 +193,29 @@
}
}
+ /**
+ * Return true if the biometric file is correctly read. Otherwise return false.
+ */
+ public boolean isInvalidBiometricState() {
+ return mIsInvalidBiometricState;
+ }
+
+ /**
+ * Delete the file of the biometric state.
+ */
+ public void deleteBiometricFile() {
+ synchronized (this) {
+ if (!mFile.exists()) {
+ return;
+ }
+ if (mFile.delete()) {
+ Slog.i(TAG, mFile + " is deleted successfully");
+ } else {
+ Slog.i(TAG, "Failed to delete " + mFile);
+ }
+ }
+ }
+
private boolean isUnique(String name) {
for (T identifier : mBiometrics) {
if (identifier.getName().equals(name)) {
@@ -218,7 +242,8 @@
try {
in = new FileInputStream(mFile);
} catch (FileNotFoundException fnfe) {
- Slog.i(TAG, "No fingerprint state");
+ Slog.i(TAG, "No fingerprint state", fnfe);
+ mIsInvalidBiometricState = true;
return;
}
try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
index ebe4679..0b4f640 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricUtils.java
@@ -33,4 +33,14 @@
CharSequence getUniqueName(Context context, int userId);
void setInvalidationInProgress(Context context, int userId, boolean inProgress);
boolean isInvalidationInProgress(Context context, int userId);
+
+ /**
+ * Return true if the biometric file is correctly read. Otherwise return false.
+ */
+ boolean hasValidBiometricUserState(Context context, int userId);
+
+ /**
+ * Delete the file of the biometric state.
+ */
+ void deleteStateForUser(int userId);
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 69ad152..3b6aeef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.biometrics.BiometricsProto;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -62,7 +63,7 @@
}
private final ArrayList<UserTemplate> mUnknownHALTemplates = new ArrayList<>();
- private final BiometricUtils<S> mBiometricUtils;
+ protected final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
@@ -105,6 +106,11 @@
startCleanupUnknownHalTemplates();
}
}
+
+ if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
+ && Flags.notifyFingerprintLoe()) {
+ handleInvalidBiometricState();
+ }
}
};
@@ -248,4 +254,8 @@
public ArrayList<UserTemplate> getUnknownHALTemplates() {
return mUnknownHALTemplates;
}
+
+ protected void handleInvalidBiometricState() {}
+
+ protected abstract int getModality();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
index c574478..79285cb 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceUtils.java
@@ -124,6 +124,22 @@
return getStateForUser(context, userId).isInvalidationInProgress();
}
+ @Override
+ public boolean hasValidBiometricUserState(Context context, int userId) {
+ return getStateForUser(context, userId).isInvalidBiometricState();
+ }
+
+ @Override
+ public void deleteStateForUser(int userId) {
+ synchronized (this) {
+ FaceUserState state = mUserStates.get(userId);
+ if (state != null) {
+ state.deleteBiometricFile();
+ mUserStates.delete(userId);
+ }
+ }
+ }
+
private FaceUserState getStateForUser(Context ctx, int userId) {
synchronized (this) {
FaceUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
index e75c6ab..964bf6c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalCleanupClient.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.face.IFace;
import android.hardware.face.Face;
import android.os.IBinder;
@@ -77,4 +78,9 @@
FaceUtils.getInstance(getSensorId()).addBiometricForUser(
getContext(), getTargetUserId(), (Face) identifier);
}
+
+ @Override
+ protected int getModality() {
+ return BiometricsProtoEnums.MODALITY_FACE;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
index 0062d31..b8c06c7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintUtils.java
@@ -140,6 +140,22 @@
return getStateForUser(context, userId).isInvalidationInProgress();
}
+ @Override
+ public boolean hasValidBiometricUserState(Context context, int userId) {
+ return getStateForUser(context, userId).isInvalidBiometricState();
+ }
+
+ @Override
+ public void deleteStateForUser(int userId) {
+ synchronized (this) {
+ FingerprintUserState state = mUserStates.get(userId);
+ if (state != null) {
+ state.deleteBiometricFile();
+ mUserStates.delete(userId);
+ }
+ }
+ }
+
private FingerprintUserState getStateForUser(Context ctx, int userId) {
synchronized (this) {
FingerprintUserState state = mUserStates.get(userId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
index 5edc2ca..1fc5179 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClient.java
@@ -22,9 +22,11 @@
import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.fingerprint.Fingerprint;
import android.os.IBinder;
+import android.util.Slog;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.BiometricNotificationUtils;
import com.android.server.biometrics.sensors.BiometricUtils;
import com.android.server.biometrics.sensors.InternalCleanupClient;
import com.android.server.biometrics.sensors.InternalEnumerateClient;
@@ -42,6 +44,8 @@
public class FingerprintInternalCleanupClient
extends InternalCleanupClient<Fingerprint, AidlSession> {
+ private static final String TAG = "FingerprintInternalCleanupClient";
+
public FingerprintInternalCleanupClient(@NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
int userId, @NonNull String owner, int sensorId,
@@ -80,4 +84,16 @@
FingerprintUtils.getInstance(getSensorId()).addBiometricForUser(
getContext(), getTargetUserId(), (Fingerprint) identifier);
}
+
+ @Override
+ public void handleInvalidBiometricState() {
+ Slog.d(TAG, "Invalid fingerprint user state: delete the state.");
+ mBiometricUtils.deleteStateForUser(getTargetUserId());
+ BiometricNotificationUtils.showFingerprintLoeNotification(getContext());
+ }
+
+ @Override
+ protected int getModality() {
+ return BiometricsProtoEnums.MODALITY_FINGERPRINT;
+ }
}
diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
index 7f7ae10..58e3452 100644
--- a/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
+++ b/services/core/java/com/android/server/inputmethod/IInputMethodManagerImpl.java
@@ -71,7 +71,20 @@
@Retention(SOURCE)
@Target({METHOD})
@interface PermissionVerified {
+ /**
+ * The name of the permission that is verified, if precisely one permission is required.
+ * If more than one permission is required, specify either {@link #allOf()} instead.
+ *
+ * <p>If specified, {@link #allOf()} must both be {@code null}.</p>
+ */
String value() default "";
+
+ /**
+ * Specifies a list of permission names that are all required.
+ *
+ * <p>If specified, {@link #value()} must both be {@code null}.</p>
+ */
+ String[] allOf() default {};
}
@BinderThread
@@ -132,13 +145,17 @@
void showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode);
- @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
@PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
boolean isInputMethodPickerShownForTest();
- @PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
void onImeSwitchButtonClickFromSystem(int displayId);
InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId);
@@ -153,7 +170,9 @@
void reportPerceptibleAsync(IBinder windowToken, boolean perceptible);
- @PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
void removeImeSurface(int displayId);
void removeImeSurfaceFromWindowAsync(IBinder windowToken);
@@ -330,13 +349,14 @@
mCallback.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
super.showInputMethodPickerFromSystem_enforcePermission();
mCallback.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
-
}
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@@ -347,7 +367,9 @@
return mCallback.isInputMethodPickerShownForTest();
}
- @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @EnforcePermission(allOf = {
+ Manifest.permission.WRITE_SECURE_SETTINGS,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
super.onImeSwitchButtonClickFromSystem_enforcePermission();
@@ -382,7 +404,9 @@
mCallback.reportPerceptibleAsync(windowToken, perceptible);
}
- @EnforcePermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @EnforcePermission(allOf = {
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW,
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL})
@Override
public void removeImeSurface(int displayId) {
super.removeImeSurface_enforcePermission();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8d652ef..5e7d391 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4002,7 +4002,9 @@
}
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
// Always call subtype picker, because subtype picker is a superset of input method
@@ -4096,7 +4098,9 @@
}
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
synchronized (ImfLock.class) {
@@ -4438,7 +4442,9 @@
});
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
@Override
public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index f603ff3..c940a9c 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -252,7 +252,9 @@
offload(() -> mInner.showInputMethodPickerFromClient(client, auxiliarySubtypeMode));
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
mInner.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
@@ -264,7 +266,9 @@
return mInner.isInputMethodPickerShownForTest();
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.WRITE_SECURE_SETTINGS)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.WRITE_SECURE_SETTINGS})
@Override
public void onImeSwitchButtonClickFromSystem(int displayId) {
mInner.onImeSwitchButtonClickFromSystem(displayId);
@@ -298,7 +302,9 @@
mInner.reportPerceptibleAsync(windowToken, perceptible);
}
- @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+ @IInputMethodManagerImpl.PermissionVerified(allOf = {
+ Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW})
@Override
public void removeImeSurface(int displayId) {
mInner.removeImeSurface(displayId);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index c83b280..ed0dc3b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2819,7 +2819,21 @@
mClearedTaskForReuse,
mClearedTaskFragmentForPip,
mClearedForReorderActivityToFront,
- calculateMinDimension());
+ calculateMinDimension(),
+ isTopNonFinishingChild());
+ }
+
+ private boolean isTopNonFinishingChild() {
+ final WindowContainer<?> parent = getParent();
+ if (parent == null) {
+ // Either the TaskFragment is not attached or is going to destroy. Return false.
+ return false;
+ }
+ final ActivityRecord topNonFishingActivity = parent.getActivity(ar -> !ar.finishing);
+ // If the parent's top non-finishing activity is this TaskFragment's, it means
+ // this TaskFragment is the top non-finishing container of its parent.
+ return topNonFishingActivity != null && topNonFishingActivity
+ .equals(getTopNonFinishingActivity());
}
/**
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 358adc3..af3ed28 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2185,8 +2185,7 @@
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
if (wallpaper != null) {
- if (!wallpaper.isVisible() && (wallpaper.isVisibleRequested()
- || (Flags.ensureWallpaperInTransitions() && showWallpaper))) {
+ if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
wallpaper.commitVisibility(showWallpaper);
} else if (Flags.ensureWallpaperInTransitions() && wallpaper.isVisible()
&& !showWallpaper && !wallpaper.getDisplayContent().isKeyguardLocked()
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4db62478..9884df9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -118,12 +118,12 @@
import static com.android.server.policy.PhoneWindowManager.TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
-import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
-import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL;
+import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.SensitiveContentPackages.PackageInfo;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
@@ -357,7 +357,6 @@
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -9390,11 +9389,6 @@
return focusedActivity;
}
- if (!Flags.embeddedActivityBackNavFlag()) {
- // Return if flag is not enabled.
- return focusedActivity;
- }
-
if (!focusedActivity.isEmbedded()) {
// Return if the focused activity is not embedded.
return focusedActivity;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index 3789531..36a7b3d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -1296,6 +1296,11 @@
mFingerprints.add((Fingerprint) identifier);
}
+ @Override
+ protected int getModality() {
+ return 0;
+ }
+
public List<Fingerprint> getFingerprints() {
return mFingerprints;
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index c9482ce..a34e796 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -30,12 +31,16 @@
import android.hardware.fingerprint.Fingerprint;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableContext;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -69,6 +74,10 @@
public final TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Mock
ISession mSession;
@Mock
@@ -168,6 +177,21 @@
assertThat(mClient.getUnknownHALTemplates()).isEmpty();
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_NOTIFY_FINGERPRINT_LOE)
+ public void invalidBiometricUserState() throws Exception {
+ mClient = createClient();
+
+ final List<Fingerprint> list = new ArrayList<>();
+ doReturn(true).when(mFingerprintUtils)
+ .hasValidBiometricUserState(mContext, 2);
+ doReturn(list).when(mFingerprintUtils).getBiometricsForUser(mContext, 2);
+
+ mClient.start(mCallback);
+ mClient.onEnumerationResult(null, 0);
+ verify(mFingerprintUtils).deleteStateForUser(2);
+ }
+
protected FingerprintInternalCleanupClient createClient() {
final Map<Integer, Long> authenticatorIds = new HashMap<>();
return new FingerprintInternalCleanupClient(mContext, () -> mAidlSession, 2 /* userId */,
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index afa22bc..a159ce3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -72,7 +72,6 @@
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
-import com.android.window.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -672,7 +671,6 @@
@Test
public void testBackOnMostRecentWindowInActivityEmbedding() {
- mSetFlagsRule.enableFlags(Flags.FLAG_EMBEDDED_ACTIVITY_BACK_NAV_FLAG);
final Task task = createTask(mDefaultDisplay);
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
final TaskFragment primaryTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3c247a0..6be1af2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -995,16 +995,14 @@
// The focus should change.
assertEquals(winLeftTop, mDisplayContent.mCurrentFocus);
- if (Flags.embeddedActivityBackNavFlag()) {
- // Move focus if the adjacent activity is more recently active.
- doReturn(1L).when(appLeftTop).getLastWindowCreateTime();
- doReturn(2L).when(appRightTop).getLastWindowCreateTime();
- assertTrue(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
+ // Move focus if the adjacent activity is more recently active.
+ doReturn(1L).when(appLeftTop).getLastWindowCreateTime();
+ doReturn(2L).when(appRightTop).getLastWindowCreateTime();
+ assertTrue(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
- // Do not move the focus if the adjacent activity is less recently active.
- doReturn(3L).when(appLeftTop).getLastWindowCreateTime();
- assertFalse(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
- }
+ // Do not move the focus if the adjacent activity is less recently active.
+ doReturn(3L).when(appLeftTop).getLastWindowCreateTime();
+ assertFalse(mWm.moveFocusToAdjacentEmbeddedWindow(winLeftTop));
}
@Test