summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt7
-rw-r--r--core/java/android/app/BroadcastOptions.java66
-rw-r--r--core/java/android/credentials/CredentialManager.java174
-rw-r--r--core/java/android/credentials/PrepareGetCredentialResponse.java7
-rw-r--r--core/java/android/widget/TextView.java36
-rw-r--r--core/java/com/android/internal/expresslog/Histogram.java13
-rw-r--r--core/jni/android_view_DisplayEventReceiver.cpp24
-rw-r--r--core/jni/android_view_MotionEvent.cpp7
-rw-r--r--core/res/res/values/config.xml5
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/credentials/CredentialManagerTest.java32
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt95
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java2
-rw-r--r--native/android/input.cpp3
-rw-r--r--packages/SystemUI/AndroidManifest.xml12
-rw-r--r--packages/SystemUI/docs/user-switching.md6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt6
-rw-r--r--packages/SystemUI/res/values/styles.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt150
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt47
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt4
-rw-r--r--services/autofill/java/com/android/server/autofill/SaveEventLogger.java318
-rw-r--r--services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java160
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java39
-rw-r--r--services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java28
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java8
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java29
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java12
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java112
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java21
-rw-r--r--services/core/java/com/android/server/wm/Transition.java18
-rw-r--r--services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt12
-rw-r--r--services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp27
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java77
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java55
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt41
55 files changed, 1678 insertions, 590 deletions
diff --git a/core/api/current.txt b/core/api/current.txt
index 51c6f6a5eecc..45c9becdcfd4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -13655,9 +13655,9 @@ package android.credentials {
public final class CredentialManager {
method public void clearCredentialState(@NonNull android.credentials.ClearCredentialStateRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.credentials.ClearCredentialStateException>);
- method public void createCredential(@NonNull android.credentials.CreateCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
- method public void getCredential(@NonNull android.credentials.GetCredentialRequest, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
- method public void getCredential(@NonNull android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle, @NonNull android.app.Activity, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+ method public void createCredential(@NonNull android.content.Context, @NonNull android.credentials.CreateCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.CreateCredentialResponse,android.credentials.CreateCredentialException>);
+ method public void getCredential(@NonNull android.content.Context, @NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
+ method public void getCredential(@NonNull android.content.Context, @NonNull android.credentials.PrepareGetCredentialResponse.PendingGetCredentialHandle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.GetCredentialResponse,android.credentials.GetCredentialException>);
method public boolean isEnabledCredentialProviderService(@NonNull android.content.ComponentName);
method public void prepareGetCredential(@NonNull android.credentials.GetCredentialRequest, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.credentials.PrepareGetCredentialResponse,android.credentials.GetCredentialException>);
method public void registerCredentialDescription(@NonNull android.credentials.RegisterCredentialDescriptionRequest);
@@ -60403,6 +60403,7 @@ package android.widget {
method public void setLineBreakStyle(int);
method public void setLineBreakWordStyle(int);
method public void setLineHeight(@IntRange(from=0) @Px int);
+ method public void setLineHeight(int, @FloatRange(from=0) float);
method public void setLineSpacing(float, float);
method public void setLines(int);
method public final void setLinkTextColor(@ColorInt int);
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index d23d3cd87fdb..6357798a6cab 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -37,8 +37,6 @@ import android.os.PowerExemptionManager;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.PowerExemptionManager.TempAllowListType;
-import com.android.internal.util.Preconditions;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -61,7 +59,8 @@ public class BroadcastOptions extends ComponentOptions {
private long mRequireCompatChangeId = CHANGE_INVALID;
private long mIdForResponseEvent;
private @DeliveryGroupPolicy int mDeliveryGroupPolicy;
- private @Nullable String mDeliveryGroupMatchingKey;
+ private @Nullable String mDeliveryGroupMatchingNamespaceFragment;
+ private @Nullable String mDeliveryGroupMatchingKeyFragment;
private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
private @DeferralPolicy int mDeferralPolicy;
@@ -196,7 +195,13 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.deliveryGroupPolicy";
/**
- * Corresponds to {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * Corresponds to namespace fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
+ */
+ private static final String KEY_DELIVERY_GROUP_NAMESPACE =
+ "android:broadcast.deliveryGroupMatchingNamespace";
+
+ /**
+ * Corresponds to key fragment of {@link #setDeliveryGroupMatchingKey(String, String)}.
*/
private static final String KEY_DELIVERY_GROUP_KEY =
"android:broadcast.deliveryGroupMatchingKey";
@@ -337,7 +342,8 @@ public class BroadcastOptions extends ComponentOptions {
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
mDeliveryGroupPolicy = opts.getInt(KEY_DELIVERY_GROUP_POLICY,
DELIVERY_GROUP_POLICY_ALL);
- mDeliveryGroupMatchingKey = opts.getString(KEY_DELIVERY_GROUP_KEY);
+ mDeliveryGroupMatchingNamespaceFragment = opts.getString(KEY_DELIVERY_GROUP_NAMESPACE);
+ mDeliveryGroupMatchingKeyFragment = opts.getString(KEY_DELIVERY_GROUP_KEY);
mDeliveryGroupExtrasMerger = opts.getParcelable(KEY_DELIVERY_GROUP_EXTRAS_MERGER,
BundleMerger.class);
mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
@@ -851,11 +857,8 @@ public class BroadcastOptions extends ComponentOptions {
@NonNull
public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
@NonNull String key) {
- Preconditions.checkArgument(!namespace.contains(":"),
- "namespace should not contain ':'");
- Preconditions.checkArgument(!key.contains(":"),
- "key should not contain ':'");
- mDeliveryGroupMatchingKey = namespace + ":" + key;
+ mDeliveryGroupMatchingNamespaceFragment = Objects.requireNonNull(namespace);
+ mDeliveryGroupMatchingKeyFragment = Objects.requireNonNull(key);
return this;
}
@@ -868,7 +871,38 @@ public class BroadcastOptions extends ComponentOptions {
*/
@Nullable
public String getDeliveryGroupMatchingKey() {
- return mDeliveryGroupMatchingKey;
+ if (mDeliveryGroupMatchingNamespaceFragment == null
+ || mDeliveryGroupMatchingKeyFragment == null) {
+ return null;
+ }
+ return String.join(":", mDeliveryGroupMatchingNamespaceFragment,
+ mDeliveryGroupMatchingKeyFragment);
+ }
+
+ /**
+ * Return the namespace fragment that is used to identify the delivery group that this
+ * broadcast belongs to.
+ *
+ * @return the delivery group namespace fragment that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * @hide
+ */
+ @Nullable
+ public String getDeliveryGroupMatchingNamespaceFragment() {
+ return mDeliveryGroupMatchingNamespaceFragment;
+ }
+
+ /**
+ * Return the key fragment that is used to identify the delivery group that this
+ * broadcast belongs to.
+ *
+ * @return the delivery group key fragment that was previously set using
+ * {@link #setDeliveryGroupMatchingKey(String, String)}.
+ * @hide
+ */
+ @Nullable
+ public String getDeliveryGroupMatchingKeyFragment() {
+ return mDeliveryGroupMatchingKeyFragment;
}
/**
@@ -876,7 +910,8 @@ public class BroadcastOptions extends ComponentOptions {
* {@link #setDeliveryGroupMatchingKey(String, String)}.
*/
public void clearDeliveryGroupMatchingKey() {
- mDeliveryGroupMatchingKey = null;
+ mDeliveryGroupMatchingNamespaceFragment = null;
+ mDeliveryGroupMatchingKeyFragment = null;
}
/**
@@ -1094,8 +1129,11 @@ public class BroadcastOptions extends ComponentOptions {
if (mDeliveryGroupPolicy != DELIVERY_GROUP_POLICY_ALL) {
b.putInt(KEY_DELIVERY_GROUP_POLICY, mDeliveryGroupPolicy);
}
- if (mDeliveryGroupMatchingKey != null) {
- b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKey);
+ if (mDeliveryGroupMatchingNamespaceFragment != null) {
+ b.putString(KEY_DELIVERY_GROUP_NAMESPACE, mDeliveryGroupMatchingNamespaceFragment);
+ }
+ if (mDeliveryGroupMatchingKeyFragment != null) {
+ b.putString(KEY_DELIVERY_GROUP_KEY, mDeliveryGroupMatchingKeyFragment);
}
if (mDeliveryGroupPolicy == DELIVERY_GROUP_POLICY_MERGED) {
if (mDeliveryGroupExtrasMerger != null) {
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 5579d2263d06..00ce17adfda6 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -25,11 +25,11 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.app.Activity;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
+import android.os.Binder;
import android.os.CancellationSignal;
import android.os.ICancellationSignal;
import android.os.OutcomeReceiver;
@@ -126,20 +126,21 @@ public final class CredentialManager {
* need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
+ * @param context the context used to launch any UI needed; use an activity context to make sure
+ * the UI will be launched within the same task stack
* @param request the request specifying type(s) of credentials to get from the user
- * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void getCredential(
+ @NonNull Context context,
@NonNull GetCredentialRequest request,
- @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
requireNonNull(request, "request must not be null");
- requireNonNull(activity, "activity must not be null");
+ requireNonNull(context, "context must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -153,7 +154,7 @@ public final class CredentialManager {
cancelRemote =
mService.executeGetCredential(
request,
- new GetCredentialTransport(activity, executor, callback),
+ new GetCredentialTransport(context, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -175,21 +176,22 @@ public final class CredentialManager {
* request through the {@link #prepareGetCredential(
* GetCredentialRequest, CancellationSignal, Executor, OutcomeReceiver)} API.
*
+ * @param context the context used to launch any UI needed; use an activity context to make sure
+ * the UI will be launched within the same task stack
* @param pendingGetCredentialHandle the handle representing the pending operation to resume
- * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void getCredential(
+ @NonNull Context context,
@NonNull PrepareGetCredentialResponse.PendingGetCredentialHandle
- pendingGetCredentialHandle,
- @NonNull Activity activity,
+ pendingGetCredentialHandle,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
requireNonNull(pendingGetCredentialHandle, "pendingGetCredentialHandle must not be null");
- requireNonNull(activity, "activity must not be null");
+ requireNonNull(context, "context must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -198,7 +200,7 @@ public final class CredentialManager {
return;
}
- pendingGetCredentialHandle.show(activity, cancellationSignal, executor, callback);
+ pendingGetCredentialHandle.show(context, cancellationSignal, executor, callback);
}
/**
@@ -207,9 +209,9 @@ public final class CredentialManager {
*
* <p>This API doesn't invoke any UI. It only performs the preparation work so that you can
* later launch the remaining get-credential operation (involves UIs) through the {@link
- * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Activity,
+ * #getCredential(PrepareGetCredentialResponse.PendingGetCredentialHandle, Context,
* CancellationSignal, Executor, OutcomeReceiver)} API which incurs less latency compared to
- * the {@link #getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
+ * the {@link #getCredential(GetCredentialRequest, Context, CancellationSignal, Executor,
* OutcomeReceiver)} API that executes the whole operation in one call.
*
* @param request the request specifying type(s) of credentials to get from the user
@@ -262,21 +264,22 @@ public final class CredentialManager {
* need additional permission {@link CREDENTIAL_MANAGER_SET_ORIGIN}
* to use this functionality
*
+ * @param context the context used to launch any UI needed; use an activity context to make sure
+ * the UI will be launched within the same task stack
* @param request the request specifying type(s) of credentials to get from the user
- * @param activity the activity used to launch any UI needed
* @param cancellationSignal an optional signal that allows for cancelling this call
* @param executor the callback will take place on this {@link Executor}
* @param callback the callback invoked when the request succeeds or fails
*/
public void createCredential(
+ @NonNull Context context,
@NonNull CreateCredentialRequest request,
- @NonNull Activity activity,
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
requireNonNull(request, "request must not be null");
- requireNonNull(activity, "activity must not be null");
+ requireNonNull(context, "context must not be null");
requireNonNull(executor, "executor must not be null");
requireNonNull(callback, "callback must not be null");
@@ -290,7 +293,7 @@ public final class CredentialManager {
cancelRemote =
mService.executeCreateCredential(
request,
- new CreateCredentialTransport(activity, executor, callback),
+ new CreateCredentialTransport(context, executor, callback),
mContext.getOpPackageName());
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -547,14 +550,24 @@ public final class CredentialManager {
@Override
public void onResponse(PrepareGetCredentialResponseInternal response) {
- mExecutor.execute(() -> mCallback.onResult(
- new PrepareGetCredentialResponse(response, mGetCredentialTransport)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(
+ new PrepareGetCredentialResponse(response, mGetCredentialTransport)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new GetCredentialException(errorType, message)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(new GetCredentialException(errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@@ -587,7 +600,12 @@ public final class CredentialManager {
@Override
public void onResponse(GetCredentialResponse response) {
if (mCallback != null) {
- mCallback.onResponse(response);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mCallback.onResponse(response);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
} else {
Log.d(TAG, "Unexpected onResponse call before the show invocation");
}
@@ -596,7 +614,12 @@ public final class CredentialManager {
@Override
public void onError(String errorType, String message) {
if (mCallback != null) {
- mCallback.onError(errorType, message);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mCallback.onError(errorType, message);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
} else {
Log.d(TAG, "Unexpected onError call before the show invocation");
}
@@ -606,15 +629,15 @@ public final class CredentialManager {
private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Activity mActivity;
+ private final Context mContext;
private final Executor mExecutor;
private final OutcomeReceiver<GetCredentialResponse, GetCredentialException> mCallback;
private GetCredentialTransport(
- Activity activity,
+ Context context,
Executor executor,
OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
- mActivity = activity;
+ mContext = context;
mExecutor = executor;
mCallback = callback;
}
@@ -622,42 +645,57 @@ public final class CredentialManager {
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
- mExecutor.execute(() -> mCallback.onError(
- new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onError(
+ new GetCredentialException(GetCredentialException.TYPE_UNKNOWN)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void onResponse(GetCredentialResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(response));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new GetCredentialException(errorType, message)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(new GetCredentialException(errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
private static class CreateCredentialTransport extends ICreateCredentialCallback.Stub {
// TODO: listen for cancellation to release callback.
- private final Activity mActivity;
+ private final Context mContext;
private final Executor mExecutor;
private final OutcomeReceiver<CreateCredentialResponse, CreateCredentialException>
mCallback;
private CreateCredentialTransport(
- Activity activity,
+ Context context,
Executor executor,
OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
- mActivity = activity;
+ mContext = context;
mExecutor = executor;
mCallback = callback;
}
@@ -665,26 +703,41 @@ public final class CredentialManager {
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- mActivity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ mContext.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(
TAG,
"startIntentSender() failed for intent:" + pendingIntent.getIntentSender(),
e);
- mExecutor.execute(() -> mCallback.onError(
- new CreateCredentialException(CreateCredentialException.TYPE_UNKNOWN)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onError(
+ new CreateCredentialException(CreateCredentialException.TYPE_UNKNOWN)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@Override
public void onResponse(CreateCredentialResponse response) {
- mExecutor.execute(() -> mCallback.onResult(response));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(response));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new CreateCredentialException(errorType, message)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(new CreateCredentialException(errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@@ -702,13 +755,24 @@ public final class CredentialManager {
@Override
public void onSuccess() {
- mCallback.onResult(null);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mCallback.onResult(null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(
+ new ClearCredentialStateException(errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
@@ -725,18 +789,34 @@ public final class CredentialManager {
}
public void onResponse(Void result) {
- mExecutor.execute(() -> mCallback.onResult(result));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(result));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onResponse() {
- mExecutor.execute(() -> mCallback.onResult(null));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(() -> mCallback.onResult(null));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
@Override
public void onError(String errorType, String message) {
- mExecutor.execute(
- () -> mCallback.onError(new SetEnabledProvidersException(errorType, message)));
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onError(
+ new SetEnabledProvidersException(errorType, message)));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
}
diff --git a/core/java/android/credentials/PrepareGetCredentialResponse.java b/core/java/android/credentials/PrepareGetCredentialResponse.java
index 81e906859cb8..056b18a51b6d 100644
--- a/core/java/android/credentials/PrepareGetCredentialResponse.java
+++ b/core/java/android/credentials/PrepareGetCredentialResponse.java
@@ -24,6 +24,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.Activity;
import android.app.PendingIntent;
+import android.content.Context;
import android.content.IntentSender;
import android.os.CancellationSignal;
import android.os.OutcomeReceiver;
@@ -67,7 +68,7 @@ public final class PrepareGetCredentialResponse {
}
/** @hide */
- void show(@NonNull Activity activity, @Nullable CancellationSignal cancellationSignal,
+ void show(@NonNull Context context, @Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
if (mPendingIntent == null) {
@@ -80,7 +81,7 @@ public final class PrepareGetCredentialResponse {
@Override
public void onPendingIntent(PendingIntent pendingIntent) {
try {
- activity.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(pendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
@@ -101,7 +102,7 @@ public final class PrepareGetCredentialResponse {
});
try {
- activity.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
+ context.startIntentSender(mPendingIntent.getIntentSender(), null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "startIntentSender() failed for intent for show()", e);
executor.execute(() -> callback.onError(
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index fd80981fe4b8..593f3dbb99de 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4604,7 +4604,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
+ @NonNull
+ private DisplayMetrics getDisplayMetricsOrSystem() {
Context c = getContext();
Resources r;
@@ -4614,8 +4615,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
r = c.getResources();
}
+ return r.getDisplayMetrics();
+ }
+
+ private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) {
mTextSizeUnit = unit;
- setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()),
+ setRawTextSize(TypedValue.applyDimension(unit, size, getDisplayMetricsOrSystem()),
shouldRequestLayout);
}
@@ -6197,10 +6202,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) {
- Preconditions.checkArgumentNonnegative(lineHeight);
+ setLineHeightPx(lineHeight);
+ }
+
+ private void setLineHeightPx(@Px @FloatRange(from = 0) float lineHeight) {
+ Preconditions.checkArgumentNonnegative((int) lineHeight);
final int fontHeight = getPaint().getFontMetricsInt(null);
// Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw.
+ // TODO(b/274974975): should this also check if lineSpacing needs to change?
if (lineHeight != fontHeight) {
// Set lineSpacingExtra by the difference of lineSpacing with lineHeight
setLineSpacing(lineHeight - fontHeight, 1f);
@@ -6208,6 +6218,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets an explicit line height to a given unit and value for this TextView. This is equivalent
+ * to the vertical distance between subsequent baselines in the TextView. See {@link
+ * TypedValue} for the possible dimension units.
+ *
+ * @param unit The desired dimension unit. SP units are strongly recommended so that line height
+ * stays proportional to the text size when fonts are scaled up for accessibility.
+ * @param lineHeight The desired line height in the given units.
+ *
+ * @see #setLineSpacing(float, float)
+ * @see #getLineSpacingExtra()
+ *
+ * @attr ref android.R.styleable#TextView_lineHeight
+ */
+ @android.view.RemotableViewMethod
+ public void setLineHeight(int unit, @FloatRange(from = 0) float lineHeight) {
+ setLineHeightPx(
+ TypedValue.applyDimension(unit, lineHeight, getDisplayMetricsOrSystem()));
+ }
+
+ /**
* Set Highlights
*
* @param highlights A highlight object. Call with null for reset.
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
index 65fbb03bf967..2fe784a5a855 100644
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ b/core/java/com/android/internal/expresslog/Histogram.java
@@ -54,6 +54,19 @@ public final class Histogram {
/*count*/ 1, binIndex);
}
+ /**
+ * Logs increment sample count for automatically calculated bin
+ *
+ * @param uid used as a dimension for the count metric
+ * @param sample value
+ * @hide
+ */
+ public void logSampleWithUid(int uid, float sample) {
+ final int binIndex = mBinOptions.getBinForSample(sample);
+ FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
+ mMetricIdHash, /*count*/ 1, binIndex, uid);
+ }
+
/** Used by Histogram to map data sample to corresponding bin */
public interface BinOptions {
/**
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 0c3ff6c28ea7..410b44161cf6 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -18,19 +18,17 @@
//#define LOG_NDEBUG 0
-#include <nativehelper/JNIHelp.h>
-
-#include <inttypes.h>
-
#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
#include <gui/DisplayEventDispatcher.h>
+#include <inttypes.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
#include <utils/Looper.h>
#include <utils/threads.h>
-#include "android_os_MessageQueue.h"
-
-#include <nativehelper/ScopedLocalRef.h>
+#include "android_os_MessageQueue.h"
#include "core_jni_helpers.h"
namespace android {
@@ -133,6 +131,12 @@ static jobject createJavaVsyncEventData(JNIEnv* env, VsyncEventData vsyncEventDa
gDisplayEventReceiverClassInfo
.frameTimelineClassInfo.clazz,
/*initial element*/ NULL));
+ if (!frameTimelineObjs.get() || env->ExceptionCheck()) {
+ ALOGW("%s: Failed to create FrameTimeline array", __func__);
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return NULL;
+ }
for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
VsyncEventData::FrameTimeline frameTimeline = vsyncEventData.frameTimelines[i];
ScopedLocalRef<jobject>
@@ -144,6 +148,12 @@ static jobject createJavaVsyncEventData(JNIEnv* env, VsyncEventData vsyncEventDa
frameTimeline.vsyncId,
frameTimeline.expectedPresentationTime,
frameTimeline.deadlineTimestamp));
+ if (!frameTimelineObj.get() || env->ExceptionCheck()) {
+ ALOGW("%s: Failed to create FrameTimeline object", __func__);
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return NULL;
+ }
env->SetObjectArrayElement(frameTimelineObjs.get(), i, frameTimelineObj.get());
}
return env->NewObject(gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz,
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 49f47c56e6a6..2e9f1790a4a5 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -316,8 +316,9 @@ static void pointerPropertiesToNative(JNIEnv* env, jobject pointerPropertiesObj,
outPointerProperties->clear();
outPointerProperties->id = env->GetIntField(pointerPropertiesObj,
gPointerPropertiesClassInfo.id);
- outPointerProperties->toolType = env->GetIntField(pointerPropertiesObj,
+ const int32_t toolType = env->GetIntField(pointerPropertiesObj,
gPointerPropertiesClassInfo.toolType);
+ outPointerProperties->toolType = static_cast<ToolType>(toolType);
}
static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* pointerProperties,
@@ -325,7 +326,7 @@ static void pointerPropertiesFromNative(JNIEnv* env, const PointerProperties* po
env->SetIntField(outPointerPropertiesObj, gPointerPropertiesClassInfo.id,
pointerProperties->id);
env->SetIntField(outPointerPropertiesObj, gPointerPropertiesClassInfo.toolType,
- pointerProperties->toolType);
+ static_cast<int32_t>(pointerProperties->toolType));
}
@@ -535,7 +536,7 @@ static jint android_view_MotionEvent_nativeGetToolType(JNIEnv* env, jclass clazz
if (!validatePointerIndex(env, pointerIndex, *event)) {
return -1;
}
- return event->getToolType(pointerIndex);
+ return static_cast<jint>(event->getToolType(pointerIndex));
}
static jlong android_view_MotionEvent_nativeGetEventTimeNanos(JNIEnv* env, jclass clazz,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5a35ca74e5dc..239747a37ec6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3345,6 +3345,11 @@
<!-- The duration in which a recent task is considered in session and should be visible. -->
<integer name="config_activeTaskDurationHours">6</integer>
+ <!-- Whether this device prefers to show snapshot or splash screen on back predict target.
+ When set true, there will create windowless starting surface for the preview target, so it
+ won't affect activity's lifecycle. This should only be disabled on low-ram device. -->
+ <bool name="config_predictShowStartingSurface">true</bool>
+
<!-- default window ShowCircularMask property -->
<bool name="config_windowShowCircularMask">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5c734df4ebf3..a09cee797878 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -393,6 +393,7 @@
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks" />
<java-symbol type="integer" name="config_activeTaskDurationHours" />
<java-symbol type="bool" name="config_windowShowCircularMask" />
+ <java-symbol type="bool" name="config_predictShowStartingSurface" />
<java-symbol type="bool" name="config_windowEnableCircularEmulatorDisplayOverlay" />
<java-symbol type="bool" name="config_supportMicNearUltrasound" />
<java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
diff --git a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
index c7e026123487..b0d5240c61ba 100644
--- a/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
+++ b/core/tests/coretests/src/android/credentials/CredentialManagerTest.java
@@ -144,7 +144,7 @@ public class CredentialManagerTest {
public void testGetCredential_nullRequest() {
GetCredentialRequest nullRequest = null;
assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(nullRequest, mMockActivity, null, mExecutor,
+ () -> mCredentialManager.getCredential(mMockActivity, nullRequest, null, mExecutor,
result -> {
}));
}
@@ -152,7 +152,7 @@ public class CredentialManagerTest {
@Test
public void testGetCredential_nullActivity() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mGetRequest, null, null, mExecutor,
+ () -> mCredentialManager.getCredential(null, mGetRequest, null, mExecutor,
result -> {
}));
}
@@ -160,7 +160,7 @@ public class CredentialManagerTest {
@Test
public void testGetCredential_nullExecutor() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mGetRequest, mMockActivity, null, null,
+ () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
result -> {
}));
}
@@ -168,7 +168,7 @@ public class CredentialManagerTest {
@Test
public void testGetCredential_nullCallback() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.getCredential(mGetRequest, mMockActivity, null, null,
+ () -> mCredentialManager.getCredential(mMockActivity, mGetRequest, null, null,
null));
}
@@ -184,7 +184,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.getCredential(mGetRequest, mMockActivity, null, mExecutor, callback);
+ mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
callbackCaptor.getValue().onError(GetCredentialException.TYPE_NO_CREDENTIAL,
@@ -200,7 +200,7 @@ public class CredentialManagerTest {
final CancellationSignal cancellation = new CancellationSignal();
cancellation.cancel();
- mCredentialManager.getCredential(mGetRequest, mMockActivity, cancellation, mExecutor,
+ mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
result -> {
});
@@ -218,7 +218,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeGetCredential(any(), any(), any())).thenReturn(
serviceSignal);
- mCredentialManager.getCredential(mGetRequest, mMockActivity, cancellation, mExecutor,
+ mCredentialManager.getCredential(mMockActivity, mGetRequest, cancellation, mExecutor,
callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
@@ -241,7 +241,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeGetCredential(any(), callbackCaptor.capture(),
any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.getCredential(mGetRequest, mMockActivity, null, mExecutor, callback);
+ mCredentialManager.getCredential(mMockActivity, mGetRequest, null, mExecutor, callback);
verify(mMockCredentialManagerService).executeGetCredential(any(), any(), eq(mPackageName));
callbackCaptor.getValue().onResponse(new GetCredentialResponse(cred));
@@ -253,7 +253,7 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_nullRequest() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(null, mMockActivity, null, mExecutor,
+ () -> mCredentialManager.createCredential(mMockActivity, null, null, mExecutor,
result -> {
}));
}
@@ -261,7 +261,7 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_nullActivity() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mCreateRequest, null, null, mExecutor,
+ () -> mCredentialManager.createCredential(null, mCreateRequest, null, mExecutor,
result -> {
}));
}
@@ -269,7 +269,7 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_nullExecutor() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mCreateRequest, mMockActivity, null, null,
+ () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, null,
result -> {
}));
}
@@ -277,7 +277,7 @@ public class CredentialManagerTest {
@Test
public void testCreateCredential_nullCallback() {
assertThrows(NullPointerException.class,
- () -> mCredentialManager.createCredential(mCreateRequest, mMockActivity, null,
+ () -> mCredentialManager.createCredential(mMockActivity, mCreateRequest, null,
mExecutor, null));
}
@@ -286,7 +286,7 @@ public class CredentialManagerTest {
final CancellationSignal cancellation = new CancellationSignal();
cancellation.cancel();
- mCredentialManager.createCredential(mCreateRequest, mMockActivity, cancellation, mExecutor,
+ mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
result -> {
});
@@ -304,7 +304,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeCreateCredential(any(), any(), any())).thenReturn(
serviceSignal);
- mCredentialManager.createCredential(mCreateRequest, mMockActivity, cancellation, mExecutor,
+ mCredentialManager.createCredential(mMockActivity, mCreateRequest, cancellation, mExecutor,
callback);
verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
@@ -326,7 +326,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.createCredential(mCreateRequest, mMockActivity, null, mExecutor,
+ mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
callback);
verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
eq(mPackageName));
@@ -353,7 +353,7 @@ public class CredentialManagerTest {
when(mMockCredentialManagerService.executeCreateCredential(any(), callbackCaptor.capture(),
any())).thenReturn(mock(ICancellationSignal.class));
- mCredentialManager.createCredential(mCreateRequest, mMockActivity, null, mExecutor,
+ mCredentialManager.createCredential(mMockActivity, mCreateRequest, null, mExecutor,
callback);
verify(mMockCredentialManagerService).executeCreateCredential(any(), any(),
eq(mPackageName));
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 51f99ec637da..22462eb11a8f 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -38,8 +38,11 @@ import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
import android.net.Uri;
import android.os.Build;
+import android.os.SystemProperties;
import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
@@ -928,6 +931,8 @@ public final class ImageDecoder implements AutoCloseable {
case "image/x-pentax-pef":
case "image/x-samsung-srw":
return true;
+ case "image/avif":
+ return isP010SupportedForAV1();
default:
return false;
}
@@ -2063,6 +2068,49 @@ public final class ImageDecoder implements AutoCloseable {
return decodeBitmapImpl(src, null);
}
+ private static boolean sIsP010SupportedForAV1 = false;
+ private static boolean sIsP010SupportedForAV1Initialized = false;
+
+ /**
+ * Checks if the device supports decoding 10-bit AV1.
+ */
+ private static boolean isP010SupportedForAV1() {
+ if (sIsP010SupportedForAV1Initialized) {
+ return sIsP010SupportedForAV1;
+ }
+
+ sIsP010SupportedForAV1Initialized = true;
+
+ if (hasHardwareDecoder("video/av01")) {
+ sIsP010SupportedForAV1 = true;
+ return true;
+ }
+
+ sIsP010SupportedForAV1 = SystemProperties.getInt("ro.product.first_api_level", 0) >=
+ Build.VERSION_CODES.S;
+ return sIsP010SupportedForAV1;
+ }
+
+ /**
+ * Checks if the device has hardware decoder for the target mime type.
+ */
+ private static boolean hasHardwareDecoder(String mime) {
+ final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ for (MediaCodecInfo info : sMCL.getCodecInfos()) {
+ if (info.isEncoder() == false && info.isHardwareAccelerated()) {
+ try {
+ if (info.getCapabilitiesForType(mime) != null) {
+ return true;
+ }
+ } catch (IllegalArgumentException e) {
+ // mime is not supported
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Private method called by JNI.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 349ff36ce8ce..6f993aebdc65 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -384,8 +384,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onMove() {
- if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()
- || mActiveCallback == null) {
+ if (!mBackGestureStarted || mBackNavigationInfo == null || mActiveCallback == null) {
return;
}
@@ -424,9 +423,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackStarted(backEvent);
- }
+ callback.onBackStarted(backEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackStarted error: ", e);
}
@@ -448,9 +445,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackCancelled();
- }
+ callback.onBackCancelled();
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackCancelled error: ", e);
}
@@ -462,19 +457,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
try {
- if (mEnableAnimations.get()) {
- callback.onBackProgressed(backEvent);
- }
+ callback.onBackProgressed(backEvent);
} catch (RemoteException e) {
Log.e(TAG, "dispatchOnBackProgressed error: ", e);
}
}
- private boolean shouldDispatchAnimation(IOnBackInvokedCallback callback) {
- // TODO(b/258698745): Only dispatch to animation callbacks.
- return mEnableAnimations.get();
- }
-
/**
* Sets to true when the back gesture has passed the triggering threshold, false otherwise.
*/
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
new file mode 100644
index 000000000000..083cfd294f96
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 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.wm.shell.flicker.pip
+
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.RequiresDevice
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import com.android.server.wm.flicker.testapp.ActivityOptions
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test the dragging of a PIP window.
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
+ private var isDraggedLeft: Boolean = true
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+
+ setup {
+ tapl.setEnableRotation(true)
+ // Launch the PIP activity and wait for it to enter PiP mode
+ RemoveAllTasksButHomeRule.removeAllTasksButHome()
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+
+ // determine the direction of dragging to test for
+ isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ teardown {
+ // release the primary pointer after dragging without release
+ pipApp.releasePipAfterDragging()
+
+ pipApp.exit(wmHelper)
+ tapl.setEnableRotation(false)
+ }
+ transitions {
+ pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50)
+ }
+ }
+
+ @Postsubmit
+ @Test
+ fun pipLayerMovesAwayFromEdge() {
+ flicker.assertLayers {
+ val pipLayerList = layers { pipApp.layerMatchesAnyOf(it) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ if (isDraggedLeft) {
+ previous.visibleRegion.isToTheRight(current.visibleRegion.region)
+ } else {
+ current.visibleRegion.isToTheRight(previous.visibleRegion.region)
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 169b9bd4dea7..806bffebd4cb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -253,8 +253,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
- verify(mAppCallback, never()).onBackStarted(any());
- verify(mAppCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mAppCallback, times(1)).onBackInvoked();
verify(mAnimatorCallback, never()).onBackStarted(any());
diff --git a/native/android/input.cpp b/native/android/input.cpp
index f1c30889c4db..432e21cb5c08 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -149,7 +149,8 @@ int32_t AMotionEvent_getPointerId(const AInputEvent* motion_event, size_t pointe
}
int32_t AMotionEvent_getToolType(const AInputEvent* motion_event, size_t pointer_index) {
- return static_cast<const MotionEvent*>(motion_event)->getToolType(pointer_index);
+ const MotionEvent& motion = static_cast<const MotionEvent&>(*motion_event);
+ return static_cast<int32_t>(motion.getToolType(pointer_index));
}
float AMotionEvent_getRawX(const AInputEvent* motion_event, size_t pointer_index) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d92e65c3c581..ff570524ca0e 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -947,18 +947,6 @@
android:visibleToInstantApps="true">
</activity>
- <activity android:name=".user.UserSwitcherActivity"
- android:label="@string/accessibility_multi_user_switch_switcher"
- android:theme="@style/Theme.UserSwitcherActivity"
- android:excludeFromRecents="true"
- android:showWhenLocked="true"
- android:showForAllUsers="true"
- android:finishOnTaskLaunch="true"
- android:lockTaskMode="always"
- android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
- android:visibleToInstantApps="true">
- </activity>
-
<receiver android:name=".controls.management.ControlsRequestReceiver"
android:exported="true">
<intent-filter>
diff --git a/packages/SystemUI/docs/user-switching.md b/packages/SystemUI/docs/user-switching.md
index b9509eb41c3c..01cba426f782 100644
--- a/packages/SystemUI/docs/user-switching.md
+++ b/packages/SystemUI/docs/user-switching.md
@@ -6,7 +6,7 @@ Multiple users and the ability to switch between them is controlled by Settings
### Quick Settings
-In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherActivity][5]).
+In the QS footer, an icon becomes available for users to tap on. The view and its onClick actions are handled by [MultiUserSwitchController][2]. Multiple visual implementations are currently in use; one for phones/foldables ([UserSwitchDialogController][6]) and one for tablets ([UserSwitcherFullscreenDialog][5]).
### Bouncer
@@ -29,7 +29,7 @@ All visual implementations should derive their logic and use the adapter specifi
## Visual Components
-### [UserSwitcherActivity][5]
+### [UserSwitcherFullscreenDialog][5]
A fullscreen user switching activity, supporting add guest/user actions if configured.
@@ -41,5 +41,5 @@ Renders user switching as a dialog over the current surface, and supports add gu
[2]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserController.java
[3]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
[4]: /frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
-[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+[5]: /frameworks/base/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
[6]: /frameworks/base/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 5b6a83c24e3d..0080517a722b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -137,6 +137,12 @@ interface ClockAnimations {
fun onPositionUpdated(fromRect: Rect, toRect: Rect, fraction: Float) {}
/**
+ * Runs when swiping clock picker, swipingFraction: 1.0 -> clock is scaled up in the preview,
+ * 0.0 -> clock is scaled down in the shade; previewRatio is previewSize / screenSize
+ */
+ fun onPickerCarouselSwiping(swipingFraction: Float, previewRatio: Float) {}
+
+ /**
* Whether this clock has a custom position update animation. If true, the keyguard will call
* `onPositionUpdated` to notify the clock of a position update animation. If false, a default
* animation will be used (e.g. a simple translation).
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 2fb1592dfe15..a3655c31fde9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -844,12 +844,10 @@
<item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
</style>
- <style name="Theme.UserSwitcherActivity" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <style name="Theme.UserSwitcherFullscreenDialog" parent="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<item name="android:statusBarColor">@color/user_switcher_fullscreen_bg</item>
<item name="android:windowBackground">@color/user_switcher_fullscreen_bg</item>
<item name="android:navigationBarColor">@color/user_switcher_fullscreen_bg</item>
- <!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen -->
- <item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
<style name="Theme.CreateUser" parent="@android:style/Theme.DeviceDefault.NoActionBar">
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 79a51d6670c4..7ffb59422bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -701,6 +701,6 @@ object Flags {
// TODO(b/272805037): Tracking Bug
@JvmField
- val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature",
- namespace = "vpn", teamfood = true)
+ val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature",
+ namespace = "vpn")
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
index 84abf57cacf2..d5129a612b04 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt
@@ -22,10 +22,10 @@ import android.content.Context
import android.content.IntentFilter
import android.hardware.biometrics.BiometricManager
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
-import android.os.Looper
import android.os.UserHandle
import android.util.Log
import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
@@ -35,8 +35,8 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.TAG
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
@@ -45,10 +45,12 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
@@ -93,8 +95,16 @@ interface BiometricSettingsRepository {
* restricted to specific postures using [R.integer.config_face_auth_supported_posture]
*/
val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
+
+ /**
+ * Whether the user manually locked down the device. This doesn't include device policy manager
+ * lockdown.
+ */
+ val isCurrentUserInLockdown: Flow<Boolean>
}
+const val TAG = "BiometricsRepositoryImpl"
+
@SysUISingleton
class BiometricSettingsRepositoryImpl
@Inject
@@ -103,19 +113,25 @@ constructor(
lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
authController: AuthController,
- userRepository: UserRepository,
+ private val userRepository: UserRepository,
devicePolicyManager: DevicePolicyManager,
@Application scope: CoroutineScope,
@Background backgroundDispatcher: CoroutineDispatcher,
biometricManager: BiometricManager?,
- @Main looper: Looper,
devicePostureRepository: DevicePostureRepository,
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
+ private val strongAuthTracker = StrongAuthTracker(userRepository, context)
+
+ override val isCurrentUserInLockdown: Flow<Boolean> =
+ strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
+
init {
+ Log.d(TAG, "Registering StrongAuthTracker")
+ lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
dumpManager.registerDumpable(this)
val configFaceAuthSupportedPosture =
DevicePosture.toPosture(
@@ -251,38 +267,14 @@ constructor(
.stateIn(scope, SharingStarted.Eagerly, false)
override val isStrongBiometricAllowed: StateFlow<Boolean> =
- selectedUserId
- .flatMapLatest { currUserId ->
- conflatedCallbackFlow {
- val callback =
- object : LockPatternUtils.StrongAuthTracker(context, looper) {
- override fun onStrongAuthRequiredChanged(userId: Int) {
- if (currUserId != userId) {
- return
- }
-
- trySendWithFailureLogging(
- isBiometricAllowedForUser(true, currUserId),
- TAG
- )
- }
-
- override fun onIsNonStrongBiometricAllowedChanged(userId: Int) {
- // no-op
- }
- }
- lockPatternUtils.registerStrongAuthTracker(callback)
- awaitClose { lockPatternUtils.unregisterStrongAuthTracker(callback) }
- }
- }
- .stateIn(
- scope,
- started = SharingStarted.Eagerly,
- initialValue =
- lockPatternUtils.isBiometricAllowedForUser(
- userRepository.getSelectedUserInfo().id
- )
+ strongAuthTracker.isStrongBiometricAllowed.stateIn(
+ scope,
+ SharingStarted.Eagerly,
+ strongAuthTracker.isBiometricAllowedForUser(
+ true,
+ userRepository.getSelectedUserInfo().id
)
+ )
override val isFingerprintEnabledByDevicePolicy: StateFlow<Boolean> =
selectedUserId
@@ -300,9 +292,44 @@ constructor(
userRepository.getSelectedUserInfo().id
)
)
+}
- companion object {
- private const val TAG = "BiometricsRepositoryImpl"
+private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
+ LockPatternUtils.StrongAuthTracker(context) {
+
+ private val _authFlags =
+ MutableStateFlow(
+ StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
+ )
+
+ val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
+ userRepository.selectedUserInfo
+ .map { it.id }
+ .distinctUntilChanged()
+ .flatMapLatest { currUserId ->
+ _authFlags
+ .filter { it.userId == currUserId }
+ .onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
+ .onStart {
+ emit(
+ StrongAuthenticationFlags(
+ currentUserId,
+ getStrongAuthForUser(currentUserId)
+ )
+ )
+ }
+ }
+
+ val isStrongBiometricAllowed: Flow<Boolean> =
+ currentUserAuthFlags.map { isBiometricAllowedForUser(true, it.userId) }
+
+ private val currentUserId
+ get() = userRepository.getSelectedUserInfo().id
+
+ override fun onStrongAuthRequiredChanged(userId: Int) {
+ val newFlags = getStrongAuthForUser(userId)
+ _authFlags.value = StrongAuthenticationFlags(userId, newFlags)
+ Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
}
}
@@ -314,3 +341,11 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
+
+private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) {
+ val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN)
+}
+
+private fun containsFlag(haystack: Int, needle: Int): Boolean {
+ return haystack and needle != 0
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
index 8387c1dd60a5..b394a079fb00 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractor.kt
@@ -89,7 +89,7 @@ interface FooterActionsInteractor {
fun showSettings(expandable: Expandable)
/** Show the user switcher. */
- fun showUserSwitcher(context: Context, expandable: Expandable)
+ fun showUserSwitcher(expandable: Expandable)
}
@SysUISingleton
@@ -177,7 +177,7 @@ constructor(
)
}
- override fun showUserSwitcher(context: Context, expandable: Expandable) {
- userInteractor.showUserSwitcher(context, expandable)
+ override fun showUserSwitcher(expandable: Expandable) {
+ userInteractor.showUserSwitcher(expandable)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index f170ac1d9d4e..3a9098ab49d3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -230,7 +230,7 @@ class FooterActionsViewModel(
return
}
- footerActionsInteractor.showUserSwitcher(context, expandable)
+ footerActionsInteractor.showUserSwitcher(expandable)
}
private fun onSettingsButtonClicked(expandable: Expandable) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserModule.java b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
index f7c8bac1b478..b2bf9727b534 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserModule.java
+++ b/packages/SystemUI/src/com/android/systemui/user/UserModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.user;
-import android.app.Activity;
import android.os.UserHandle;
import com.android.settingslib.users.EditUserInfoController;
@@ -24,11 +23,8 @@ import com.android.systemui.user.data.repository.UserRepositoryModule;
import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule;
import com.android.systemui.user.ui.dialog.UserDialogModule;
-import dagger.Binds;
import dagger.Module;
import dagger.Provides;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
/**
* Dagger module for User related classes.
@@ -49,12 +45,6 @@ public abstract class UserModule {
return new EditUserInfoController(FILE_PROVIDER_AUTHORITY);
}
- /** Provides UserSwitcherActivity */
- @Binds
- @IntoMap
- @ClassKey(UserSwitcherActivity.class)
- public abstract Activity provideUserSwitcherActivity(UserSwitcherActivity activity);
-
/**
* Provides the {@link UserHandle} for the user associated with this System UI process.
*
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
deleted file mode 100644
index 52b7fb63c1a2..000000000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2022 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.user
-
-import android.os.Bundle
-import android.view.WindowInsets.Type
-import android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
-import androidx.activity.ComponentActivity
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.R
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import dagger.Lazy
-import javax.inject.Inject
-
-/** Support a fullscreen user switcher */
-open class UserSwitcherActivity
-@Inject
-constructor(
- private val falsingCollector: FalsingCollector,
- private val viewModelFactory: Lazy<UserSwitcherViewModel.Factory>,
-) : ComponentActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.user_switcher_fullscreen)
- window.decorView.windowInsetsController?.let { controller ->
- controller.systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
- controller.hide(Type.systemBars())
- }
- val viewModel =
- ViewModelProvider(this, viewModelFactory.get())[UserSwitcherViewModel::class.java]
- UserSwitcherViewBinder.bind(
- view = requireViewById(R.id.user_switcher_root),
- viewModel = viewModel,
- lifecycleOwner = this,
- layoutInflater = layoutInflater,
- falsingCollector = falsingCollector,
- onFinish = this::finish,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
new file mode 100644
index 000000000000..72786efc416d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.user
+
+import android.content.Context
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowInsets
+import android.view.WindowInsetsController
+import com.android.systemui.R
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.user.ui.binder.UserSwitcherViewBinder
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+
+class UserSwitchFullscreenDialog(
+ context: Context,
+ private val falsingCollector: FalsingCollector,
+ private val userSwitcherViewModel: UserSwitcherViewModel,
+) : SystemUIDialog(context, R.style.Theme_UserSwitcherFullscreenDialog) {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setShowForAllUsers(true)
+ setCanceledOnTouchOutside(true)
+
+ window?.decorView?.windowInsetsController?.let { controller ->
+ controller.systemBarsBehavior =
+ WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+ controller.hide(WindowInsets.Type.systemBars())
+ }
+
+ val view =
+ LayoutInflater.from(this.context).inflate(R.layout.user_switcher_fullscreen, null)
+ setContentView(view)
+
+ UserSwitcherViewBinder.bind(
+ view = requireViewById(R.id.user_switcher_root),
+ viewModel = userSwitcherViewModel,
+ layoutInflater = layoutInflater,
+ falsingCollector = falsingCollector,
+ onFinish = this::dismiss,
+ )
+ }
+
+ override fun getWidth(): Int {
+ val displayMetrics = context.resources.displayMetrics.apply {
+ context.display.getRealMetrics(this)
+ }
+ return displayMetrics.widthPixels
+ }
+
+ override fun getHeight() = MATCH_PARENT
+
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 94dd1b309436..0ec1a214660c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -49,7 +49,6 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -513,24 +512,12 @@ constructor(
}
}
- fun showUserSwitcher(context: Context, expandable: Expandable) {
- if (!featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ fun showUserSwitcher(expandable: Expandable) {
+ if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
+ showDialog(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+ } else {
showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable))
- return
}
-
- val intent =
- Intent(context, UserSwitcherActivity::class.java).apply {
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
- }
-
- activityStarter.startActivity(
- intent,
- true /* dismissShade */,
- expandable.activityLaunchController(),
- true /* showOverlockscreenwhenlocked */,
- UserHandle.SYSTEM,
- )
}
private fun showDialog(request: ShowDialogRequestModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
index 14cc3e783fed..de73cdbb6026 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/model/ShowDialogRequestModel.kt
@@ -50,4 +50,8 @@ sealed class ShowDialogRequestModel(
data class ShowUserSwitcherDialog(
override val expandable: Expandable?,
) : ShowDialogRequestModel()
+
+ data class ShowUserSwitcherFullscreenDialog(
+ override val expandable: Expandable?,
+ ) : ShowDialogRequestModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index e13710786fbb..7236e0fd134a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -31,19 +31,18 @@ import android.widget.TextView
import androidx.constraintlayout.helper.widget.Flow as FlowWidget
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.UserSwitcherPopupMenu
import com.android.systemui.user.UserSwitcherRootView
import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.ui.viewmodel.UserActionViewModel
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import com.android.systemui.util.children
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
@@ -56,7 +55,6 @@ object UserSwitcherViewBinder {
fun bind(
view: ViewGroup,
viewModel: UserSwitcherViewModel,
- lifecycleOwner: LifecycleOwner,
layoutInflater: LayoutInflater,
falsingCollector: FalsingCollector,
onFinish: () -> Unit,
@@ -79,88 +77,92 @@ object UserSwitcherViewBinder {
addButton.setOnClickListener { viewModel.onOpenMenuButtonClicked() }
cancelButton.setOnClickListener { viewModel.onCancelButtonClicked() }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
- launch {
- viewModel.isFinishRequested
- .filter { it }
- .collect {
- onFinish()
- viewModel.onFinished()
- }
+ view.repeatWhenAttached {
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch {
+ viewModel.isFinishRequested
+ .filter { it }
+ .collect {
+ //finish requested, we want to dismiss popupmenu at the same time
+ popupMenu?.dismiss()
+ onFinish()
+ viewModel.onFinished()
+ }
+ }
}
}
- }
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
-
- launch {
- viewModel.isMenuVisible.collect { isVisible ->
- if (isVisible && popupMenu?.isShowing != true) {
- popupMenu?.dismiss()
- // Use post to make sure we show the popup menu *after* the activity is
- // ready to show one to avoid a WindowManager$BadTokenException.
- view.post {
- popupMenu =
- createAndShowPopupMenu(
- context = view.context,
- anchorView = addButton,
- adapter = popupMenuAdapter,
- onDismissed = viewModel::onMenuClosed,
- )
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch { viewModel.isOpenMenuButtonVisible.collect { addButton.isVisible = it } }
+
+ launch {
+ viewModel.isMenuVisible.collect { isVisible ->
+ if (isVisible && popupMenu?.isShowing != true) {
+ popupMenu?.dismiss()
+ // Use post to make sure we show the popup menu *after* the activity is
+ // ready to show one to avoid a WindowManager$BadTokenException.
+ view.post {
+ popupMenu =
+ createAndShowPopupMenu(
+ context = view.context,
+ anchorView = addButton,
+ adapter = popupMenuAdapter,
+ onDismissed = viewModel::onMenuClosed,
+ )
+ }
+ } else if (!isVisible && popupMenu?.isShowing == true) {
+ popupMenu?.dismiss()
+ popupMenu = null
}
- } else if (!isVisible && popupMenu?.isShowing == true) {
- popupMenu?.dismiss()
- popupMenu = null
}
}
- }
- launch {
- viewModel.menu.collect { menuViewModels ->
- popupMenuAdapter.setItems(menuViewModels)
+ launch {
+ viewModel.menu.collect { menuViewModels ->
+ popupMenuAdapter.setItems(menuViewModels)
+ }
}
- }
- launch {
- viewModel.maximumUserColumns.collect { maximumColumns ->
- flowWidget.setMaxElementsWrap(maximumColumns)
+ launch {
+ viewModel.maximumUserColumns.collect { maximumColumns ->
+ flowWidget.setMaxElementsWrap(maximumColumns)
+ }
}
- }
- launch {
- viewModel.users.collect { users ->
- val viewPool =
- gridContainerView.children
- .filter { it.tag == USER_VIEW_TAG }
- .toMutableList()
- viewPool.forEach {
- gridContainerView.removeView(it)
- flowWidget.removeView(it)
- }
- users.forEach { userViewModel ->
- val userView =
- if (viewPool.isNotEmpty()) {
- viewPool.removeAt(0)
- } else {
- val inflatedView =
- layoutInflater.inflate(
- R.layout.user_switcher_fullscreen_item,
- view,
- false,
- )
- inflatedView.tag = USER_VIEW_TAG
- inflatedView
- }
- userView.id = View.generateViewId()
- gridContainerView.addView(userView)
- flowWidget.addView(userView)
- UserViewBinder.bind(
- view = userView,
- viewModel = userViewModel,
- )
+ launch {
+ viewModel.users.collect { users ->
+ val viewPool =
+ gridContainerView.children
+ .filter { it.tag == USER_VIEW_TAG }
+ .toMutableList()
+ viewPool.forEach {
+ gridContainerView.removeView(it)
+ flowWidget.removeView(it)
+ }
+ users.forEach { userViewModel ->
+ val userView =
+ if (viewPool.isNotEmpty()) {
+ viewPool.removeAt(0)
+ } else {
+ val inflatedView =
+ layoutInflater.inflate(
+ R.layout.user_switcher_fullscreen_item,
+ view,
+ false,
+ )
+ inflatedView.tag = USER_VIEW_TAG
+ inflatedView
+ }
+ userView.id = View.generateViewId()
+ gridContainerView.addView(userView)
+ flowWidget.addView(userView)
+ UserViewBinder.bind(
+ view = userView,
+ viewModel = userViewModel,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
index 79721b370c21..0930cb8a3d7a 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/dialog/UserSwitcherDialogCoordinator.kt
@@ -26,13 +26,16 @@ import com.android.systemui.CoreStartable
import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.UserDetailView
+import com.android.systemui.user.UserSwitchFullscreenDialog
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.domain.model.ShowDialogRequestModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
import dagger.Lazy
import javax.inject.Inject
import javax.inject.Provider
@@ -54,6 +57,8 @@ constructor(
private val userDetailAdapterProvider: Provider<UserDetailView.Adapter>,
private val eventLogger: Lazy<UiEventLogger>,
private val activityStarter: Lazy<ActivityStarter>,
+ private val falsingCollector: Lazy<FalsingCollector>,
+ private val userSwitcherViewModel: Lazy<UserSwitcherViewModel>,
) : CoreStartable {
private var currentDialog: Dialog? = null
@@ -124,6 +129,15 @@ constructor(
INTERACTION_JANK_EXIT_GUEST_MODE_TAG,
),
)
+ is ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog ->
+ Pair(
+ UserSwitchFullscreenDialog(
+ context = context.get(),
+ falsingCollector = falsingCollector.get(),
+ userSwitcherViewModel = userSwitcherViewModel.get(),
+ ),
+ null, /* dialogCuj */
+ )
}
currentDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 3300e8e5b2a5..78edad7c3af2 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -55,5 +55,5 @@ constructor(
interactor.selectedUser.mapLatest { userModel -> userModel.image }
/** Action to execute on click. Should launch the user switcher */
- val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(context, it) }
+ val onClick: (Expandable) -> Unit = { interactor.showUserSwitcher(it) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 37115ad53880..afd72e7ed1be 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -17,12 +17,10 @@
package com.android.systemui.user.ui.viewmodel
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.drawable.CircularDrawable
-import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.UserInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
@@ -36,12 +34,13 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
/** Models UI state for the user switcher feature. */
+@SysUISingleton
class UserSwitcherViewModel
-private constructor(
+@Inject
+constructor(
private val userInteractor: UserInteractor,
private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
-) : ViewModel() {
+) {
/** On-device users. */
val users: Flow<List<UserViewModel>> =
@@ -112,34 +111,15 @@ private constructor(
}
}
- private fun createFinishRequestedFlow(): Flow<Boolean> {
- var mostRecentSelectedUserId: Int? = null
- var mostRecentIsInteractive: Boolean? = null
-
- return combine(
- // When the user is switched, we should finish.
- userInteractor.selectedUser
- .map { it.id }
- .map {
- val selectedUserChanged =
- mostRecentSelectedUserId != null && mostRecentSelectedUserId != it
- mostRecentSelectedUserId = it
- selectedUserChanged
- },
- // When the screen turns off, we should finish.
- powerInteractor.isInteractive.map {
- val screenTurnedOff = mostRecentIsInteractive == true && !it
- mostRecentIsInteractive = it
- screenTurnedOff
- },
+ private fun createFinishRequestedFlow(): Flow<Boolean> =
+ combine(
// When the cancel button is clicked, we should finish.
hasCancelButtonBeenClicked,
// If an executed action told us to finish, we should finish,
isFinishRequiredDueToExecutedAction,
- ) { selectedUserChanged, screenTurnedOff, cancelButtonClicked, executedActionFinish ->
- selectedUserChanged || screenTurnedOff || cancelButtonClicked || executedActionFinish
+ ) { cancelButtonClicked, executedActionFinish ->
+ cancelButtonClicked || executedActionFinish
}
- }
private fun toViewModel(
model: UserModel,
@@ -210,22 +190,4 @@ private constructor(
{ userInteractor.selectUser(model.id) }
}
}
-
- class Factory
- @Inject
- constructor(
- private val userInteractor: UserInteractor,
- private val guestUserInteractor: GuestUserInteractor,
- private val powerInteractor: PowerInteractor,
- ) : ViewModelProvider.Factory {
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return UserSwitcherViewModel(
- userInteractor = userInteractor,
- guestUserInteractor = guestUserInteractor,
- powerInteractor = powerInteractor,
- )
- as T
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
index 5dc04f7efa63..fb7d379c0627 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt
@@ -30,6 +30,8 @@ import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
@@ -42,7 +44,6 @@ import com.android.systemui.keyguard.data.repository.BiometricType.UNDER_DISPLAY
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -78,6 +79,8 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var dumpManager: DumpManager
@Mock private lateinit var biometricManager: BiometricManager
+ @Captor
+ private lateinit var strongAuthTracker: ArgumentCaptor<LockPatternUtils.StrongAuthTracker>
@Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback>
@Captor
private lateinit var biometricManagerCallback:
@@ -112,12 +115,12 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
devicePolicyManager = devicePolicyManager,
scope = testScope.backgroundScope,
backgroundDispatcher = testDispatcher,
- looper = testableLooper!!.looper,
- dumpManager = dumpManager,
biometricManager = biometricManager,
devicePostureRepository = devicePostureRepository,
+ dumpManager = dumpManager,
)
testScope.runCurrent()
+ verify(lockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture())
}
@Test
@@ -147,21 +150,18 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
val strongBiometricAllowed = collectLastValue(underTest.isStrongBiometricAllowed)
runCurrent()
- val captor = argumentCaptor<LockPatternUtils.StrongAuthTracker>()
- verify(lockPatternUtils).registerStrongAuthTracker(captor.capture())
-
- captor.value.stub.onStrongAuthRequiredChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
- testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ onStrongAuthChanged(STRONG_AUTH_NOT_REQUIRED, PRIMARY_USER_ID)
assertThat(strongBiometricAllowed()).isTrue()
- captor.value.stub.onStrongAuthRequiredChanged(
- STRONG_AUTH_REQUIRED_AFTER_BOOT,
- PRIMARY_USER_ID
- )
- testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_BOOT, PRIMARY_USER_ID)
assertThat(strongBiometricAllowed()).isFalse()
}
+ private fun onStrongAuthChanged(flags: Int, userId: Int) {
+ strongAuthTracker.value.stub.onStrongAuthRequiredChanged(flags, userId)
+ testableLooper?.processAllMessages() // StrongAuthTracker uses the TestableLooper
+ }
+
@Test
fun fingerprintDisabledByDpmChange() =
testScope.runTest {
@@ -351,6 +351,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(isFaceAuthSupported()).isTrue()
}
+ @Test
+ fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
+ testScope.runTest {
+ createBiometricSettingsRepository()
+
+ val isUserInLockdown = collectLastValue(underTest.isCurrentUserInLockdown)
+ // has default value.
+ assertThat(isUserInLockdown()).isFalse()
+
+ // change strong auth flags for another user.
+ // Combine with one more flag to check if we do the bitwise and
+ val inLockdown =
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
+ onStrongAuthChanged(inLockdown, ANOTHER_USER_ID)
+
+ // Still false.
+ assertThat(isUserInLockdown()).isFalse()
+
+ // change strong auth flags for current user.
+ onStrongAuthChanged(inLockdown, PRIMARY_USER_ID)
+
+ assertThat(isUserInLockdown()).isTrue()
+ }
+
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 8d74c82da6b0..adba53823d2c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.user.domain.interactor
import android.app.ActivityManager
import android.app.admin.DevicePolicyManager
-import android.content.ComponentName
import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.Bitmap
@@ -49,7 +48,6 @@ import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.user.UserSwitcherActivity
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -58,11 +56,9 @@ import com.android.systemui.user.shared.model.UserActionModel
import com.android.systemui.user.shared.model.UserModel
import com.android.systemui.user.utils.MultiUserActionsEvent
import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertNotNull
@@ -842,7 +838,7 @@ class UserInteractorTest : SysuiTestCase() {
fun `show user switcher - full screen disabled - shows dialog switcher`() =
testScope.runTest {
val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
+ underTest.showUserSwitcher(expandable)
val dialogRequest = collectLastValue(underTest.dialogShowRequests)
@@ -855,30 +851,22 @@ class UserInteractorTest : SysuiTestCase() {
}
@Test
- fun `show user switcher - full screen enabled - launches activity`() {
- featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
-
- val expandable = mock<Expandable>()
- underTest.showUserSwitcher(context, expandable)
-
- // Dialog is shown.
- val intentCaptor = argumentCaptor<Intent>()
- verify(activityStarter)
- .startActivity(
- intentCaptor.capture(),
- /* dismissShade= */ eq(true),
- /* ActivityLaunchAnimator.Controller= */ nullable(),
- /* showOverLockscreenWhenLocked= */ eq(true),
- eq(UserHandle.SYSTEM),
- )
- assertThat(intentCaptor.value.component)
- .isEqualTo(
- ComponentName(
- context,
- UserSwitcherActivity::class.java,
- )
- )
- }
+ fun `show user switcher - full screen enabled - launches full screen dialog`() =
+ testScope.runTest {
+ featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+
+ val expandable = mock<Expandable>()
+ underTest.showUserSwitcher(expandable)
+
+ val dialogRequest = collectLastValue(underTest.dialogShowRequests)
+
+ // Dialog is shown.
+ assertThat(dialogRequest())
+ .isEqualTo(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable))
+
+ underTest.onDialogShown()
+ assertThat(dialogRequest()).isNull()
+ }
@Test
fun `users - secondary user - managed profile is not included`() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 7780a4372976..a342dadcb775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -34,8 +34,6 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.power.data.repository.FakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
@@ -88,7 +86,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
- private lateinit var powerRepository: FakePowerRepository
private lateinit var testDispatcher: TestDispatcher
private lateinit var testScope: TestScope
@@ -116,7 +113,6 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
keyguardRepository = FakeKeyguardRepository()
- powerRepository = FakePowerRepository()
val refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
@@ -145,7 +141,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
set(Flags.FACE_AUTH_REFACTOR, true)
}
underTest =
- UserSwitcherViewModel.Factory(
+ UserSwitcherViewModel(
userInteractor =
UserInteractor(
applicationContext = context,
@@ -174,13 +170,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
),
- powerInteractor =
- PowerInteractor(
- repository = powerRepository,
- ),
guestUserInteractor = guestUserInteractor,
)
- .create(UserSwitcherViewModel::class.java)
}
@Test
@@ -327,46 +318,12 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
}
@Test
- fun `isFinishRequested - finishes when user is switched`() =
- testScope.runTest {
- val userInfos = setUsers(count = 2)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- userRepository.setSelectedUserInfo(userInfos[1])
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
- fun `isFinishRequested - finishes when the screen turns off`() =
- testScope.runTest {
- setUsers(count = 2)
- powerRepository.setInteractive(true)
- val isFinishRequested = mutableListOf<Boolean>()
- val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
- assertThat(isFinishRequested.last()).isFalse()
-
- powerRepository.setInteractive(false)
-
- assertThat(isFinishRequested.last()).isTrue()
-
- job.cancel()
- }
-
- @Test
fun `isFinishRequested - finishes when cancel button is clicked`() =
testScope.runTest {
setUsers(count = 2)
- powerRepository.setInteractive(true)
val isFinishRequested = mutableListOf<Boolean>()
val job =
- launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
+ launch(testDispatcher) { underTest.isFinishRequested.toList(isFinishRequested) }
assertThat(isFinishRequested.last()).isFalse()
underTest.onCancelButtonClicked()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
index d4b1701892c7..d8b3270d3aff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeBiometricSettingsRepository.kt
@@ -46,6 +46,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
get() = flowOf(true)
+ private val _isCurrentUserInLockdown = MutableStateFlow(false)
+ override val isCurrentUserInLockdown: Flow<Boolean>
+ get() = _isCurrentUserInLockdown
+
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
}
diff --git a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
new file mode 100644
index 000000000000..4b7d5bdf0002
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_DATASET_MATCH;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_FIELD_VALIDATION_FAILED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_HAS_EMPTY_REQUIRED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NONE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_SAVE_INFO;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_VALUE_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_UNKNOWN;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.IntDef;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Optional;
+
+/**
+ * Helper class to log Autofill Save event stats.
+ */
+public final class SaveEventLogger {
+ private static final String TAG = "SaveEventLogger";
+
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillSaveEventReported.SaveUiShownReason}.
+ */
+ @IntDef(prefix = {"SAVE_UI_SHOWN_REASON"}, value = {
+ SAVE_UI_SHOWN_REASON_UNKNOWN,
+ SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE,
+ SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE,
+ SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SaveUiShownReason {
+ }
+
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillSaveEventReported.SaveUiNotShownReason}.
+ */
+ @IntDef(prefix = {"SAVE_UI_NOT_SHOWN_REASON"}, value = {
+ NO_SAVE_REASON_UNKNOWN,
+ NO_SAVE_REASON_NONE,
+ NO_SAVE_REASON_NO_SAVE_INFO,
+ NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG,
+ NO_SAVE_REASON_HAS_EMPTY_REQUIRED,
+ NO_SAVE_REASON_NO_VALUE_CHANGED,
+ NO_SAVE_REASON_FIELD_VALIDATION_FAILED,
+ NO_SAVE_REASON_DATASET_MATCH
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SaveUiNotShownReason {
+ }
+
+ public static final int SAVE_UI_SHOWN_REASON_UNKNOWN =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_UNKNOWN;
+ public static final int SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE;
+ public static final int SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE;
+ public static final int SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_SHOWN_REASON__SAVE_UI_SHOWN_REASON_TRIGGER_ID_SET;
+
+ public static final int NO_SAVE_REASON_UNKNOWN =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_UNKNOWN;
+ public static final int NO_SAVE_REASON_NONE =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NONE;
+ public static final int NO_SAVE_REASON_NO_SAVE_INFO =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_SAVE_INFO;
+ public static final int NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG;
+ public static final int NO_SAVE_REASON_HAS_EMPTY_REQUIRED =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_HAS_EMPTY_REQUIRED;
+ public static final int NO_SAVE_REASON_NO_VALUE_CHANGED =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_NO_VALUE_CHANGED;
+ public static final int NO_SAVE_REASON_FIELD_VALIDATION_FAILED =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_FIELD_VALIDATION_FAILED;
+ public static final int NO_SAVE_REASON_DATASET_MATCH =
+ AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_DATASET_MATCH;
+
+ private final int mSessionId;
+ private Optional<SaveEventInternal> mEventInternal;
+
+ private SaveEventLogger(int sessionId) {
+ mSessionId = sessionId;
+ mEventInternal = Optional.of(new SaveEventInternal());
+ }
+
+ /**
+ * A factory constructor to create FillRequestEventLogger.
+ */
+ public static SaveEventLogger forSessionId(int sessionId) {
+ return new SaveEventLogger(sessionId);
+ }
+
+ /**
+ * Set request_id as long as mEventInternal presents.
+ */
+ public void maybeSetRequestId(int requestId) {
+ mEventInternal.ifPresent(event -> event.mRequestId = requestId);
+ }
+
+ /**
+ * Set app_package_uid as long as mEventInternal presents.
+ */
+ public void maybeSetAppPackageUid(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mAppPackageUid = val;
+ });
+ }
+
+ /**
+ * Set save_ui_trigger_ids as long as mEventInternal presents.
+ */
+ public void maybeSetSaveUiTriggerIds(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveUiTriggerIds = val;
+ });
+ }
+
+ /**
+ * Set flag as long as mEventInternal presents.
+ */
+ public void maybeSetFlag(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mFlag = val;
+ });
+ }
+
+ /**
+ * Set is_new_field as long as mEventInternal presents.
+ */
+ public void maybeSetIsNewField(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsNewField = val;
+ });
+ }
+
+ /**
+ * Set save_ui_shown_reason as long as mEventInternal presents.
+ */
+ public void maybeSetSaveUiShownReason(@SaveUiShownReason int reason) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveUiShownReason = reason;
+ });
+ }
+
+ /**
+ * Set save_ui_not_shown_reason as long as mEventInternal presents.
+ */
+ public void maybeSetSaveUiNotShownReason(@SaveUiNotShownReason int reason) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveUiNotShownReason = reason;
+ });
+ }
+
+ /**
+ * Set save_button_clicked as long as mEventInternal presents.
+ */
+ public void maybeSetSaveButtonClicked(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mSaveButtonClicked = val;
+ });
+ }
+
+ /**
+ * Set cancel_button_clicked as long as mEventInternal presents.
+ */
+ public void maybeSetCancelButtonClicked(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mCancelButtonClicked = val;
+ });
+ }
+
+ /**
+ * Set dialog_dismissed as long as mEventInternal presents.
+ */
+ public void maybeSetDialogDismissed(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mDialogDismissed = val;
+ });
+ }
+
+ /**
+ * Set is_saved as long as mEventInternal presents.
+ */
+ public void maybeSetIsSaved(boolean val) {
+ mEventInternal.ifPresent(event -> {
+ event.mIsSaved = val;
+ });
+ }
+
+ /**
+ * Set latency_save_ui_display_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencySaveUiDisplayMillis(long timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencySaveUiDisplayMillis = timestamp;
+ });
+ }
+
+ /**
+ * Set latency_save_request_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencySaveRequestMillis(long timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencySaveRequestMillis = timestamp;
+ });
+ }
+
+ /**
+ * Set latency_save_finish_millis as long as mEventInternal presents.
+ */
+ public void maybeSetLatencySaveFinishMillis(long timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mLatencySaveFinishMillis = timestamp;
+ });
+ }
+
+ /**
+ * Log an AUTOFILL_SAVE_EVENT_REPORTED event.
+ */
+ public void logAndEndEvent() {
+ if (!mEventInternal.isPresent()) {
+ Slog.w(TAG, "Shouldn't be logging AutofillSaveEventReported again for same "
+ + "event");
+ return;
+ }
+ SaveEventInternal event = mEventInternal.get();
+ if (sVerbose) {
+ Slog.v(TAG, "Log AutofillSaveEventReported:"
+ + " requestId=" + event.mRequestId
+ + " sessionId=" + mSessionId
+ + " mAppPackageUid=" + event.mAppPackageUid
+ + " mSaveUiTriggerIds=" + event.mSaveUiTriggerIds
+ + " mFlag=" + event.mFlag
+ + " mIsNewField=" + event.mIsNewField
+ + " mSaveUiShownReason=" + event.mSaveUiShownReason
+ + " mSaveUiNotShownReason=" + event.mSaveUiNotShownReason
+ + " mSaveButtonClicked=" + event.mSaveButtonClicked
+ + " mCancelButtonClicked=" + event.mCancelButtonClicked
+ + " mDialogDismissed=" + event.mDialogDismissed
+ + " mIsSaved=" + event.mIsSaved
+ + " mLatencySaveUiDisplayMillis=" + event.mLatencySaveUiDisplayMillis
+ + " mLatencySaveRequestMillis=" + event.mLatencySaveRequestMillis
+ + " mLatencySaveFinishMillis=" + event.mLatencySaveFinishMillis);
+ }
+ FrameworkStatsLog.write(
+ AUTOFILL_SAVE_EVENT_REPORTED,
+ event.mRequestId,
+ mSessionId,
+ event.mAppPackageUid,
+ event.mSaveUiTriggerIds,
+ event.mFlag,
+ event.mIsNewField,
+ event.mSaveUiShownReason,
+ event.mSaveUiNotShownReason,
+ event.mSaveButtonClicked,
+ event.mCancelButtonClicked,
+ event.mDialogDismissed,
+ event.mIsSaved,
+ event.mLatencySaveUiDisplayMillis,
+ event.mLatencySaveRequestMillis,
+ event.mLatencySaveFinishMillis);
+ mEventInternal = Optional.empty();
+ }
+
+ private static final class SaveEventInternal {
+ int mRequestId;
+ int mAppPackageUid = -1;
+ int mSaveUiTriggerIds = -1;
+ long mFlag = -1;
+ boolean mIsNewField = false;
+ int mSaveUiShownReason = SAVE_UI_SHOWN_REASON_UNKNOWN;
+ int mSaveUiNotShownReason = NO_SAVE_REASON_UNKNOWN;
+ boolean mSaveButtonClicked = false;
+ boolean mCancelButtonClicked = false;
+ boolean mDialogDismissed = false;
+ boolean mIsSaved = false;
+ long mLatencySaveUiDisplayMillis = 0;
+ long mLatencySaveRequestMillis = 0;
+ long mLatencySaveFinishMillis = 0;
+
+ SaveEventInternal() {
+ }
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
new file mode 100644
index 000000000000..92d72ac828f3
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_ACTIVITY_FINISHED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_UNKNOWN;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CLICKED;
+import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_COMMITTED;
+import static com.android.server.autofill.Helper.sVerbose;
+
+import android.annotation.IntDef;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Optional;
+
+/**
+ * Helper class to log Autofill session committed event stats.
+ */
+public final class SessionCommittedEventLogger {
+ private static final String TAG = "SessionCommittedEventLogger";
+
+ /**
+ * Reasons why presentation was not shown. These are wrappers around
+ * {@link com.android.os.AtomsProto.AutofillSessionCommitted.AutofillCommitReason}.
+ */
+ @IntDef(prefix = {"COMMIT_REASON"}, value = {
+ COMMIT_REASON_UNKNOWN,
+ COMMIT_REASON_ACTIVITY_FINISHED,
+ COMMIT_REASON_VIEW_COMMITTED,
+ COMMIT_REASON_VIEW_CLICKED,
+ COMMIT_REASON_VIEW_CHANGED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CommitReason {
+ }
+
+ public static final int COMMIT_REASON_UNKNOWN =
+ AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_UNKNOWN;
+ public static final int COMMIT_REASON_ACTIVITY_FINISHED =
+ AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_ACTIVITY_FINISHED;
+ public static final int COMMIT_REASON_VIEW_COMMITTED =
+ AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_COMMITTED;
+ public static final int COMMIT_REASON_VIEW_CLICKED =
+ AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CLICKED;
+ public static final int COMMIT_REASON_VIEW_CHANGED =
+ AUTOFILL_SESSION_COMMITTED__COMMIT_REASON__COMMIT_REASON_VIEW_CHANGED;
+
+ private final int mSessionId;
+ private Optional<SessionCommittedEventInternal> mEventInternal;
+
+ private SessionCommittedEventLogger(int sessionId) {
+ mSessionId = sessionId;
+ mEventInternal = Optional.of(new SessionCommittedEventInternal());
+ }
+
+ /**
+ * A factory constructor to create SessionCommittedEventLogger.
+ */
+ public static SessionCommittedEventLogger forSessionId(int sessionId) {
+ return new SessionCommittedEventLogger(sessionId);
+ }
+
+ /**
+ * Set component_package_uid as long as mEventInternal presents.
+ */
+ public void maybeSetComponentPackageUid(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mComponentPackageUid = val;
+ });
+ }
+
+ /**
+ * Set request_count as long as mEventInternal presents.
+ */
+ public void maybeSetRequestCount(int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mRequestCount = val;
+ });
+ }
+
+ /**
+ * Set commit_reason as long as mEventInternal presents.
+ */
+ public void maybeSetCommitReason(@CommitReason int val) {
+ mEventInternal.ifPresent(event -> {
+ event.mCommitReason = val;
+ });
+ }
+
+ /**
+ * Set session_duration_millis as long as mEventInternal presents.
+ */
+ public void maybeSetSessionDurationMillis(long timestamp) {
+ mEventInternal.ifPresent(event -> {
+ event.mSessionDurationMillis = timestamp;
+ });
+ }
+
+ /**
+ * Log an AUTOFILL_SESSION_COMMITTED event.
+ */
+ public void logAndEndEvent() {
+ if (!mEventInternal.isPresent()) {
+ Slog.w(TAG, "Shouldn't be logging AutofillSessionCommitted again for same session.");
+ return;
+ }
+ SessionCommittedEventInternal event = mEventInternal.get();
+ if (sVerbose) {
+ Slog.v(TAG, "Log AutofillSessionCommitted:"
+ + " sessionId=" + mSessionId
+ + " mComponentPackageUid=" + event.mComponentPackageUid
+ + " mRequestCount=" + event.mRequestCount
+ + " mCommitReason=" + event.mCommitReason
+ + " mSessionDurationMillis=" + event.mSessionDurationMillis);
+ }
+ FrameworkStatsLog.write(
+ AUTOFILL_SESSION_COMMITTED,
+ mSessionId,
+ event.mComponentPackageUid,
+ event.mRequestCount,
+ event.mCommitReason,
+ event.mSessionDurationMillis);
+ mEventInternal = Optional.empty();
+ }
+
+ private static final class SessionCommittedEventInternal {
+ int mComponentPackageUid = -1;
+ int mRequestCount = 0;
+ int mCommitReason = COMMIT_REASON_UNKNOWN;
+ long mSessionDurationMillis = 0;
+
+ SessionCommittedEventInternal() {
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 6bd3c7953e01..e6ef3b468d2d 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -999,23 +999,50 @@ final class BroadcastRecord extends Binder {
private static boolean matchesDeliveryGroup(@NonNull BroadcastRecord newRecord,
@NonNull BroadcastRecord oldRecord) {
- final String newMatchingKey = getDeliveryGroupMatchingKey(newRecord);
- final String oldMatchingKey = getDeliveryGroupMatchingKey(oldRecord);
final IntentFilter newMatchingFilter = getDeliveryGroupMatchingFilter(newRecord);
// If neither delivery group key nor matching filter is specified, then use
// Intent.filterEquals() to identify the delivery group.
- if (newMatchingKey == null && oldMatchingKey == null && newMatchingFilter == null) {
+ if (isMatchingKeyNull(newRecord) && isMatchingKeyNull(oldRecord)
+ && newMatchingFilter == null) {
return newRecord.intent.filterEquals(oldRecord.intent);
}
if (newMatchingFilter != null && !newMatchingFilter.asPredicate().test(oldRecord.intent)) {
return false;
}
- return Objects.equals(newMatchingKey, oldMatchingKey);
+ return areMatchingKeysEqual(newRecord, oldRecord);
+ }
+
+ private static boolean isMatchingKeyNull(@NonNull BroadcastRecord record) {
+ final String namespace = getDeliveryGroupMatchingNamespaceFragment(record);
+ final String key = getDeliveryGroupMatchingKeyFragment(record);
+ // If either namespace or key part is null, then treat the entire matching key as null.
+ return namespace == null || key == null;
+ }
+
+ private static boolean areMatchingKeysEqual(@NonNull BroadcastRecord newRecord,
+ @NonNull BroadcastRecord oldRecord) {
+ final String newNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(newRecord);
+ final String oldNamespaceFragment = getDeliveryGroupMatchingNamespaceFragment(oldRecord);
+ if (!Objects.equals(newNamespaceFragment, oldNamespaceFragment)) {
+ return false;
+ }
+
+ final String newKeyFragment = getDeliveryGroupMatchingKeyFragment(newRecord);
+ final String oldKeyFragment = getDeliveryGroupMatchingKeyFragment(oldRecord);
+ return Objects.equals(newKeyFragment, oldKeyFragment);
+ }
+
+ @Nullable
+ private static String getDeliveryGroupMatchingNamespaceFragment(
+ @NonNull BroadcastRecord record) {
+ return record.options == null
+ ? null : record.options.getDeliveryGroupMatchingNamespaceFragment();
}
@Nullable
- private static String getDeliveryGroupMatchingKey(@NonNull BroadcastRecord record) {
- return record.options == null ? null : record.options.getDeliveryGroupMatchingKey();
+ private static String getDeliveryGroupMatchingKeyFragment(@NonNull BroadcastRecord record) {
+ return record.options == null
+ ? null : record.options.getDeliveryGroupMatchingKeyFragment();
}
@Nullable
diff --git a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
index 2be2ef8c35af..7a70db22106e 100644
--- a/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
+++ b/services/core/java/com/android/server/locales/AppLocaleChangedAtomRecord.java
@@ -20,24 +20,32 @@ import static android.os.Process.INVALID_UID;
import com.android.internal.util.FrameworkStatsLog;
+import java.util.Locale;
+
/**
* Holds data used to report the ApplicationLocalesChanged atom.
*/
public final class AppLocaleChangedAtomRecord {
+ private static final String DEFAULT_PREFIX = "default-";
final int mCallingUid;
int mTargetUid = INVALID_UID;
- String mNewLocales = "";
- String mPrevLocales = "";
+ String mNewLocales = DEFAULT_PREFIX;
+ String mPrevLocales = DEFAULT_PREFIX;
int mStatus = FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__STATUS__STATUS_UNSPECIFIED;
int mCaller = FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_UNKNOWN;
AppLocaleChangedAtomRecord(int callingUid) {
this.mCallingUid = callingUid;
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale != null) {
+ this.mNewLocales = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ this.mPrevLocales = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ }
}
void setNewLocales(String newLocales) {
- this.mNewLocales = newLocales;
+ this.mNewLocales = convertEmptyLocales(newLocales);
}
void setTargetUid(int targetUid) {
@@ -45,7 +53,7 @@ public final class AppLocaleChangedAtomRecord {
}
void setPrevLocales(String prevLocales) {
- this.mPrevLocales = prevLocales;
+ this.mPrevLocales = convertEmptyLocales(prevLocales);
}
void setStatus(int status) {
@@ -55,4 +63,16 @@ public final class AppLocaleChangedAtomRecord {
void setCaller(int caller) {
this.mCaller = caller;
}
+
+ private String convertEmptyLocales(String locales) {
+ String target = locales;
+ if ("".equals(locales)) {
+ Locale defaultLocale = Locale.getDefault();
+ if (defaultLocale != null) {
+ target = DEFAULT_PREFIX + defaultLocale.toLanguageTag();
+ }
+ }
+
+ return target;
+ }
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 6cd2ed41e94c..0049213cbf55 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -44,6 +44,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -377,7 +378,8 @@ class LocaleManagerBackupHelper {
// Restore the locale immediately
try {
mLocaleManagerService.setApplicationLocales(pkgName, userId,
- LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate);
+ LocaleList.forLanguageTags(localesInfo.mLocales), localesInfo.mSetFromDelegate,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
if (DEBUG) {
Slog.d(TAG, "Restored locales=" + localesInfo.mLocales + " fromDelegate="
+ localesInfo.mSetFromDelegate + " for package=" + pkgName);
@@ -662,7 +664,9 @@ class LocaleManagerBackupHelper {
try {
LocaleConfig localeConfig = new LocaleConfig(
mContext.createPackageContextAsUser(packageName, 0, UserHandle.of(userId)));
- mLocaleManagerService.removeUnsupportedAppLocales(packageName, userId, localeConfig);
+ mLocaleManagerService.removeUnsupportedAppLocales(packageName, userId, localeConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APP_UPDATE_LOCALES_CHANGE);
} catch (PackageManager.NameNotFoundException e) {
Slog.e(TAG, "Can not found the package name : " + packageName + " / " + e);
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index e3a555bd2f6a..43e346a5bfa3 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -182,8 +182,11 @@ public class LocaleManagerService extends SystemService {
@Override
public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
@NonNull LocaleList locales, boolean fromDelegate) throws RemoteException {
+ int caller = fromDelegate
+ ? FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DELEGATE
+ : FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS;
LocaleManagerService.this.setApplicationLocales(appPackageName, userId, locales,
- fromDelegate);
+ fromDelegate, caller);
}
@Override
@@ -226,13 +229,14 @@ public class LocaleManagerService extends SystemService {
* Sets the current UI locales for a specified app.
*/
public void setApplicationLocales(@NonNull String appPackageName, @UserIdInt int userId,
- @NonNull LocaleList locales, boolean fromDelegate)
+ @NonNull LocaleList locales, boolean fromDelegate, int caller)
throws RemoteException, IllegalArgumentException {
AppLocaleChangedAtomRecord atomRecordForMetrics = new
AppLocaleChangedAtomRecord(Binder.getCallingUid());
try {
requireNonNull(appPackageName);
requireNonNull(locales);
+ atomRecordForMetrics.setCaller(caller);
atomRecordForMetrics.setNewLocales(locales.toLanguageTags());
//Allow apps with INTERACT_ACROSS_USERS permission to set locales for different user.
userId = mActivityManagerInternal.handleIncomingUser(
@@ -273,8 +277,8 @@ public class LocaleManagerService extends SystemService {
+ " and user " + userId);
}
- atomRecordForMetrics.setPrevLocales(getApplicationLocalesUnchecked(appPackageName, userId)
- .toLanguageTags());
+ atomRecordForMetrics.setPrevLocales(
+ getApplicationLocalesUnchecked(appPackageName, userId).toLanguageTags());
final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
userId);
@@ -619,7 +623,10 @@ public class LocaleManagerService extends SystemService {
Slog.d(TAG, "remove the override LocaleConfig");
file.delete();
}
- removeUnsupportedAppLocales(appPackageName, userId, resLocaleConfig);
+ removeUnsupportedAppLocales(appPackageName, userId, resLocaleConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DYNAMIC_LOCALES_CHANGE
+ );
atomRecord.setOverrideRemoved(true);
atomRecord.setStatus(FrameworkStatsLog
.APP_SUPPORTED_LOCALES_CHANGED__STATUS__SUCCESS);
@@ -661,7 +668,10 @@ public class LocaleManagerService extends SystemService {
}
atomicFile.finishWrite(stream);
// Clear per-app locales if they are not in the override LocaleConfig.
- removeUnsupportedAppLocales(appPackageName, userId, overrideLocaleConfig);
+ removeUnsupportedAppLocales(appPackageName, userId, overrideLocaleConfig,
+ FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DYNAMIC_LOCALES_CHANGE
+ );
if (overrideLocaleConfig.isSameLocaleConfig(resLocaleConfig)) {
Slog.d(TAG, "setOverrideLocaleConfig, same as the app's LocaleConfig");
atomRecord.setSameAsResConfig(true);
@@ -678,9 +688,12 @@ public class LocaleManagerService extends SystemService {
/**
* Checks if the per-app locales are in the LocaleConfig. Per-app locales missing from the
* LocaleConfig will be removed.
+ *
+ * <p><b>Note:</b> Check whether to remove the per-app locales when the app is upgraded or
+ * the LocaleConfig is overridden.
*/
void removeUnsupportedAppLocales(String appPackageName, int userId,
- LocaleConfig localeConfig) {
+ LocaleConfig localeConfig, int caller) {
LocaleList appLocales = getApplicationLocalesUnchecked(appPackageName, userId);
// Remove the per-app locales from the locale list if they don't exist in the LocaleConfig.
boolean resetAppLocales = false;
@@ -707,7 +720,7 @@ public class LocaleManagerService extends SystemService {
try {
setApplicationLocales(appPackageName, userId,
new LocaleList(newAppLocales.toArray(locales)),
- mBackupHelper.areLocalesSetFromDelegate(userId, appPackageName));
+ mBackupHelper.areLocalesSetFromDelegate(userId, appPackageName), caller);
} catch (RemoteException | IllegalArgumentException e) {
Slog.e(TAG, "Could not set locales for " + appPackageName, e);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2ea59b3fd6fd..6944b2918d84 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -5266,10 +5266,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTransitionController.collect(this);
} else {
inFinishingTransition = mTransitionController.inFinishingTransition(this);
- if (!inFinishingTransition) {
+ if (!inFinishingTransition && !mDisplayContent.isSleeping()) {
Slog.e(TAG, "setVisibility=" + visible
+ " while transition is not collecting or finishing "
+ this + " caller=" + Debug.getCallers(8));
+ // Force showing the parents because they may be hidden by previous transition.
+ if (visible) {
+ final Transaction t = getSyncTransaction();
+ for (WindowContainer<?> p = getParent(); p != null && p != mDisplayContent;
+ p = p.getParent()) {
+ if (p.mSurfaceControl != null) {
+ t.show(p.mSurfaceControl);
+ }
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index fb79c0677a2f..0d1f2ce8d63f 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -32,6 +32,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.content.res.ResourceId;
import android.graphics.Point;
import android.graphics.Rect;
@@ -75,7 +76,7 @@ class BackNavigationController {
private Runnable mPendingAnimation;
private final NavigationMonitor mNavigationMonitor = new NavigationMonitor();
- private AnimationHandler mAnimationHandler;
+ AnimationHandler mAnimationHandler;
private final ArrayList<WindowContainer> mTmpOpenApps = new ArrayList<>();
private final ArrayList<WindowContainer> mTmpCloseApps = new ArrayList<>();
@@ -651,7 +652,8 @@ class BackNavigationController {
/**
* Create and handling animations status for an open/close animation targets.
*/
- private static class AnimationHandler {
+ static class AnimationHandler {
+ private final boolean mShowWindowlessSurface;
private final WindowManagerService mWindowManagerService;
private BackWindowAnimationAdaptor mCloseAdaptor;
private BackWindowAnimationAdaptor mOpenAdaptor;
@@ -670,19 +672,37 @@ class BackNavigationController {
AnimationHandler(WindowManagerService wms) {
mWindowManagerService = wms;
+ final Context context = wms.mContext;
+ mShowWindowlessSurface = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_predictShowStartingSurface);
}
private static final int UNKNOWN = 0;
private static final int TASK_SWITCH = 1;
private static final int ACTIVITY_SWITCH = 2;
+ private static boolean isActivitySwitch(WindowContainer close, WindowContainer open) {
+ if (close.asActivityRecord() == null || open.asActivityRecord() == null
+ || (close.asActivityRecord().getTask()
+ != open.asActivityRecord().getTask())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isTaskSwitch(WindowContainer close, WindowContainer open) {
+ if (close.asTask() == null || open.asTask() == null
+ || (close.asTask() == open.asTask())) {
+ return false;
+ }
+ return true;
+ }
+
private void initiate(WindowContainer close, WindowContainer open) {
WindowContainer closeTarget;
- if (close.asActivityRecord() != null && open.asActivityRecord() != null
- && (close.asActivityRecord().getTask() == open.asActivityRecord().getTask())) {
+ if (isActivitySwitch(close, open)) {
mSwitchType = ACTIVITY_SWITCH;
closeTarget = close.asActivityRecord();
- } else if (close.asTask() != null && open.asTask() != null
- && close.asTask() != open.asTask()) {
+ } else if (isTaskSwitch(close, open)) {
mSwitchType = TASK_SWITCH;
closeTarget = close.asTask().getTopNonFinishingActivity();
} else {
@@ -699,7 +719,8 @@ class BackNavigationController {
}
}
- boolean composeAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) {
+ private boolean composeAnimations(@NonNull WindowContainer close,
+ @NonNull WindowContainer open) {
clearBackAnimateTarget(null /* cleanupTransaction */);
if (close == null || open == null) {
Slog.e(TAG, "reset animation with null target close: "
@@ -757,15 +778,15 @@ class BackNavigationController {
}
boolean isTarget(WindowContainer wc, boolean open) {
- if (open) {
- return wc == mOpenAdaptor.mTarget || mOpenAdaptor.mTarget.hasChild(wc);
+ if (!mComposed) {
+ return false;
}
-
+ final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
if (mSwitchType == TASK_SWITCH) {
- return wc == mCloseAdaptor.mTarget
- || (wc.asTask() != null && wc.hasChild(mCloseAdaptor.mTarget));
+ return wc == target
+ || (wc.asTask() != null && wc.hasChild(target));
} else if (mSwitchType == ACTIVITY_SWITCH) {
- return wc == mCloseAdaptor.mTarget;
+ return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
}
return false;
}
@@ -983,21 +1004,20 @@ class BackNavigationController {
case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
return new ScheduleAnimationBuilder(backType, adapter)
.setComposeTarget(currentActivity, previousActivity)
- .setOpeningSnapshot(getActivitySnapshot(previousActivity));
+ .setIsLaunchBehind(false);
case BackNavigationInfo.TYPE_CROSS_TASK:
return new ScheduleAnimationBuilder(backType, adapter)
.setComposeTarget(currentTask, previousTask)
- .setOpeningSnapshot(getTaskSnapshot(previousTask));
+ .setIsLaunchBehind(false);
}
return null;
}
- private class ScheduleAnimationBuilder {
+ class ScheduleAnimationBuilder {
final int mType;
final BackAnimationAdapter mBackAnimationAdapter;
WindowContainer mCloseTarget;
WindowContainer mOpenTarget;
- TaskSnapshot mOpenSnapshot;
boolean mIsLaunchBehind;
ScheduleAnimationBuilder(int type, BackAnimationAdapter backAnimationAdapter) {
@@ -1011,11 +1031,6 @@ class BackNavigationController {
return this;
}
- ScheduleAnimationBuilder setOpeningSnapshot(TaskSnapshot snapshot) {
- mOpenSnapshot = snapshot;
- return this;
- }
-
ScheduleAnimationBuilder setIsLaunchBehind(boolean launchBehind) {
mIsLaunchBehind = launchBehind;
return this;
@@ -1026,17 +1041,32 @@ class BackNavigationController {
|| wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget);
}
+ /**
+ * Apply preview strategy on the opening target
+ * @param open The opening target.
+ * @param visibleOpenActivity The visible activity in opening target.
+ * @return If the preview strategy is launch behind, returns the Activity that has
+ * launchBehind set, or null otherwise.
+ */
+ private ActivityRecord applyPreviewStrategy(WindowContainer open,
+ ActivityRecord visibleOpenActivity) {
+ if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) {
+ createStartingSurface(getSnapshot(open));
+ return null;
+ }
+ setLaunchBehind(visibleOpenActivity);
+ return visibleOpenActivity;
+ }
+
Runnable build() {
if (mOpenTarget == null || mCloseTarget == null) {
return null;
}
- final boolean shouldLaunchBehind = mIsLaunchBehind || !isSupportWindowlessSurface();
- final ActivityRecord launchBehindActivity = !shouldLaunchBehind ? null
- : mOpenTarget.asTask() != null
+ final ActivityRecord openActivity = mOpenTarget.asTask() != null
? mOpenTarget.asTask().getTopNonFinishingActivity()
: mOpenTarget.asActivityRecord() != null
? mOpenTarget.asActivityRecord() : null;
- if (shouldLaunchBehind && launchBehindActivity == null) {
+ if (openActivity == null) {
Slog.e(TAG, "No opening activity");
return null;
}
@@ -1044,11 +1074,8 @@ class BackNavigationController {
if (!composeAnimations(mCloseTarget, mOpenTarget)) {
return null;
}
- if (launchBehindActivity != null) {
- setLaunchBehind(launchBehindActivity);
- } else {
- createStartingSurface(mOpenSnapshot);
- }
+ final ActivityRecord launchBehindActivity =
+ applyPreviewStrategy(mOpenTarget, openActivity);
final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback(
launchBehindActivity != null ? triggerBack -> {
@@ -1171,25 +1198,22 @@ class BackNavigationController {
mPendingAnimationBuilder = null;
}
- private static TaskSnapshot getActivitySnapshot(@NonNull ActivityRecord r) {
+ static TaskSnapshot getSnapshot(@NonNull WindowContainer w) {
if (!isScreenshotEnabled()) {
return null;
}
- // Check if we have a screenshot of the previous activity, indexed by its
- // component name.
- // TODO return TaskSnapshot when feature complete.
-// final HardwareBuffer hw = r.getTask().getSnapshotForActivityRecord(r);
- return null;
- }
+ if (w.asTask() != null) {
+ final Task task = w.asTask();
+ return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
+ task.mTaskId, task.mUserId, false /* restoreFromDisk */,
+ false /* isLowResolution */);
+ }
- private static TaskSnapshot getTaskSnapshot(Task task) {
- if (!isScreenshotEnabled()) {
+ if (w.asActivityRecord() != null) {
+ // TODO (b/259497289) return TaskSnapshot when feature complete.
return null;
}
- // Don't read from disk!!
- return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot(
- task.mTaskId, task.mUserId, false /* restoreFromDisk */,
- false /* isLowResolution */);
+ return null;
}
void setWindowManager(WindowManagerService wm) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 2f5634362e68..07daa4b22ac9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2349,12 +2349,23 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
// Prepare transition before resume top activity, so it can be collected.
- if (!displayShouldSleep && display.isDefaultDisplay
- && !display.getDisplayPolicy().isAwake()
- && display.mTransitionController.isShellTransitionsEnabled()
+ if (!displayShouldSleep && display.mTransitionController.isShellTransitionsEnabled()
&& !display.mTransitionController.isCollecting()) {
- display.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
- 0 /* flags */, null /* trigger */, display);
+ int transit = TRANSIT_NONE;
+ if (!display.getDisplayPolicy().isAwake()) {
+ // Note that currently this only happens on default display because non-default
+ // display is always awake.
+ transit = TRANSIT_WAKE;
+ } else if (display.isKeyguardOccluded()) {
+ // The display was awake so this is resuming activity for occluding keyguard.
+ transit = WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
+ }
+ if (transit != TRANSIT_NONE) {
+ display.mTransitionController.requestStartTransition(
+ display.mTransitionController.createTransition(transit),
+ null /* startTask */, null /* remoteTransition */,
+ null /* displayChange */);
+ }
}
// Set the sleeping state of the root tasks on the display.
display.forAllRootTasks(rootTask -> {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2cfd2af89c80..8fa7592e6da0 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2363,6 +2363,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (isTranslucent(wc)) {
flags |= FLAG_TRANSLUCENT;
}
+ if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
+ flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
+ }
final Task task = wc.asTask();
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
@@ -2371,19 +2374,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
&& topActivity.mStartingData.hasImeSurface()) {
flags |= FLAG_WILL_IME_SHOWN;
}
- if (topActivity.mAtmService.mBackNavigationController
- .isMonitorTransitionTarget(topActivity)) {
- flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
- }
- if (topActivity != null && topActivity.mLaunchTaskBehind) {
+ if (topActivity.mLaunchTaskBehind) {
Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
flags |= FLAG_TASK_LAUNCHING_BEHIND;
}
- } else {
- if (task.mAtmService.mBackNavigationController
- .isMonitorTransitionTarget(task)) {
- flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
- }
}
if (task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
@@ -2397,10 +2391,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
flags |= FLAG_IS_VOICE_INTERACTION;
}
flags |= record.mTransitionChangeFlags;
- if (record.mAtmService.mBackNavigationController
- .isMonitorTransitionTarget(record)) {
- flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
- }
}
final TaskFragment taskFragment = wc.asTaskFragment();
if (taskFragment != null && task == null) {
diff --git a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
index 9f9e6a310d0b..d8139623f393 100644
--- a/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
+++ b/services/tests/PackageManagerServiceTests/host/src/com/android/server/pm/test/SdCardEjectionTests.kt
@@ -57,7 +57,8 @@ class SdCardEjectionTests : BaseHostJUnit4Test() {
@Parameterized.Parameters(name = "reboot={0}")
@JvmStatic
- fun parameters() = arrayOf(false, true)
+ // TODO(b/275403538): re-enable non-reboot scenarios with better tracking of APK removal
+ fun parameters() = arrayOf(/*false, */true)
data class Volume(
val diskId: String,
@@ -200,15 +201,6 @@ class SdCardEjectionTests : BaseHostJUnit4Test() {
// TODO: There must be a better way to prevent it from auto-mounting.
removeVirtualDisk()
device.reboot()
- } else {
- // Because PackageManager unmount scan is asynchronous, need to retry until the package
- // has been unloaded. This only has to be done in the non-reboot case. Reboot will
- // clear the data structure by its nature.
- retryUntilSuccess {
- // The compiler section will print the state of the physical APK
- HostUtils.packageSection(device, pkgName, sectionName = "Compiler stats")
- .any { it.contains("Unable to find package: $pkgName") }
- }
}
}
diff --git a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
index 1cc7ccc01283..5cc3371a1a6e 100644
--- a/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
+++ b/services/tests/PackageManagerServiceTests/host/test-apps/Generic/Android.bp
@@ -24,30 +24,45 @@ package {
android_test_helper_app {
name: "PackageManagerTestAppStub",
manifest: "AndroidManifestVersion1.xml",
- srcs: []
+ srcs: [],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion1",
- manifest: "AndroidManifestVersion1.xml"
+ manifest: "AndroidManifestVersion1.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion2",
- manifest: "AndroidManifestVersion2.xml"
+ manifest: "AndroidManifestVersion2.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion3",
- manifest: "AndroidManifestVersion3.xml"
+ manifest: "AndroidManifestVersion3.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppVersion4",
- manifest: "AndroidManifestVersion4.xml"
+ manifest: "AndroidManifestVersion4.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
android_test_helper_app {
name: "PackageManagerTestAppOriginalOverride",
- manifest: "AndroidManifestOriginalOverride.xml"
+ manifest: "AndroidManifestOriginalOverride.xml",
+ srcs: [
+ "src/**/*.kt",
+ ],
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 390119c968cd..36d191b466ba 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -1008,6 +1008,42 @@ public final class BroadcastQueueModernImplTest {
dropboxEntryBroadcast2.first, expectedMergedBroadcast.first));
}
+ @Test
+ public void testDeliveryGroupPolicy_sameAction_differentMatchingCriteria() {
+ final Intent closeSystemDialogs1 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+ final BroadcastOptions optionsCloseSystemDialog1 = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
+
+ final Intent closeSystemDialogs2 = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+ .putExtra("reason", "testing");
+ final BroadcastOptions optionsCloseSystemDialog2 = BroadcastOptions.makeBasic()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing");
+
+ // Halt all processing so that we get a consistent view
+ mHandlerThread.getLooper().getQueue().postSyncBarrier();
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ // Verify that only the older broadcast with no extras was removed.
+ final BroadcastProcessQueue queue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ verifyPendingRecords(queue, List.of(closeSystemDialogs2, closeSystemDialogs1));
+
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs1, optionsCloseSystemDialog1));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
+ closeSystemDialogs2, optionsCloseSystemDialog2));
+ // Verify that only the older broadcast with no extras was removed.
+ verifyPendingRecords(queue, List.of(closeSystemDialogs1, closeSystemDialogs2));
+ }
+
private Pair<Intent, BroadcastOptions> createDropboxBroadcast(String tag, long timestampMs,
int droppedCount) {
final Intent dropboxEntryAdded = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 13371cce5fb5..40ecaf1770a9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -54,6 +54,7 @@ import android.util.Xml;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -264,7 +265,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
}
@@ -280,7 +282,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, false);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
@@ -303,7 +306,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
- DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+ DEFAULT_USER_ID, DEFAULT_LOCALES, true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
@@ -327,7 +331,8 @@ public class LocaleManagerBackupRestoreTest {
// Locales were restored
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
- DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true);
+ DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID, DEFAULT_LOCALES, true,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
checkStageDataDoesNotExist(DEFAULT_USER_ID);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, true,
@@ -369,7 +374,8 @@ public class LocaleManagerBackupRestoreTest {
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsA), true);
+ LocaleList.forLanguageTags(langTagsA), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
pkgLocalesMap.remove(pkgNameA);
@@ -422,11 +428,12 @@ public class LocaleManagerBackupRestoreTest {
// Restore locales only for myAppB.
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameA), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsB), true);
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameC), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
// App C is staged.
pkgLocalesMap.remove(pkgNameA);
@@ -484,7 +491,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsA), false);
+ LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
@@ -499,7 +507,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
- LocaleList.forLanguageTags(langTagsB), true);
+ LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
@@ -606,7 +615,8 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameA, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(
- pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false);
+ pkgNameA, DEFAULT_USER_ID, LocaleList.forLanguageTags(langTagsA), false,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
pkgLocalesMap.remove(pkgNameA);
@@ -620,7 +630,7 @@ public class LocaleManagerBackupRestoreTest {
mPackageMonitor.onPackageAdded(pkgNameB, DEFAULT_UID);
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(eq(pkgNameB), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
checkStageDataDoesNotExist(DEFAULT_USER_ID);
}
@@ -734,7 +744,7 @@ public class LocaleManagerBackupRestoreTest {
*/
private void verifyNothingRestored() throws Exception {
verify(mMockLocaleManagerService, times(0)).setApplicationLocales(anyString(), anyInt(),
- any(), anyBoolean());
+ any(), anyBoolean(), anyInt());
}
private static void verifyPayloadForAppLocales(Map<String, LocalesInfo> expectedPkgLocalesMap,
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 07fda309f03e..550204b99323 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -52,6 +52,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal.PackageConfig;
@@ -136,7 +137,8 @@ public class LocaleManagerServiceTest {
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- LocaleList.getEmptyLocaleList(), false);
+ LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected SecurityException");
} finally {
verify(mMockContext).enforceCallingOrSelfPermission(
@@ -151,7 +153,8 @@ public class LocaleManagerServiceTest {
public void testSetApplicationLocales_nullPackageName_fails() throws Exception {
try {
mLocaleManagerService.setApplicationLocales(/* appPackageName = */ null,
- DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false);
+ DEFAULT_USER_ID, LocaleList.getEmptyLocaleList(), false,
+ FrameworkStatsLog.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected NullPointerException");
} finally {
verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -165,7 +168,8 @@ public class LocaleManagerServiceTest {
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- /* locales = */ null, false);
+ /* locales = */ null, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected NullPointerException");
} finally {
verify(mMockBackupHelper, times(0)).notifyBackupManager();
@@ -183,7 +187,8 @@ public class LocaleManagerServiceTest {
setUpPassingPermissionCheckFor(Manifest.permission.CHANGE_CONFIGURATION);
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- DEFAULT_LOCALES, true);
+ DEFAULT_LOCALES, true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_DELEGATE);
assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -196,7 +201,8 @@ public class LocaleManagerServiceTest {
.when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- DEFAULT_LOCALES, false);
+ DEFAULT_LOCALES, false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
verify(mMockBackupHelper, times(1)).notifyBackupManager();
@@ -208,7 +214,8 @@ public class LocaleManagerServiceTest {
.when(mMockPackageManager).getPackageUidAsUser(anyString(), any(), anyInt());
try {
mLocaleManagerService.setApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID,
- LocaleList.getEmptyLocaleList(), false);
+ LocaleList.getEmptyLocaleList(), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_APPS);
fail("Expected IllegalArgumentException");
} finally {
assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
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 b80c3e84198b..d0628f12336c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -26,25 +26,31 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.window.BackNavigationInfo.typeToString;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -58,6 +64,7 @@ import android.window.IOnBackInvokedCallback;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.OnBackInvokedDispatcher;
+import android.window.TaskSnapshot;
import android.window.WindowOnBackInvokedDispatcher;
import com.android.server.LocalServices;
@@ -66,6 +73,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -408,6 +417,25 @@ public class BackNavigationControllerTests extends WindowTestsBase {
0, navigationObserver.getCount());
}
+
+ /**
+ * Test with
+ * config_predictShowStartingSurface = true
+ */
+ @Test
+ public void testEnableWindowlessSurface() {
+ testPrepareAnimation(true);
+ }
+
+ /**
+ * Test with
+ * config_predictShowStartingSurface = false
+ */
+ @Test
+ public void testDisableWindowlessSurface() {
+ testPrepareAnimation(false);
+ }
+
private IOnBackInvokedCallback withSystemCallback(Task task) {
IOnBackInvokedCallback callback = createOnBackInvokedCallback();
task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo(
@@ -492,6 +520,55 @@ public class BackNavigationControllerTests extends WindowTestsBase {
doReturn(true).when(kc).isDisplayOccluded(anyInt());
}
+ private void testPrepareAnimation(boolean preferWindowlessSurface) {
+ final TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
+ final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext));
+ final Resources resourcesSpy = Mockito.spy(contextSpy.getResources());
+
+ when(contextSpy.getResources()).thenReturn(resourcesSpy);
+
+ MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class)
+ .strictness(Strictness.LENIENT).startMocking();
+ doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any()));
+ when(resourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_predictShowStartingSurface))
+ .thenReturn(preferWindowlessSurface);
+
+ final BackNavigationController.AnimationHandler animationHandler =
+ Mockito.spy(new BackNavigationController.AnimationHandler(mWm));
+ doReturn(true).when(animationHandler).isSupportWindowlessSurface();
+ testWithConfig(animationHandler, preferWindowlessSurface);
+ mockitoSession.finishMocking();
+ }
+
+ private void testWithConfig(BackNavigationController.AnimationHandler animationHandler,
+ boolean preferWindowlessSurface) {
+ final Task task = createTask(mDefaultDisplay);
+ final ActivityRecord bottomActivity = createActivityRecord(task);
+ final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity();
+
+ final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder =
+ animationHandler.prepareAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME,
+ mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity);
+ assertTrue(toHomeBuilder.mIsLaunchBehind);
+ toHomeBuilder.build();
+ verify(animationHandler, never()).createStartingSurface(any());
+
+ // Back to ACTIVITY and TASK have the same logic, just with different target.
+ final ActivityRecord topActivity = createActivityRecord(task);
+ final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder =
+ animationHandler.prepareAnimation(
+ BackNavigationInfo.TYPE_CROSS_ACTIVITY, mBackAnimationAdapter, task, task,
+ topActivity, bottomActivity);
+ assertFalse(toActivityBuilder.mIsLaunchBehind);
+ toActivityBuilder.build();
+ if (preferWindowlessSurface) {
+ verify(animationHandler).createStartingSurface(any());
+ } else {
+ verify(animationHandler, never()).createStartingSurface(any());
+ }
+ }
+
@NonNull
private Task createTopTaskWithActivity() {
Task task = createTask(mDefaultDisplay);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
index 858cd7672fe3..a8f1b3de564e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/GestureHelper.java
@@ -26,6 +26,8 @@ import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
+import androidx.annotation.Nullable;
+
/**
* Injects gestures given an {@link Instrumentation} object.
*/
@@ -36,6 +38,13 @@ public class GestureHelper {
private final UiAutomation mUiAutomation;
/**
+ * Primary pointer should be cached here for separate release
+ */
+ @Nullable private PointerProperties mPrimaryPtrProp;
+ @Nullable private PointerCoords mPrimaryPtrCoord;
+ private long mPrimaryPtrDownTime;
+
+ /**
* A pair of floating point values.
*/
public static class Tuple {
@@ -53,6 +62,52 @@ public class GestureHelper {
}
/**
+ * Injects a series of {@link MotionEvent}s to simulate a drag gesture without pointer release.
+ *
+ * Simulates a drag gesture without releasing the primary pointer. The primary pointer info
+ * will be cached for potential release later on by {@code releasePrimaryPointer()}
+ *
+ * @param startPoint initial coordinates of the primary pointer
+ * @param endPoint final coordinates of the primary pointer
+ * @param steps number of steps to take to animate dragging
+ * @return true if gesture is injected successfully
+ */
+ public boolean dragWithoutRelease(@NonNull Tuple startPoint,
+ @NonNull Tuple endPoint, int steps) {
+ PointerProperties ptrProp = getPointerProp(0, MotionEvent.TOOL_TYPE_FINGER);
+ PointerCoords ptrCoord = getPointerCoord(startPoint.x, startPoint.y, 1, 1);
+
+ PointerProperties[] ptrProps = new PointerProperties[] { ptrProp };
+ PointerCoords[] ptrCoords = new PointerCoords[] { ptrCoord };
+
+ long downTime = SystemClock.uptimeMillis();
+
+ if (!primaryPointerDown(ptrProp, ptrCoord, downTime)) {
+ return false;
+ }
+
+ // cache the primary pointer info for later potential release
+ mPrimaryPtrProp = ptrProp;
+ mPrimaryPtrCoord = ptrCoord;
+ mPrimaryPtrDownTime = downTime;
+
+ return movePointers(ptrProps, ptrCoords, new Tuple[] { endPoint }, downTime, steps);
+ }
+
+ /**
+ * Release primary pointer if previous gesture has cached the primary pointer info.
+ *
+ * @return true if the release was injected successfully
+ */
+ public boolean releasePrimaryPointer() {
+ if (mPrimaryPtrProp != null && mPrimaryPtrCoord != null) {
+ return primaryPointerUp(mPrimaryPtrProp, mPrimaryPtrCoord, mPrimaryPtrDownTime);
+ }
+
+ return false;
+ }
+
+ /**
* Injects a series of {@link MotionEvent} objects to simulate a pinch gesture.
*
* @param startPoint1 initial coordinates of the first pointer
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index de57d06a5619..e497ae4779a7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -58,6 +58,43 @@ open class PipAppHelper(instrumentation: Instrumentation) :
}
/**
+ * Drags the PIP window to the provided final coordinates without releasing the pointer.
+ */
+ fun dragPipWindowAwayFromEdgeWithoutRelease(
+ wmHelper: WindowManagerStateHelper,
+ steps: Int
+ ) {
+ val initWindowRect = getWindowRect(wmHelper).clone()
+
+ // initial pointer at the center of the window
+ val initialCoord = GestureHelper.Tuple(initWindowRect.centerX().toFloat(),
+ initWindowRect.centerY().toFloat())
+
+ // the offset to the right (or left) of the window center to drag the window to
+ val offset = 50
+
+ // the actual final x coordinate with the offset included;
+ // if the pip window is closer to the right edge of the display the offset is negative
+ // otherwise the offset is positive
+ val endX = initWindowRect.centerX() +
+ offset * (if (isCloserToRightEdge(wmHelper)) -1 else 1)
+ val finalCoord = GestureHelper.Tuple(endX.toFloat(), initWindowRect.centerY().toFloat())
+
+ // drag to the final coordinate
+ gestureHelper.dragWithoutRelease(initialCoord, finalCoord, steps)
+ }
+
+ /**
+ * Releases the primary pointer.
+ *
+ * Injects the release of the primary pointer if the primary pointer info was cached after
+ * another gesture was injected without pointer release.
+ */
+ fun releasePipAfterDragging() {
+ gestureHelper.releasePrimaryPointer()
+ }
+
+ /**
* Drags the PIP window away from the screen edge while not crossing the display center.
*
* @throws IllegalStateException if default display bounds are not available
@@ -72,10 +109,10 @@ open class PipAppHelper(instrumentation: Instrumentation) :
val displayRect = wmHelper.currentState.wmState.getDefaultDisplay()?.displayRect
?: throw IllegalStateException("Default display is null")
- // the offset to the right of the display center to drag the window to
+ // the offset to the right (or left) of the display center to drag the window to
val offset = 20
- // the actual final x coordinate with the offset included
+ // the actual final x coordinate with the offset included;
// if the pip window is closer to the right edge of the display the offset is positive
// otherwise the offset is negative
val endX = displayRect.centerX() + offset * (if (isCloserToRightEdge(wmHelper)) 1 else -1)