Merge "Add a direct call to `unminimizedTask` from `addOrMoveFreeformTaskToTop` instead of each usage calling this method separately." into main
diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java
index 32d1964..ca6d86a 100644
--- a/core/java/android/content/IntentSender.java
+++ b/core/java/android/content/IntentSender.java
@@ -16,6 +16,8 @@
package android.content;
+import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM;
+
import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -23,6 +25,9 @@
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.IApplicationThread;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Handler;
@@ -65,6 +70,11 @@
* {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}.
*/
public class IntentSender implements Parcelable {
+ /** If enabled consider the deprecated @hide method as removed. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = VANILLA_ICE_CREAM)
+ private static final long REMOVE_HIDDEN_SEND_INTENT_METHOD = 356174596;
+
private static final Bundle SEND_INTENT_DEFAULT_OPTIONS =
ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle();
@@ -220,6 +230,44 @@
* original Intent. Use {@code null} to not modify the original Intent.
* @param onFinished The object to call back on when the send has
* completed, or {@code null} for no callback.
+ * @param handler Handler identifying the thread on which the callback
+ * should happen. If {@code null}, the callback will happen from the thread
+ * pool of the process.
+ * @param options Additional options the caller would like to provide to modify the sending
+ * behavior. Typically built from using {@link ActivityOptions} to apply to an activity start.
+ *
+ * @throws SendIntentException Throws CanceledIntentException if the IntentSender
+ * is no longer allowing more intents to be sent through it.
+ *
+ * @deprecated use {@link #sendIntent(Context, int, Intent, String, Bundle, Executor,
+ * OnFinished)}
+ *
+ * @hide
+ */
+ @Deprecated public void sendIntent(Context context, int code, Intent intent,
+ OnFinished onFinished, Handler handler, String requiredPermission,
+ @Nullable Bundle options)
+ throws SendIntentException {
+ if (CompatChanges.isChangeEnabled(REMOVE_HIDDEN_SEND_INTENT_METHOD)) {
+ throw new NoSuchMethodError("This overload of sendIntent was removed.");
+ }
+ sendIntent(context, code, intent, requiredPermission, options,
+ handler == null ? null : handler::post, onFinished);
+ }
+
+ /**
+ * Perform the operation associated with this IntentSender, allowing the
+ * caller to specify information about the Intent to use and be notified
+ * when the send has completed.
+ *
+ * @param context The Context of the caller. This may be {@code null} if
+ * <var>intent</var> is also {@code null}.
+ * @param code Result code to supply back to the IntentSender's target.
+ * @param intent Additional Intent data. See {@link Intent#fillIn
+ * Intent.fillIn()} for information on how this is applied to the
+ * original Intent. Use {@code null} to not modify the original Intent.
+ * @param onFinished The object to call back on when the send has
+ * completed, or {@code null} for no callback.
* @param executor Executor identifying the thread on which the callback
* should happen. If {@code null}, the callback will happen from the thread
* pool of the process.
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/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5d3bc89..d184f64 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -101,6 +101,8 @@
bool Properties::clipSurfaceViews = false;
bool Properties::hdr10bitPlus = false;
+int Properties::timeoutMultiplier = 1;
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -174,6 +176,8 @@
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
hdr10bitPlus = hwui_flags::hdr_10bit_plus();
+ timeoutMultiplier = android::base::GetIntProperty("ro.hw_timeout_multiplier", 1);
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d3176f6..e264642 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -343,6 +343,8 @@
static bool clipSurfaceViews;
static bool hdr10bitPlus;
+ static int timeoutMultiplier;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index afe4c38..2f15722 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -91,8 +91,10 @@
{
ATRACE_NAME("sync_wait");
- if (sourceFence != -1 && sync_wait(sourceFence.get(), 500 /* ms */) != NO_ERROR) {
- ALOGE("Timeout (500ms) exceeded waiting for buffer fence, abandoning readback attempt");
+ int syncWaitTimeoutMs = 500 * Properties::timeoutMultiplier;
+ if (sourceFence != -1 && sync_wait(sourceFence.get(), syncWaitTimeoutMs) != NO_ERROR) {
+ ALOGE("Timeout (%dms) exceeded waiting for buffer fence, abandoning readback attempt",
+ syncWaitTimeoutMs);
return request->onCopyFinished(CopyResult::Timeout);
}
}
@@ -109,9 +111,8 @@
sk_sp<SkColorSpace> colorSpace =
DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
- sk_sp<SkImage> image =
- SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType,
- colorSpace);
+ sk_sp<SkImage> image = SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(),
+ kPremul_SkAlphaType, colorSpace);
if (!image.get()) {
return request->onCopyFinished(CopyResult::UnknownError);
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/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
index d69c87b..2dc2650 100644
--- a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt
@@ -21,6 +21,7 @@
import android.content.Intent
import android.os.OutcomeReceiver
import android.telephony.satellite.SatelliteManager
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.Log
import android.view.WindowManager
import androidx.lifecycle.LifecycleOwner
@@ -31,12 +32,19 @@
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job
import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeoutException
import kotlin.coroutines.resume
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOn
/** A util for Satellite dialog */
object SatelliteDialogUtils {
@@ -70,7 +78,7 @@
coroutineScope.launch {
var isSatelliteModeOn = false
try {
- isSatelliteModeOn = requestIsEnabled(context)
+ isSatelliteModeOn = requestIsSessionStarted(context)
} catch (e: InterruptedException) {
Log.w(TAG, "Error to get satellite status : $e")
} catch (e: ExecutionException) {
@@ -134,6 +142,70 @@
}
}
+ private suspend fun requestIsSessionStarted(
+ context: Context
+ ): Boolean = withContext(Default) {
+ getIsSessionStartedFlow(context).conflate().first()
+ }
+
+ /**
+ * Provides a Flow that emits the session state of the satellite modem. Updates are triggered
+ * when the modem state changes.
+ *
+ * @param defaultDispatcher The CoroutineDispatcher to use (Defaults to `Dispatchers.Default`).
+ * @return A Flow emitting `true` when the session is started and `false` otherwise.
+ */
+ private fun getIsSessionStartedFlow(
+ context: Context
+ ): Flow<Boolean> {
+ val satelliteManager: SatelliteManager? =
+ context.getSystemService(SatelliteManager::class.java)
+ if (satelliteManager == null) {
+ Log.w(TAG, "SatelliteManager is null")
+ return flowOf(false)
+ }
+
+ return callbackFlow {
+ val callback = SatelliteModemStateCallback { state ->
+ val isSessionStarted = isSatelliteSessionStarted(state)
+ Log.i(TAG, "Satellite modem state changed: state=$state"
+ + ", isSessionStarted=$isSessionStarted")
+ trySend(isSessionStarted)
+ }
+
+ val registerResult = satelliteManager.registerForModemStateChanged(
+ Default.asExecutor(),
+ callback
+ )
+
+ if (registerResult != SatelliteManager.SATELLITE_RESULT_SUCCESS) {
+ // If the registration failed (e.g., device doesn't support satellite),
+ // SatelliteManager will not emit the current state by callback.
+ // We send `false` value by ourself to make sure the flow has initial value.
+ Log.w(TAG, "Failed to register for satellite modem state change: $registerResult")
+ trySend(false)
+ }
+
+ awaitClose { satelliteManager.unregisterForModemStateChanged(callback) }
+ }.flowOn(Default)
+ }
+
+
+ /**
+ * Check if the modem is in a satellite session.
+ *
+ * @param state The SatelliteModemState provided by the SatelliteManager.
+ * @return `true` if the modem is in a satellite session, `false` otherwise.
+ */
+ fun isSatelliteSessionStarted(@SatelliteManager.SatelliteModemState state: Int): Boolean {
+ return when (state) {
+ SatelliteManager.SATELLITE_MODEM_STATE_OFF,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE,
+ SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN -> false
+ else -> true
+ }
+ }
+
const val TAG = "SatelliteDialogUtils"
const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
index aeda1ed6..31d7130 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt
@@ -17,11 +17,12 @@
package com.android.settingslib.satellite
import android.content.Context
-import android.content.Intent
-import android.os.OutcomeReceiver
import android.platform.test.annotations.RequiresFlagsEnabled
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SatelliteException
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_ENABLING_SATELLITE
+import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF
+import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_MODEM_ERROR
+import android.telephony.satellite.SatelliteModemStateCallback
import android.util.AndroidRuntimeException
import androidx.test.core.app.ApplicationProvider
import com.android.internal.telephony.flags.Flags
@@ -67,26 +68,21 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(
1
)
- receiver.onResult(true)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_ENABLING_SATELLITE)
null
}
try {
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertTrue(it)
- })
+ assertTrue(it)
+ })
} catch (e: AndroidRuntimeException) {
// Catch exception of starting activity .
}
@@ -95,68 +91,49 @@
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
.thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
+ val callback = invocation
+ .getArgument<SatelliteModemStateCallback>(
1
)
- receiver.onResult(false)
+ callback.onSatelliteModemStateChanged(SATELLITE_MODEM_STATE_OFF)
null
}
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking {
- `when`(context.getSystemService(SatelliteManager::class.java))
- .thenReturn(null)
+ `when`(context.getSystemService(SatelliteManager::class.java)).thenReturn(null)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
@Test
@RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking {
- `when`(
- satelliteManager.requestIsEnabled(
- any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>()
- )
- )
- .thenAnswer { invocation ->
- val receiver = invocation
- .getArgument<
- OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>(
- 1
- )
- receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR))
- null
- }
-
+ `when`(satelliteManager.registerForModemStateChanged(any(), any()))
+ .thenReturn(SATELLITE_RESULT_MODEM_ERROR)
SatelliteDialogUtils.mayStartSatelliteWarningDialog(
- context, coroutineScope, TYPE_IS_WIFI, allowClick = {
- assertFalse(it)
- })
+ context, coroutineScope, TYPE_IS_WIFI, allowClick = {
+ assertFalse(it)
+ })
- verify(context, Times(0)).startActivity(any<Intent>())
+ verify(context, Times(0)).startActivity(any())
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 7f5839d4..0da252d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -2086,6 +2086,7 @@
private void handleUserUnlocked(int userId) {
Assert.isMainThread();
+ mLogger.logUserUnlocked(userId);
mUserIsUnlocked.put(userId, true);
mNeedsSlowUnlockTransition = resolveNeedsSlowUnlockTransition();
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -2098,12 +2099,15 @@
private void handleUserStopped(int userId) {
Assert.isMainThread();
- mUserIsUnlocked.put(userId, mUserManager.isUserUnlocked(userId));
+ boolean isUnlocked = mUserManager.isUserUnlocked(userId);
+ mLogger.logUserStopped(userId, isUnlocked);
+ mUserIsUnlocked.put(userId, isUnlocked);
}
@VisibleForTesting
void handleUserRemoved(int userId) {
Assert.isMainThread();
+ mLogger.logUserRemoved(userId);
mUserIsUnlocked.delete(userId);
mUserTrustIsUsuallyManaged.delete(userId);
}
@@ -2444,7 +2448,9 @@
mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
int user = mSelectedUserInteractor.getSelectedUserId(true);
- mUserIsUnlocked.put(user, mUserManager.isUserUnlocked(user));
+ boolean isUserUnlocked = mUserManager.isUserUnlocked(user);
+ mLogger.logUserUnlockedInitialState(user, isUserUnlocked);
+ mUserIsUnlocked.put(user, isUserUnlocked);
mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
updateSecondaryLockscreenRequirement(user);
List<UserInfo> allUsers = mUserManager.getUsers();
@@ -4059,6 +4065,9 @@
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println("ActiveUnlockRunning="
+ mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
+ pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId));
+ pw.println("actualUserUnlocked[userid=" + userId + "]="
+ + mUserManager.isUserUnlocked(userId));
new DumpsysTableLogger(
"KeyguardActiveUnlockTriggers",
KeyguardActiveUnlockModel.TABLE_HEADERS,
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 1f4e732..0b58f06 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -391,6 +391,7 @@
{ "handleTimeFormatUpdate timeFormat=$str1" }
)
}
+
fun logUdfpsPointerDown(sensorId: Int) {
logBuffer.log(TAG, DEBUG, { int1 = sensorId }, { "onUdfpsPointerDown, sensorId: $int1" })
}
@@ -639,12 +640,45 @@
{ "fingerprint acquire message: $int1" }
)
}
+
fun logForceIsDismissibleKeyguard(keepUnlocked: Boolean) {
logBuffer.log(
- TAG,
- DEBUG,
- { bool1 = keepUnlocked },
- { "keepUnlockedOnFold changed to: $bool1" }
+ TAG,
+ DEBUG,
+ { bool1 = keepUnlocked },
+ { "keepUnlockedOnFold changed to: $bool1" }
+ )
+ }
+
+ fun logUserUnlocked(userId: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" })
+ }
+
+ fun logUserStopped(userId: Int, isUnlocked: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isUnlocked
+ },
+ { "userStopped userId: $int1 isUnlocked: $bool1" }
+ )
+ }
+
+ fun logUserRemoved(userId: Int) {
+ logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userRemoved userId: $int1" })
+ }
+
+ fun logUserUnlockedInitialState(userId: Int, isUnlocked: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = isUnlocked
+ },
+ { "userUnlockedInitialState userId: $int1 isUnlocked: $bool1" }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index f041f4d..083f1db 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -52,6 +52,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.HapClientProfile;
@@ -104,6 +105,7 @@
private final AudioManager mAudioManager;
private final LocalBluetoothProfileManager mProfileManager;
private final HapClientProfile mHapClientProfile;
+ private final UiEventLogger mUiEventLogger;
private HearingDevicesListAdapter mDeviceListAdapter;
private HearingDevicesPresetsController mPresetsController;
private Context mApplicationContext;
@@ -163,7 +165,8 @@
DialogTransitionAnimator dialogTransitionAnimator,
@Nullable LocalBluetoothManager localBluetoothManager,
@Main Handler handler,
- AudioManager audioManager) {
+ AudioManager audioManager,
+ UiEventLogger uiEventLogger) {
mApplicationContext = applicationContext;
mShowPairNewDevice = showPairNewDevice;
mSystemUIDialogFactory = systemUIDialogFactory;
@@ -174,6 +177,7 @@
mAudioManager = audioManager;
mProfileManager = localBluetoothManager.getProfileManager();
mHapClientProfile = mProfileManager.getHapClientProfile();
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -187,6 +191,7 @@
@Override
public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
dismissDialogIfExists();
Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
Bundle bundle = new Bundle();
@@ -198,13 +203,21 @@
}
@Override
- public void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
+ public void onDeviceItemClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice();
switch (deviceItem.getType()) {
- case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE ->
- cachedBluetoothDevice.disconnect();
- case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> cachedBluetoothDevice.setActive();
- case SAVED_BLUETOOTH_DEVICE -> cachedBluetoothDevice.connect();
+ case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
+ cachedBluetoothDevice.disconnect();
+ }
+ case AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_SET_ACTIVE);
+ cachedBluetoothDevice.setActive();
+ }
+ case SAVED_BLUETOOTH_DEVICE -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_CONNECT);
+ cachedBluetoothDevice.connect();
+ }
}
}
@@ -262,6 +275,7 @@
if (mLocalBluetoothManager == null) {
return;
}
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_DIALOG_SHOW);
mPairButton = dialog.requireViewById(R.id.pair_new_device_button);
mDeviceList = dialog.requireViewById(R.id.device_list);
mPresetSpinner = dialog.requireViewById(R.id.preset_spinner);
@@ -341,12 +355,17 @@
}
});
+ // Refresh the spinner and setSelection(index, false) before setOnItemSelectedListener() to
+ // avoid extra onItemSelected() get called when first register the listener.
+ final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
+ final int activePresetIndex = mPresetsController.getActivePresetIndex();
+ refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PRESET_SELECT);
mPresetsController.selectPreset(
mPresetsController.getAllPresetInfo().get(position).getIndex());
- mPresetSpinner.setSelection(position);
}
@Override
@@ -354,9 +373,6 @@
// Do nothing
}
});
- final List<BluetoothHapPresetInfo> presetInfos = mPresetsController.getAllPresetInfo();
- final int activePresetIndex = mPresetsController.getActivePresetIndex();
- refreshPresetInfoAdapter(presetInfos, activePresetIndex);
mPresetSpinner.setVisibility(
(activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
&& !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
@@ -365,6 +381,7 @@
private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
if (visibility == VISIBLE) {
mPairButton.setOnClickListener(v -> {
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
dismissDialogIfExists();
final Intent intent = new Intent(Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -413,7 +430,7 @@
final int size = mPresetInfoAdapter.getCount();
for (int position = 0; position < size; position++) {
if (presetInfos.get(position).getIndex() == activePresetIndex) {
- mPresetSpinner.setSelection(position);
+ mPresetSpinner.setSelection(position, /* animate= */ false);
}
}
}
@@ -464,12 +481,15 @@
text.setText(item.getToolName());
Intent intent = item.getToolIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- view.setOnClickListener(
- v -> {
- dismissDialogIfExists();
- mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
- mDialogTransitionAnimator.createActivityTransitionController(view));
- });
+ view.setOnClickListener(v -> {
+ final String name = intent.getComponent() != null
+ ? intent.getComponent().flattenToString()
+ : intent.getPackage() + "/" + intent.getAction();
+ mUiEventLogger.log(HearingDevicesUiEvent.HEARING_DEVICES_RELATED_TOOL_CLICK, 0, name);
+ dismissDialogIfExists();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0,
+ mDialogTransitionAnimator.createActivityTransitionController(view));
+ });
return view;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
index 737805b..b46b8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesListAdapter.java
@@ -96,7 +96,7 @@
* @param deviceItem bluetooth device item
* @param view the view that was clicked
*/
- void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view);
+ void onDeviceItemClicked(@NonNull DeviceItem deviceItem, @NonNull View view);
}
private static class DeviceItemViewHolder extends RecyclerView.ViewHolder {
@@ -119,7 +119,7 @@
public void bindView(DeviceItem item, HearingDeviceItemCallback callback) {
mContainer.setEnabled(item.isEnabled());
- mContainer.setOnClickListener(view -> callback.onDeviceItemOnClicked(item, view));
+ mContainer.setOnClickListener(view -> callback.onDeviceItemClicked(item, view));
Integer backgroundResId = item.getBackground();
if (backgroundResId != null) {
mContainer.setBackground(mContext.getDrawable(item.getBackground()));
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
new file mode 100644
index 0000000..3fbe56e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesUiEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.hearingaid;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum HearingDevicesUiEvent implements UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "Hearing devices dialog is shown")
+ HEARING_DEVICES_DIALOG_SHOW(1848),
+ @UiEvent(doc = "Pair new device")
+ HEARING_DEVICES_PAIR(1849),
+ @UiEvent(doc = "Connect to the device")
+ HEARING_DEVICES_CONNECT(1850),
+ @UiEvent(doc = "Disconnect from the device")
+ HEARING_DEVICES_DISCONNECT(1851),
+ @UiEvent(doc = "Set the device as active device")
+ HEARING_DEVICES_SET_ACTIVE(1852),
+ @UiEvent(doc = "Click on the device gear to enter device detail page")
+ HEARING_DEVICES_GEAR_CLICK(1853),
+ @UiEvent(doc = "Select a preset from preset spinner")
+ HEARING_DEVICES_PRESET_SELECT(1854),
+ @UiEvent(doc = "Click on related tool")
+ HEARING_DEVICES_RELATED_TOOL_CLICK(1856);
+
+ private final int mId;
+
+ HearingDevicesUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 5ea5c21..d3b7d22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -52,6 +52,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.BluetoothEventManager;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -123,6 +124,8 @@
@Mock
private AudioManager mAudioManager;
@Mock
+ private UiEventLogger mUiEventLogger;
+ @Mock
private CachedBluetoothDevice mCachedDevice;
@Mock
private BluetoothDevice mDevice;
@@ -179,6 +182,7 @@
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
Settings.ACTION_HEARING_DEVICE_PAIRING_SETTINGS);
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_PAIR);
}
@Test
@@ -192,7 +196,7 @@
anyInt(), any());
assertThat(intentCaptor.getValue().getAction()).isEqualTo(
HearingDevicesDialogDelegate.ACTION_BLUETOOTH_DEVICE_DETAILS);
-
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_GEAR_CLICK);
}
@Test
@@ -200,9 +204,10 @@
setUpDeviceListDialog();
when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
- mDialogDelegate.onDeviceItemOnClicked(mHearingDeviceItem, new View(mContext));
+ mDialogDelegate.onDeviceItemClicked(mHearingDeviceItem, new View(mContext));
verify(mCachedDevice).disconnect();
+ verify(mUiEventLogger).log(HearingDevicesUiEvent.HEARING_DEVICES_DISCONNECT);
}
@Test
@@ -304,7 +309,8 @@
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
- mAudioManager
+ mAudioManager,
+ mUiEventLogger
);
mDialog = mDialogDelegate.createDialog();
@@ -326,7 +332,8 @@
mDialogTransitionAnimator,
mLocalBluetoothManager,
new Handler(mTestableLooper.getLooper()),
- mAudioManager
+ mAudioManager,
+ mUiEventLogger
);
mDialog = mDialogDelegate.createDialog();
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 2a24e34..0ee5896 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -1659,11 +1659,12 @@
}
float dX = event.getX() - firstPointerDownLocation.x;
float dY = event.getY() - firstPointerDownLocation.y;
- if (isAtLeftEdge() && dX > 0) {
+ if (isAtLeftEdge() && isScrollingLeft(dX, dY)) {
return OVERSCROLL_LEFT_EDGE;
- } else if (isAtRightEdge() && dX < 0) {
+ } else if (isAtRightEdge() && isScrollingRight(dX, dY)) {
return OVERSCROLL_RIGHT_EDGE;
- } else if ((isAtTopEdge() && dY > 0) || (isAtBottomEdge() && dY < 0)) {
+ } else if ((isAtTopEdge() && isScrollingUp(dX, dY))
+ || (isAtBottomEdge() && isScrollingDown(dX, dY))) {
return OVERSCROLL_VERTICAL_EDGE;
}
return OVERSCROLL_NONE;
@@ -1673,18 +1674,34 @@
return mFullScreenMagnificationController.isAtLeftEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingLeft(float dX, float dY) {
+ return Math.abs(dX) > Math.abs(dY) && dX > 0;
+ }
+
private boolean isAtRightEdge() {
return mFullScreenMagnificationController.isAtRightEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingRight(float dX, float dY) {
+ return Math.abs(dX) > Math.abs(dY) && dX < 0;
+ }
+
private boolean isAtTopEdge() {
return mFullScreenMagnificationController.isAtTopEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingUp(float dX, float dY) {
+ return Math.abs(dX) < Math.abs(dY) && dY > 0;
+ }
+
private boolean isAtBottomEdge() {
return mFullScreenMagnificationController.isAtBottomEdge(mDisplayId, mOverscrollEdgeSlop);
}
+ private static boolean isScrollingDown(float dX, float dY) {
+ return Math.abs(dX) < Math.abs(dY) && dY < 0;
+ }
+
private boolean pointerValid(PointF pointerDownLocation) {
return !(Float.isNaN(pointerDownLocation.x) && Float.isNaN(pointerDownLocation.y));
}
diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java
index f336120..3b0147c 100644
--- a/services/core/java/com/android/server/am/PendingIntentController.java
+++ b/services/core/java/com/android/server/am/PendingIntentController.java
@@ -149,21 +149,6 @@
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
}
- if (opts != null && opts.isPendingIntentBackgroundActivityLaunchAllowedByPermission()) {
- Slog.wtf(TAG,
- "Resetting option pendingIntentBackgroundActivityLaunchAllowedByPermission"
- + " which is set by the pending intent creator ("
- + packageName
- + ") because this option is meant for the pending intent sender");
- if (CompatChanges.isChangeEnabled(PendingIntent.PENDING_INTENT_OPTIONS_CHECK,
- callingUid)) {
- throw new IllegalArgumentException(
- "pendingIntentBackgroundActivityLaunchAllowedByPermission "
- + "can not be set by creator of a PendingIntent");
- }
- opts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(false);
- }
-
final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0;
final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0;
final boolean updateCurrent = (flags & PendingIntent.FLAG_UPDATE_CURRENT) != 0;
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/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
index 3c3bdd5..7746276 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java
@@ -769,6 +769,7 @@
* @return true if the UI Broadcast type is valid
*/
private static boolean isValidUiBroadcastType(int value) {
+ value = value & 0xFF;
return ((value == 0x00)
|| (value == 0x01)
|| (value == 0x10)
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/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 0b3f3f0..42a99de 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -121,9 +121,9 @@
@GuardedBy("ImfLock.class")
private boolean mRequestedImeScreenshot;
- /** The window token of the current visible IME layering target overlay. */
+ /** Whether there is a visible IME layering target overlay. */
@GuardedBy("ImfLock.class")
- private IBinder mCurVisibleImeLayeringOverlay;
+ private boolean mHasVisibleImeLayeringOverlay;
/** The window token of the current visible IME input target. */
@GuardedBy("ImfLock.class")
@@ -218,33 +218,36 @@
mPolicy = imePolicy;
mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() {
@Override
- public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken,
+ public void onImeTargetOverlayVisibilityChanged(@NonNull IBinder overlayWindowToken,
@WindowManager.LayoutParams.WindowType int windowType, boolean visible,
boolean removed) {
// Ignoring the starting window since it's ok to cover the IME target
// window in temporary without affecting the IME visibility.
- final var overlay = (visible && !removed && windowType != TYPE_APPLICATION_STARTING)
- ? overlayWindowToken : null;
+ final boolean hasOverlay = visible && !removed
+ && windowType != TYPE_APPLICATION_STARTING;
synchronized (ImfLock.class) {
- mCurVisibleImeLayeringOverlay = overlay;
+ mHasVisibleImeLayeringOverlay = hasOverlay;
}
}
@Override
public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget,
boolean visibleRequested, boolean removed) {
+ final boolean visibleAndNotRemoved = visibleRequested && !removed;
synchronized (ImfLock.class) {
- if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested
- || removed)
- && mCurVisibleImeLayeringOverlay != null) {
+ if (visibleAndNotRemoved) {
+ mCurVisibleImeInputTarget = imeInputTarget;
+ return;
+ }
+ if (mHasVisibleImeLayeringOverlay
+ && mCurVisibleImeInputTarget == imeInputTarget) {
final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
mService.onApplyImeVisibilityFromComputerLocked(imeInputTarget, statsToken,
new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason));
}
- mCurVisibleImeInputTarget =
- (visibleRequested && !removed) ? imeInputTarget : null;
+ mCurVisibleImeInputTarget = null;
}
}
});
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index d9bc467..5e7d391 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1126,6 +1126,21 @@
}
});
}
+
+ @Override
+ public void onUserStopped(@NonNull TargetUser user) {
+ final int userId = user.getUserIdentifier();
+ // Called on ActivityManager thread.
+ SecureSettingsWrapper.onUserStopped(userId);
+ mService.mIoHandler.post(() -> {
+ final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId);
+ final var settings = InputMethodManagerService.queryInputMethodServicesInternal(
+ mService.mContext, userId, additionalSubtypeMap,
+ DirectBootAwareness.AUTO).getMethodMap();
+ InputMethodSettingsRepository.put(userId,
+ InputMethodSettings.create(settings, userId));
+ });
+ }
}
@GuardedBy("ImfLock.class")
@@ -3987,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
@@ -4081,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) {
@@ -4423,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/SecureSettingsWrapper.java b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
index c208a5b..476888e 100644
--- a/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
+++ b/services/core/java/com/android/server/inputmethod/SecureSettingsWrapper.java
@@ -377,6 +377,23 @@
}
/**
+ * Called when a user is stopped, which changes the user storage to the locked state again.
+ *
+ * @param userId the ID of the user whose storage is being locked again.
+ */
+ @AnyThread
+ static void onUserStopped(@UserIdInt int userId) {
+ final LockedUserImpl lockedUserImpl = new LockedUserImpl(userId, sContentResolver);
+ synchronized (sMutationLock) {
+ final ReaderWriter current = sUserMap.get(userId);
+ if (current == null || current instanceof LockedUserImpl) {
+ return;
+ }
+ sUserMap = sUserMap.cloneWithPutOrSelf(userId, lockedUserImpl);
+ }
+ }
+
+ /**
* Put the given string {@code value} to {@code key}.
*
* @param key a secure settings key.
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/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4425079..a0d5ea8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -501,9 +501,9 @@
mPm.setUpCustomResolverActivity(pkg, pkgSetting);
}
- // When upgrading a package, pkgSetting is copied from oldPkgSetting. Clear the app
- // metadata file path for the new package.
- if (oldPkgSetting != null) {
+ // When upgrading a package, clear the app metadata file path for the new package.
+ if (oldPkgSetting != null
+ && oldPkgSetting.getLastUpdateTime() < pkgSetting.getLastUpdateTime()) {
pkgSetting.setAppMetadataFilePath(null);
pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN);
}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 39954b8..5f41090 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -59,58 +59,61 @@
*/
public void exportAggregatedPowerStats(BatteryUsageStats.Builder batteryUsageStatsBuilder,
long monotonicStartTime, long monotonicEndTime) {
- boolean hasStoredSpans = false;
- long maxEndTime = monotonicStartTime;
- List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
- for (int i = spans.size() - 1; i >= 0; i--) {
- PowerStatsSpan.Metadata metadata = spans.get(i);
- if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
- continue;
- }
-
- List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames();
- long spanMinTime = Long.MAX_VALUE;
- long spanMaxTime = Long.MIN_VALUE;
- for (int j = 0; j < timeFrames.size(); j++) {
- PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j);
- long startMonotonicTime = timeFrame.startMonotonicTime;
- long endMonotonicTime = startMonotonicTime + timeFrame.duration;
- if (startMonotonicTime < spanMinTime) {
- spanMinTime = startMonotonicTime;
+ synchronized (this) {
+ boolean hasStoredSpans = false;
+ long maxEndTime = monotonicStartTime;
+ List<PowerStatsSpan.Metadata> spans = mPowerStatsStore.getTableOfContents();
+ for (int i = spans.size() - 1; i >= 0; i--) {
+ PowerStatsSpan.Metadata metadata = spans.get(i);
+ if (!metadata.getSections().contains(AggregatedPowerStatsSection.TYPE)) {
+ continue;
}
- if (endMonotonicTime > spanMaxTime) {
- spanMaxTime = endMonotonicTime;
+
+ List<PowerStatsSpan.TimeFrame> timeFrames = metadata.getTimeFrames();
+ long spanMinTime = Long.MAX_VALUE;
+ long spanMaxTime = Long.MIN_VALUE;
+ for (int j = 0; j < timeFrames.size(); j++) {
+ PowerStatsSpan.TimeFrame timeFrame = timeFrames.get(j);
+ long startMonotonicTime = timeFrame.startMonotonicTime;
+ long endMonotonicTime = startMonotonicTime + timeFrame.duration;
+ if (startMonotonicTime < spanMinTime) {
+ spanMinTime = startMonotonicTime;
+ }
+ if (endMonotonicTime > spanMaxTime) {
+ spanMaxTime = endMonotonicTime;
+ }
+ }
+
+ if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) {
+ continue;
+ }
+
+ if (spanMaxTime > maxEndTime) {
+ maxEndTime = spanMaxTime;
+ }
+
+ PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
+ AggregatedPowerStatsSection.TYPE);
+ if (span == null) {
+ Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
+ continue;
+ }
+ List<PowerStatsSpan.Section> sections = span.getSections();
+ for (int k = 0; k < sections.size(); k++) {
+ hasStoredSpans = true;
+ PowerStatsSpan.Section section = sections.get(k);
+ populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
+ ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
}
}
- if (!(spanMinTime >= monotonicStartTime && spanMaxTime < monotonicEndTime)) {
- continue;
+ if (!hasStoredSpans
+ || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
+ mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
+ stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
}
-
- if (spanMaxTime > maxEndTime) {
- maxEndTime = spanMaxTime;
- }
-
- PowerStatsSpan span = mPowerStatsStore.loadPowerStatsSpan(metadata.getId(),
- AggregatedPowerStatsSection.TYPE);
- if (span == null) {
- Slog.e(TAG, "Could not read PowerStatsStore section " + metadata);
- continue;
- }
- List<PowerStatsSpan.Section> sections = span.getSections();
- for (int k = 0; k < sections.size(); k++) {
- hasStoredSpans = true;
- PowerStatsSpan.Section section = sections.get(k);
- populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder,
- ((AggregatedPowerStatsSection) section).getAggregatedPowerStats());
- }
+ mPowerStatsAggregator.reset();
}
-
- if (!hasStoredSpans || maxEndTime < monotonicEndTime - mBatterySessionTimeSpanSlackMillis) {
- mPowerStatsAggregator.aggregatePowerStats(maxEndTime, monotonicEndTime,
- stats -> populateBatteryUsageStatsBuilder(batteryUsageStatsBuilder, stats));
- }
- mPowerStatsAggregator.reset();
}
private void populateBatteryUsageStatsBuilder(
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 54024e9..02c8a49 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,9 +20,11 @@
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityOptions.BackgroundActivityStartMode;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -226,6 +228,21 @@
};
}
+ static String balStartModeToString(@BackgroundActivityStartMode int startMode) {
+ return switch (startMode) {
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED -> "MODE_BACKGROUND_ACTIVITY_START_ALLOWED";
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED ->
+ "MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED";
+ case MODE_BACKGROUND_ACTIVITY_START_COMPAT -> "MODE_BACKGROUND_ACTIVITY_START_COMPAT";
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED -> "MODE_BACKGROUND_ACTIVITY_START_DENIED";
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS ->
+ "MODE_BACKGROUND_ACTIVITY_START_ALWAYS";
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE ->
+ "MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE";
+ default -> "MODE_BACKGROUND_ACTIVITY_START_ALLOWED(" + startMode + ")";
+ };
+ }
+
@GuardedBy("mService.mGlobalLock")
private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
new HashMap<>();
@@ -464,10 +481,6 @@
this.mResultForRealCaller = resultForRealCaller;
}
- public boolean isPendingIntentBalAllowedByPermission() {
- return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
- }
-
public boolean callerExplicitOptInOrAutoOptIn() {
if (mAutoOptInCaller) {
return !callerExplicitOptOut();
@@ -528,6 +541,8 @@
sb.append("; balAllowedByPiCreatorWithHardening: ")
.append(mBalAllowedByPiCreatorWithHardening);
sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller);
+ sb.append("; callerStartMode: ").append(balStartModeToString(
+ mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
sb.append("; hasRealCaller: ").append(hasRealCaller());
sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -553,6 +568,8 @@
}
sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
+ sb.append("; realCallerStartMode: ").append(balStartModeToString(
+ mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()));
}
// features
sb.append("; balImproveRealCallerVisibilityCheck: ")
@@ -949,7 +966,8 @@
}
}
- if (state.isPendingIntentBalAllowedByPermission()
+ if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
&& hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
return new BalVerdict(BAL_ALLOW_PERMISSION,
/*background*/ false,
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
index 8bd8098..27e6e09 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettingsProvider.java
@@ -55,8 +55,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Implementation of {@link SettingsProvider} that reads the base settings provided in a display
@@ -152,19 +154,35 @@
/**
* Removes display override settings that are no longer associated with active displays.
- * This is necessary because displays can be dynamically added or removed during
- * the system's lifecycle (e.g., user switch, system server restart).
+ * <p>
+ * This cleanup process is essential due to the dynamic nature of displays, which can
+ * be added or removed during various system events such as user switching or
+ * system server restarts.
*
- * @param root The root window container used to obtain the currently active displays.
+ * @param wms the WindowManagerService instance for retrieving all possible {@link DisplayInfo}
+ * for the given logical display.
+ * @param root the root window container used to obtain the currently active displays.
*/
- void removeStaleDisplaySettings(@NonNull RootWindowContainer root) {
+ void removeStaleDisplaySettingsLocked(@NonNull WindowManagerService wms,
+ @NonNull RootWindowContainer root) {
if (!Flags.perUserDisplayWindowSettings()) {
return;
}
final Set<String> displayIdentifiers = new ArraySet<>();
+ final Consumer<DisplayInfo> addDisplayIdentifier =
+ displayInfo -> displayIdentifiers.add(mOverrideSettings.getIdentifier(displayInfo));
root.forAllDisplays(dc -> {
- final String identifier = mOverrideSettings.getIdentifier(dc.getDisplayInfo());
- displayIdentifiers.add(identifier);
+ // Begin with the current display's information. Note that the display layout of the
+ // current device state might not include this display (e.g., external or virtual
+ // displays), resulting in empty possible display info.
+ addDisplayIdentifier.accept(dc.getDisplayInfo());
+
+ // Then, add all possible display information for this display if available.
+ final List<DisplayInfo> displayInfos = wms.getPossibleDisplayInfoLocked(dc.mDisplayId);
+ final int size = displayInfos.size();
+ for (int i = 0; i < size; i++) {
+ addDisplayIdentifier.accept(displayInfos.get(i));
+ }
});
mOverrideSettings.removeStaleDisplaySettings(displayIdentifiers);
}
diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
index e3a2065..6e87977 100644
--- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
+++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java
@@ -57,7 +57,7 @@
* Returns, for the given displayId, a list of unique display infos. List contains each
* supported device state.
* <p>List contents are guaranteed to be unique, but returned as a list rather than a set to
- * minimize copies needed to make an iteraable data structure.
+ * minimize copies needed to make an iterable data structure.
*/
public List<DisplayInfo> getPossibleDisplayInfos(int displayId) {
// Update display infos before returning, since any cached values would have been removed
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..13453a6 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;
@@ -3750,7 +3749,7 @@
}
mCurrentUserId = newUserId;
mDisplayWindowSettingsProvider.setOverrideSettingsForUser(newUserId);
- mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
+ mDisplayWindowSettingsProvider.removeStaleDisplaySettingsLocked(this, mRoot);
mPolicy.setCurrentUserLw(newUserId);
mKeyguardDisableHandler.setCurrentUser(newUserId);
@@ -5491,7 +5490,7 @@
mRoot.forAllDisplays(DisplayContent::reconfigureDisplayLocked);
// Per-user display settings may leave outdated settings after user switches, especially
// during reboots starting with the default user without setCurrentUser called.
- mDisplayWindowSettingsProvider.removeStaleDisplaySettings(mRoot);
+ mDisplayWindowSettingsProvider.removeStaleDisplaySettingsLocked(this, mRoot);
}
}
@@ -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/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 8d0b279..fc28f9e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -266,8 +266,8 @@
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
- any(), any());
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(),
+ (Bundle) any(), any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
@@ -336,8 +336,8 @@
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any(),
- any(), any());
+ verify(mIntentSender).sendIntent(any(), anyInt(), intentCaptor.capture(), any(),
+ (Bundle) any(), any(), any());
Intent value = intentCaptor.getValue();
assertThat(value.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME)).isEqualTo(PACKAGE);
assertThat(value.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)).isEqualTo(
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index f428aae..60bcecc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -984,6 +984,53 @@
}
@Test
+ public void testSingleFingerOverscrollAtTopEdge_isWatch_scrollDiagonally_noOverscroll() {
+ assumeTrue(isWatch());
+ goFromStateIdleTo(STATE_SINGLE_PANNING);
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
+ mFullScreenMagnificationController.setCenter(
+ DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1);
+ final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
+ PointF initCoords =
+ new PointF(
+ mFullScreenMagnificationController.getCenterX(DISPLAY_0),
+ mFullScreenMagnificationController.getCenterY(DISPLAY_0));
+ PointF edgeCoords = new PointF(initCoords.x, initCoords.y);
+ // Scroll diagonally towards top-right with a bigger right delta
+ edgeCoords.offset(swipeMinDistance * 2, swipeMinDistance);
+
+ swipeAndHold(initCoords, edgeCoords);
+
+ assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_NONE);
+ assertTrue(isZoomed());
+ }
+
+ @Test
+ public void
+ testSingleFingerOverscrollAtTopEdge_isWatch_scrollDiagonally_expectedOverscrollState() {
+ assumeTrue(isWatch());
+ goFromStateIdleTo(STATE_SINGLE_PANNING);
+ float centerX =
+ (INITIAL_MAGNIFICATION_BOUNDS.right + INITIAL_MAGNIFICATION_BOUNDS.left) / 2.0f;
+ mFullScreenMagnificationController.setCenter(
+ DISPLAY_0, centerX, INITIAL_MAGNIFICATION_BOUNDS.top, false, 1);
+ final float swipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop() + 1;
+ PointF initCoords =
+ new PointF(
+ mFullScreenMagnificationController.getCenterX(DISPLAY_0),
+ mFullScreenMagnificationController.getCenterY(DISPLAY_0));
+ PointF edgeCoords = new PointF(initCoords.x, initCoords.y);
+ // Scroll diagonally towards top-right with a bigger top delta
+ edgeCoords.offset(swipeMinDistance, swipeMinDistance * 2);
+
+ swipeAndHold(initCoords, edgeCoords);
+
+ assertTrue(mMgh.mOverscrollHandler.mOverscrollState == mMgh.OVERSCROLL_VERTICAL_EDGE);
+ assertTrue(isZoomed());
+ }
+
+ @Test
public void testSingleFingerScrollAtEdge_isWatch_noOverscroll() {
assumeTrue(isWatch());
goFromStateIdleTo(STATE_SINGLE_PANNING);
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/BackgroundActivityStartControllerExemptionTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
index 366e519..6e48818 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerExemptionTests.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND;
@@ -23,7 +26,6 @@
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
-import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -58,7 +60,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.quality.Strictness;
import java.lang.reflect.Field;
@@ -134,9 +135,8 @@
ActivityOptions mCheckedOptions = ActivityOptions.makeBasic()
.setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
- .setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
class TestableBackgroundActivityStartController extends BackgroundActivityStartController {
private Set<Pair<Integer, Integer>> mBalPermissionUidPidPairs = new HashSet<>();
@@ -175,7 +175,6 @@
when(mService.getAppOpsManager()).thenReturn(mAppOpsManager);
setViaReflection(mService, "mProcessMap", mProcessMap);
- //Mockito.when(mSupervisor.getBackgroundActivityLaunchController()).thenReturn(mController);
setViaReflection(mSupervisor, "mRecentTasks", mRecentTasks);
mController = new TestableBackgroundActivityStartController(mService, mSupervisor);
@@ -397,7 +396,7 @@
// setup state
WindowProcessControllerMap mProcessMap = new WindowProcessControllerMap();
- WindowProcessController otherProcess = Mockito.mock(WindowProcessController.class);
+ WindowProcessController otherProcess = mock(WindowProcessController.class);
mProcessMap.put(callingPid, mCallerApp);
mProcessMap.put(REGULAR_PID_1_1, otherProcess);
setViaReflection(mService, "mProcessMap", mProcessMap);
@@ -516,14 +515,13 @@
BackgroundStartPrivileges forcedBalByPiSender = BackgroundStartPrivileges.NONE;
Intent intent = TEST_INTENT;
ActivityOptions checkedOptions = mCheckedOptions;
- checkedOptions.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
+ checkedOptions.setPendingIntentBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
BackgroundActivityStartController.BalState balState = mController.new BalState(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, null,
originatingPendingIntent, forcedBalByPiSender, mResultRecord, intent,
checkedOptions);
- assertThat(balState.isPendingIntentBalAllowedByPermission()).isTrue();
-
// call
BalVerdict realCallerVerdict = mController.checkBackgroundActivityStartAllowedBySender(
balState);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
index f110c69..e364264 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java
@@ -547,7 +547,7 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isTrue();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).contains(
+ assertThat(balState.toString()).startsWith(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
@@ -563,6 +563,7 @@
+ "balAllowedByPiCreator: BSP.ALLOW_BAL; "
+ "balAllowedByPiCreatorWithHardening: BSP.ALLOW_BAL; "
+ "resultIfPiCreatorAllowsBal: null; "
+ + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
+ "isPendingIntent: false; "
@@ -646,7 +647,7 @@
assertThat(balState.callerExplicitOptInOrOut()).isFalse();
assertThat(balState.realCallerExplicitOptInOrAutoOptIn()).isFalse();
assertThat(balState.realCallerExplicitOptInOrOut()).isFalse();
- assertThat(balState.toString()).contains(
+ assertThat(balState.toString()).startsWith(
"[callingPackage: package.app1; "
+ "callingPackageTargetSdk: -1; "
+ "callingUid: 10001; "
@@ -662,6 +663,7 @@
+ "balAllowedByPiCreator: BSP.NONE; "
+ "balAllowedByPiCreatorWithHardening: BSP.NONE; "
+ "resultIfPiCreatorAllowsBal: null; "
+ + "callerStartMode: MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; "
+ "hasRealCaller: true; "
+ "isCallForResult: false; "
+ "isPendingIntent: true; "
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
index 3fcf304..2e0d4d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowSettingsProviderTests.java
@@ -24,12 +24,15 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.testng.Assert.assertFalse;
import android.annotation.Nullable;
@@ -58,6 +61,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.List;
import java.util.function.Consumer;
/**
@@ -352,20 +356,58 @@
}
@Test
- public void testRemovesStaleDisplaySettings() {
+ public void testRemovesStaleDisplaySettings_defaultDisplay_removesStaleDisplaySettings() {
assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
- final DisplayWindowSettingsProvider provider =
- new DisplayWindowSettingsProvider(mDefaultVendorSettingsStorage,
- mOverrideSettingsStorage);
- final DisplayInfo displayInfo = mSecondaryDisplay.getDisplayInfo();
- updateOverrideSettings(provider, displayInfo, settings -> settings.mForcedDensity = 356);
+ // Write density setting for second display then remove it.
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final DisplayInfo secDisplayInfo = mSecondaryDisplay.getDisplayInfo();
+ updateOverrideSettings(provider, secDisplayInfo, setting -> setting.mForcedDensity = 356);
mRootWindowContainer.removeChild(mSecondaryDisplay);
- provider.removeStaleDisplaySettings(mRootWindowContainer);
+ // Write density setting for inner and outer default display.
+ final DisplayInfo innerDisplayInfo = mPrimaryDisplay.getDisplayInfo();
+ final DisplayInfo outerDisplayInfo = new DisplayInfo(secDisplayInfo);
+ outerDisplayInfo.displayId = mPrimaryDisplay.mDisplayId;
+ outerDisplayInfo.uniqueId = "TEST_OUTER_DISPLAY_" + System.currentTimeMillis();
+ updateOverrideSettings(provider, innerDisplayInfo, setting -> setting.mForcedDensity = 490);
+ updateOverrideSettings(provider, outerDisplayInfo, setting -> setting.mForcedDensity = 420);
+ final List<DisplayInfo> possibleDisplayInfos = List.of(innerDisplayInfo, outerDisplayInfo);
+ doReturn(possibleDisplayInfos)
+ .when(mWm).getPossibleDisplayInfoLocked(eq(innerDisplayInfo.displayId));
+
+ provider.removeStaleDisplaySettingsLocked(mWm, mRootWindowContainer);
assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();
- assertThat(provider.getOverrideSettingsSize()).isEqualTo(0);
+ assertThat(provider.getOverrideSettingsSize()).isEqualTo(2);
+ assertThat(provider.getOverrideSettings(innerDisplayInfo).mForcedDensity).isEqualTo(490);
+ assertThat(provider.getOverrideSettings(outerDisplayInfo).mForcedDensity).isEqualTo(420);
+ }
+
+ @Test
+ public void testRemovesStaleDisplaySettings_displayNotInLayout_keepsDisplaySettings() {
+ assumeTrue(com.android.window.flags.Flags.perUserDisplayWindowSettings());
+
+ // Write density setting for primary display.
+ final DisplayWindowSettingsProvider provider = new DisplayWindowSettingsProvider(
+ mDefaultVendorSettingsStorage, mOverrideSettingsStorage);
+ final DisplayInfo primDisplayInfo = mPrimaryDisplay.getDisplayInfo();
+ updateOverrideSettings(provider, primDisplayInfo, setting -> setting.mForcedDensity = 420);
+
+ // Add a virtual display and write density setting for it.
+ final DisplayInfo virtDisplayInfo = new DisplayInfo(primDisplayInfo);
+ virtDisplayInfo.uniqueId = "TEST_VIRTUAL_DISPLAY_" + System.currentTimeMillis();
+ createNewDisplay(virtDisplayInfo);
+ waitUntilHandlersIdle(); // Wait until unfrozen after a display is added.
+ updateOverrideSettings(provider, virtDisplayInfo, setting -> setting.mForcedDensity = 490);
+
+ provider.removeStaleDisplaySettingsLocked(mWm, mRootWindowContainer);
+
+ assertThat(mOverrideSettingsStorage.wasWriteSuccessful()).isTrue();
+ assertThat(provider.getOverrideSettingsSize()).isEqualTo(2);
+ assertThat(provider.getOverrideSettings(primDisplayInfo).mForcedDensity).isEqualTo(420);
+ assertThat(provider.getOverrideSettings(virtDisplayInfo).mForcedDensity).isEqualTo(490);
}
/**
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