Merge "Reduce member fileds in InputMethodMenuController" into main
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index c65e506..de6f023 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -12,4 +12,11 @@
namespace: "backstage_power"
description: "Throw an exception if an unsupported app uses JobInfo.setBias"
bug: "300477393"
-}
\ No newline at end of file
+}
+
+flag {
+ name: "batch_jobs_on_network_activation"
+ namespace: "backstage_power"
+ description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
+ bug: "318394184"
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index df073dd..7ab234a 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -33391,6 +33391,7 @@
public class Process {
ctor public Process();
+ method public static final int getAppUidForSdkSandboxUid(int);
method public static final long getElapsedCpuTime();
method public static final int[] getExclusiveCores();
method public static final int getGidForName(String);
@@ -33405,6 +33406,7 @@
method public static final boolean isIsolated();
method public static final boolean isIsolatedUid(int);
method public static final boolean isSdkSandbox();
+ method public static final boolean isSdkSandboxUid(int);
method public static final void killProcess(int);
method public static final int myPid();
method @NonNull public static String myProcessName();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index de330de..c1b9f64 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -395,8 +395,6 @@
}
public class Process {
- method public static final int getAppUidForSdkSandboxUid(int);
- method public static final boolean isSdkSandboxUid(int);
method public static final int toSdkSandboxUid(int);
field public static final int NFC_UID = 1027; // 0x403
field public static final int VPN_UID = 1016; // 0x3f8
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 812ba6d..ad8b685 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2343,9 +2343,7 @@
}
public class Process {
- method public static final int getAppUidForSdkSandboxUid(int);
method public static final int getThreadScheduler(int) throws java.lang.IllegalArgumentException;
- method public static final boolean isSdkSandboxUid(int);
method public static final int toSdkSandboxUid(int);
field public static final int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; // 0x15f90
field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 45515dd..c1c5c0e 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -142,10 +142,7 @@
private static final String KEY_ACCOUNT =
"android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
- private final Context mContext;
-
public AbstractAccountAuthenticator(Context context) {
- mContext = context;
}
private class Transport extends IAccountAuthenticator.Stub {
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index a998ff2..0bae5e6 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -97,6 +97,11 @@
public boolean isUserFullscreenOverrideEnabled;
/**
+ * Whether the system has forced the activity to be fullscreen
+ */
+ public boolean isSystemFullscreenOverrideEnabled;
+
+ /**
* Hint about the letterbox state of the top activity.
*/
public boolean topActivityBoundsLetterboxed;
@@ -202,7 +207,8 @@
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
&& topActivityLetterboxHorizontalPosition
== that.topActivityLetterboxHorizontalPosition
- && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
+ && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled;
}
/**
@@ -224,7 +230,8 @@
&& topActivityLetterboxWidth == that.topActivityLetterboxWidth
&& topActivityLetterboxHeight == that.topActivityLetterboxHeight
&& cameraCompatControlState == that.cameraCompatControlState
- && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
+ && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
+ && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled;
}
/**
@@ -243,6 +250,7 @@
topActivityLetterboxWidth = source.readInt();
topActivityLetterboxHeight = source.readInt();
isUserFullscreenOverrideEnabled = source.readBoolean();
+ isSystemFullscreenOverrideEnabled = source.readBoolean();
}
/**
@@ -262,6 +270,7 @@
dest.writeInt(topActivityLetterboxWidth);
dest.writeInt(topActivityLetterboxHeight);
dest.writeBoolean(isUserFullscreenOverrideEnabled);
+ dest.writeBoolean(isSystemFullscreenOverrideEnabled);
}
@Override
@@ -280,6 +289,7 @@
+ " topActivityLetterboxWidth=" + topActivityLetterboxWidth
+ " topActivityLetterboxHeight=" + topActivityLetterboxHeight
+ " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
+ + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled
+ " cameraCompatControlState="
+ cameraCompatControlStateToString(cameraCompatControlState)
+ "}";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4b24b1f..1db1caf 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -71,6 +71,7 @@
import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
import android.util.Pools;
@@ -7705,6 +7706,14 @@
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(int code, int uid, @Mode int mode) {
try {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Log.i(DEBUG_LOGGING_TAG,
+ "setUidMode called for OP_BLUETOOTH_CONNECT with mode: " + mode
+ + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+ + " trace: "
+ + Arrays.toString(Thread.currentThread().getStackTrace()));
+ }
mService.setUidMode(code, uid, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -7725,6 +7734,15 @@
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
try {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (appOp.equals(OPSTR_BLUETOOTH_CONNECT)) {
+ Log.i(DEBUG_LOGGING_TAG,
+ "setUidMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+ + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+ + " trace: "
+ + Arrays.toString(Thread.currentThread().getStackTrace()));
+ }
+
mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -7765,6 +7783,14 @@
@RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
public void setMode(int code, int uid, String packageName, @Mode int mode) {
try {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Log.i(DEBUG_LOGGING_TAG,
+ "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+ + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+ + " trace: "
+ + Arrays.toString(Thread.currentThread().getStackTrace()));
+ }
mService.setMode(code, uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -7787,6 +7813,14 @@
public void setMode(@NonNull String op, int uid, @Nullable String packageName,
@Mode int mode) {
try {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (op.equals(OPSTR_BLUETOOTH_CONNECT)) {
+ Log.i(DEBUG_LOGGING_TAG,
+ "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+ + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+ + " trace: "
+ + Arrays.toString(Thread.currentThread().getStackTrace()));
+ }
mService.setMode(strOpToOp(op), uid, packageName, mode);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 5dd4eb7..2e47fee 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -41,6 +41,7 @@
import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Process;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IntArray;
@@ -87,12 +88,17 @@
Slog.d(TAG, transactionToString(transaction, mTransactionHandler));
}
- if (transaction.getTransactionItems() != null) {
- executeTransactionItems(transaction);
- } else {
- // TODO(b/260873529): cleanup after launch.
- executeCallbacks(transaction);
- executeLifecycleState(transaction);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "clientTransactionExecuted");
+ try {
+ if (transaction.getTransactionItems() != null) {
+ executeTransactionItems(transaction);
+ } else {
+ // TODO(b/260873529): cleanup after launch.
+ executeCallbacks(transaction);
+ executeLifecycleState(transaction);
+ }
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
if (!mContextToPreChangedConfigMap.isEmpty()) {
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index ec2e5fe..084cba3 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -20,3 +20,10 @@
description: "Move state file IO to non-critical path"
bug: "312949280"
}
+
+flag {
+ name: "draw_data_parcel"
+ namespace: "app_widgets"
+ description: "Enable support for transporting draw instructions as data parcel"
+ bug: "286130467"
+}
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index ad3ccc4..3fcb3da 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -37,6 +37,7 @@
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Log;
+import android.view.autofill.IAutoFillManagerClient;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -135,7 +136,8 @@
@Nullable CancellationSignal cancellationSignal,
@CallbackExecutor @NonNull Executor executor,
@NonNull OutcomeReceiver<GetCandidateCredentialsResponse,
- GetCandidateCredentialsException> callback
+ GetCandidateCredentialsException> callback,
+ @NonNull IAutoFillManagerClient clientCallback
) {
requireNonNull(request, "request must not be null");
requireNonNull(callingPackage, "callingPackage must not be null");
@@ -153,6 +155,7 @@
mService.getCandidateCredentials(
request,
new GetCandidateCredentialsTransport(executor, callback),
+ clientCallback,
callingPackage);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 1b130a9..530fead 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,7 @@
import android.annotation.Hide;
import android.annotation.NonNull;
+import android.app.PendingIntent;
import android.credentials.ui.GetCredentialProviderData;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,22 +36,39 @@
*/
@Hide
public final class GetCandidateCredentialsResponse implements Parcelable {
- // TODO(b/299321990): Add members
-
@NonNull
private final List<GetCredentialProviderData> mCandidateProviderDataList;
+ private final PendingIntent mPendingIntent;
+
+ private final GetCredentialResponse mGetCredentialResponse;
+
/**
* @hide
*/
@Hide
public GetCandidateCredentialsResponse(
- List<GetCredentialProviderData> candidateProviderDataList
+ GetCredentialResponse getCredentialResponse
+ ) {
+ mCandidateProviderDataList = null;
+ mPendingIntent = null;
+ mGetCredentialResponse = getCredentialResponse;
+ }
+
+ /**
+ * @hide
+ */
+ @Hide
+ public GetCandidateCredentialsResponse(
+ List<GetCredentialProviderData> candidateProviderDataList,
+ PendingIntent pendingIntent
) {
Preconditions.checkCollectionNotEmpty(
candidateProviderDataList,
/*valueName=*/ "candidateProviderDataList");
mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
+ mPendingIntent = pendingIntent;
+ mGetCredentialResponse = null;
}
/**
@@ -62,17 +80,40 @@
return mCandidateProviderDataList;
}
+ /**
+ * Returns candidate provider data list.
+ *
+ * @hide
+ */
+ public GetCredentialResponse getGetCredentialResponse() {
+ return mGetCredentialResponse;
+ }
+
+ /**
+ * Returns candidate provider data list.
+ *
+ * @hide
+ */
+ public PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
protected GetCandidateCredentialsResponse(Parcel in) {
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
in.readTypedList(candidateProviderDataList, GetCredentialProviderData.CREATOR);
mCandidateProviderDataList = candidateProviderDataList;
AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
+
+ mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+ mGetCredentialResponse = in.readTypedObject(GetCredentialResponse.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(mCandidateProviderDataList);
+ dest.writeTypedObject(mPendingIntent, flags);
+ dest.writeTypedObject(mGetCredentialResponse, flags);
}
@Override
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index d081576..726bc97 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -22,6 +22,7 @@
import android.credentials.ClearCredentialStateRequest;
import android.credentials.CreateCredentialRequest;
import android.credentials.GetCandidateCredentialsRequest;
+import android.view.autofill.IAutoFillManagerClient;
import android.credentials.GetCredentialRequest;
import android.credentials.RegisterCredentialDescriptionRequest;
import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -47,7 +48,7 @@
@nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
- @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);
+ @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IAutoFillManagerClient clientCallback, String callingPackage);
@nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index e714887..f5b3a7b 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -114,10 +114,16 @@
/** Format: 8 bits red */
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_8 = 0x38;
- /** Format: 16 bits red */
+ /**
+ * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
+ * implicit unsigned normalized.
+ */
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int R_16_UINT = 0x39;
- /** Format: 16 bits each red, green */
+ /**
+ * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
+ * instead of the implicit unsigned normalized.
+ */
@FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
public static final int RG_1616_UINT = 0x3a;
/** Format: 10 bits each red, green, blue, alpha */
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 80ec458..f952fcf 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -21,6 +21,7 @@
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UptimeMillisLong;
@@ -978,12 +979,10 @@
}
/**
- * Returns whether the provided UID belongs to a SDK sandbox process.
- *
- * @hide
+ * Returns whether the provided UID belongs to an sdk sandbox process
+ * @see android.app.sdksandbox.SdkSandboxManager
*/
- @SystemApi(client = MODULE_LIBRARIES)
- @TestApi
+ @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
@android.ravenwood.annotation.RavenwoodKeep
public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
@@ -991,15 +990,20 @@
}
/**
+ * Returns the app uid corresponding to an sdk sandbox uid.
+ * @see android.app.sdksandbox.SdkSandboxManager
*
- * Returns the app process corresponding to an sdk sandbox process.
+ * @param uid the sdk sandbox uid
+ * @return the app uid for the given sdk sandbox uid
*
- * @hide
+ * @throws IllegalArgumentException if input is not an sdk sandbox uid
*/
- @SystemApi(client = MODULE_LIBRARIES)
- @TestApi
+ @SuppressLint("UnflaggedApi") // promoting from @SystemApi.
@android.ravenwood.annotation.RavenwoodKeep
public static final int getAppUidForSdkSandboxUid(int uid) {
+ if (!isSdkSandboxUid(uid)) {
+ throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
+ }
return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index cb1b5d3..5ad2502 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -37,6 +37,7 @@
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.os.IResultReceiver;
@@ -621,6 +622,23 @@
new FillCallback(callback, request.getId())));
}
+
+ @Override
+ public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
+ IAutoFillManagerClient autofillClientCallback) {
+ ICancellationSignal transport = CancellationSignal.createTransport();
+ try {
+ callback.onCancellable(transport);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ mHandler.sendMessage(obtainMessage(
+ AutofillService::onFillCredentialRequest,
+ AutofillService.this, request, CancellationSignal.fromTransport(transport),
+ new FillCallback(callback, request.getId()),
+ autofillClientCallback));
+ }
+
@Override
public void onSaveRequest(SaveRequest request, ISaveCallback callback) {
mHandler.sendMessage(obtainMessage(
@@ -683,6 +701,15 @@
@NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
/**
+ * Variant of onFillRequest for internal credential manager proxy autofill service only.
+ *
+ * @hide
+ */
+ public void onFillCredentialRequest(@NonNull FillRequest request,
+ @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
+ IAutoFillManagerClient autofillClientCallback) {}
+
+ /**
* Called when the user requests the service to save the contents of a screen.
*
* <p>If the service could not handle the request right away—for example, because it must
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index d88e094..03ead32 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -20,6 +20,7 @@
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.os.IResultReceiver;
/**
@@ -30,6 +31,8 @@
oneway interface IAutoFillService {
void onConnectedStateChanged(boolean connected);
void onFillRequest(in FillRequest request, in IFillCallback callback);
+ void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
+ in IAutoFillManagerClient client);
void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
void onSavedPasswordCountRequest(in IResultReceiver receiver);
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index a38092a..49d2ceb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2067,10 +2067,10 @@
}
/**
- * Start sequence (infinite) type of flash notification. Use
- * {@code Context.getOpPackageName()} as the identifier of this flash notification.
+ * Start sequence (infinite) type of flash notification. Use {@code Context} to retrieve the
+ * package name as the identifier of this flash notification.
* The notification can be cancelled later by calling {@link #stopFlashNotificationSequence}
- * with same {@code Context.getOpPackageName()}.
+ * with same {@code Context}.
* If the binder associated with this {@link AccessibilityManager} instance dies then the
* sequence will stop automatically. It is strongly recommended to call
* {@link #stopFlashNotificationSequence} within a reasonable amount of time after calling
@@ -2104,8 +2104,8 @@
}
/**
- * Stop sequence (infinite) type of flash notification. The flash notification with
- * {@code Context.getOpPackageName()} as identifier will be stopped if exist.
+ * Stop sequence (infinite) type of flash notification. The flash notification with the
+ * package name retrieved from {@code Context} as identifier will be stopped if exist.
* It is strongly recommended to call this method within a reasonable amount of time after
* calling {@link #startFlashNotificationSequence} method.
*
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7c5885a..7e325a5 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2187,6 +2187,7 @@
}
if (multiuser_get_app_id(uid) == AID_NETWORK_STACK) {
+ capabilities |= (1LL << CAP_WAKE_ALARM);
capabilities |= (1LL << CAP_NET_ADMIN);
capabilities |= (1LL << CAP_NET_BROADCAST);
capabilities |= (1LL << CAP_NET_BIND_SERVICE);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 742d5a2..917a300 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4453,6 +4453,12 @@
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
+ "1946983717": {
+ "message": "Waiting for screen on due to %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_STATES",
+ "at": "com\/android\/server\/wm\/TaskFragment.java"
+ },
"1947239194": {
"message": "Deferring rotation, still finishing previous rotation",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
index ed99501..29cf054 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/WindowExtensionsImpl.java
@@ -55,7 +55,7 @@
// TODO(b/241126279) Introduce constants to better version functionality
@Override
public int getVendorApiLevel() {
- return 4;
+ return 5;
}
@NonNull
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index afd3b14..81d1399 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -232,6 +232,7 @@
return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
&& (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
|| taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+ && !taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
&& Intent.ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(Intent.CATEGORY_LAUNCHER)
&& (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 71bf487..0ef047f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -235,7 +235,8 @@
mainChoreographer,
taskOrganizer,
displayController,
- syncQueue);
+ syncQueue,
+ transitions);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index dc82fc1..88949b2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -55,6 +55,26 @@
"persist.wm.debug.desktop_stashing", false);
/**
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows", true);
+
+ /**
+ * Flag to indicate whether to apply shadows to the focused window in desktop mode.
+ *
+ * Note: this flag is only relevant if USE_WINDOW_SHADOWS is false.
+ */
+ private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
+
+ /**
+ * Flag to indicate whether to apply shadows to windows in desktop mode.
+ */
+ private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_use_rounded_corners", true);
+
+ /**
* Return {@code true} is desktop windowing proto 2 is enabled
*/
public static boolean isEnabled() {
@@ -81,4 +101,21 @@
public static boolean isStashingEnabled() {
return IS_STASHING_ENABLED;
}
+
+ /**
+ * Return whether to use window shadows.
+ *
+ * @param isFocusedWindow whether the window to apply shadows to is focused
+ */
+ public static boolean useWindowShadow(boolean isFocusedWindow) {
+ return USE_WINDOW_SHADOWS
+ || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
+ }
+
+ /**
+ * Return whether to use rounded corners for windows.
+ */
+ public static boolean useRoundedCorners() {
+ return USE_ROUNDED_CORNERS;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 144555d..4a1bcaa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -721,6 +721,9 @@
finishTransaction: SurfaceControl.Transaction
) {
// Add rounded corners to freeform windows
+ if (!DesktopModeStatus.useRoundedCorners()) {
+ return
+ }
val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
info.changes
.filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cf16920..cebc400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -54,6 +54,7 @@
private final Choreographer mMainChoreographer;
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
+ private final Transitions mTransitions;
private TaskOperations mTaskOperations;
private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -64,13 +65,15 @@
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ Transitions transitions) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ mTransitions = transitions;
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
}
@@ -133,7 +136,8 @@
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
}
@@ -145,7 +149,8 @@
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* setTaskCropAndPosition */);
}
@Override
@@ -191,16 +196,17 @@
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- final DragPositioningCallback dragPositioningCallback =
- new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- 0 /* disallowedAreaForEndBoundsHeight */);
+ final FluidResizeTaskPositioner taskPositioner =
+ new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
+ mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
final CaptionTouchEventListener touchEventListener =
- new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
- windowDecoration.setDragPositioningCallback(dragPositioningCallback);
+ windowDecoration.setDragPositioningCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
+ windowDecoration.setTaskDragResizer(taskPositioner);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
setupCaptionColor(taskInfo, windowDecoration);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 6e7d11d..1debb02 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -157,15 +157,21 @@
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
+ boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
final int shadowRadiusID = taskInfo.isFocused
? R.dimen.freeform_decor_shadow_focused_thickness
: R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -183,6 +189,7 @@
mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index ab29df1..4fd3625 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -335,7 +335,8 @@
if (decoration == null) {
createWindowDecoration(taskInfo, taskSurface, startT, finishT);
} else {
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
}
@@ -347,7 +348,8 @@
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
if (decoration == null) return;
- decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+ decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+ false /* shouldSetTaskPositionAndCrop */);
}
@Override
@@ -1010,8 +1012,23 @@
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
windowDecoration.createResizeVeil();
- final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
- windowDecoration);
+ final DragPositioningCallback dragPositioningCallback;
+ final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.desktop_mode_transition_area_height);
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ dragPositioningCallback = new FluidResizeTaskPositioner(
+ mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
+ mDragStartListener, mTransactionFactory, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (FluidResizeTaskPositioner) dragPositioningCallback);
+ } else {
+ dragPositioningCallback = new VeiledResizeTaskPositioner(
+ mTaskOrganizer, windowDecoration, mDisplayController,
+ mDragStartListener, mTransitions, transitionAreaHeight);
+ windowDecoration.setTaskDragResizer(
+ (VeiledResizeTaskPositioner) dragPositioningCallback);
+ }
+
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
@@ -1021,23 +1038,9 @@
windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
- false /* applyStartTransactionOnDraw */);
+ false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
incrementEventReceiverTasks(taskInfo.displayId);
}
- private DragPositioningCallback createDragPositioningCallback(
- @NonNull DesktopModeWindowDecoration windowDecoration) {
- final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
- R.dimen.desktop_mode_transition_area_height);
- if (!DesktopModeStatus.isVeiledResizeEnabled()) {
- return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransactionFactory,
- transitionAreaHeight);
- } else {
- return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
- mDisplayController, mDragStartListener, mTransitions,
- transitionAreaHeight);
- }
- }
private RunningTaskInfo getOtherSplitTask(int taskId) {
@SplitPosition int remainingTaskPosition = mSplitScreenController
@@ -1138,7 +1141,6 @@
}
}
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 3b6be8a..0c8e93b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -46,6 +46,7 @@
import android.widget.ImageButton;
import android.window.WindowContainerTransaction;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.IconProvider;
@@ -186,55 +187,33 @@
}
final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ // The crop and position of the task should only be set when a task is fluid resizing. In
+ // all other cases, it is expected that the transition handler positions and crops the task
+ // in order to allow the handler time to animate before the task before the final
+ // position and crop are set.
+ final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
+ && mTaskDragResizer.isResizingOrAnimating();
// Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
// synced with the buffer transaction (that draws the View). Both will be shown on screen
// at the same, whereas applying them independently causes flickering. See b/270202228.
- relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+ relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+ shouldSetTaskPositionAndCrop);
}
void relayout(ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
- boolean applyStartTransactionOnDraw) {
- final int shadowRadiusID = taskInfo.isFocused
- ? R.dimen.freeform_decor_shadow_focused_thickness
- : R.dimen.freeform_decor_shadow_unfocused_thickness;
- final boolean isFreeform =
- taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
- final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
-
+ boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
+ shouldSetTaskPositionAndCrop);
+
final WindowDecorLinearLayout oldRootView = mResult.mRootView;
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
- taskInfo.getWindowingMode());
- mRelayoutParams.reset();
- mRelayoutParams.mRunningTaskInfo = taskInfo;
- mRelayoutParams.mLayoutResId = windowDecorLayoutId;
- mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
- mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
- // The configuration used to lay out the window decoration. The system context's config is
- // used when the task density has been overridden to a custom density so that the resources
- // and views of the decoration aren't affected and match the rest of the System UI, if not
- // then just use the task's configuration. A copy is made instead of using the original
- // reference so that the configuration isn't mutated on config changes and diff checks can
- // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
- // See b/301119301.
- // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
- // instead of using a whole Configuration as a parameter.
- final Configuration windowDecorConfig = new Configuration();
- windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
- ? mContext.getResources().getConfiguration() // Use system context.
- : mTaskInfo.configuration); // Use task configuration.
- mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
-
- mRelayoutParams.mCornerRadius =
- (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -273,6 +252,9 @@
closeMaximizeMenu();
}
+ final boolean isFreeform =
+ taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
if (!isDragResizeable) {
if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
// We still want to track caption bar's exclusion region on a non-resizeable task.
@@ -323,6 +305,47 @@
}
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ Context context,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw,
+ boolean shouldSetTaskPositionAndCrop) {
+ relayoutParams.reset();
+ relayoutParams.mRunningTaskInfo = taskInfo;
+ relayoutParams.mLayoutResId =
+ getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+ relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+ if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) {
+ relayoutParams.mShadowRadiusId = taskInfo.isFocused
+ ? R.dimen.freeform_decor_shadow_focused_thickness
+ : R.dimen.freeform_decor_shadow_unfocused_thickness;
+ }
+ relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+ relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+ // The configuration used to lay out the window decoration. The system context's config is
+ // used when the task density has been overridden to a custom density so that the resources
+ // and views of the decoration aren't affected and match the rest of the System UI, if not
+ // then just use the task's configuration. A copy is made instead of using the original
+ // reference so that the configuration isn't mutated on config changes and diff checks can
+ // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+ // See b/301119301.
+ // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+ // instead of using a whole Configuration as a parameter.
+ final Configuration windowDecorConfig = new Configuration();
+ windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+ ? context.getResources().getConfiguration() // Use system context.
+ : taskInfo.configuration); // Use task configuration.
+ relayoutParams.mWindowDecorConfig = windowDecorConfig;
+
+ if (DesktopModeStatus.useRoundedCorners()) {
+ relayoutParams.mCornerRadius =
+ (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+ }
+ }
+
+
private PointF calculateMaximizeMenuPosition() {
final PointF position = new PointF();
final Resources resources = mContext.getResources();
@@ -684,7 +707,7 @@
super.close();
}
- private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
+ private static int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FREEFORM
? R.layout.desktop_mode_app_controls_window_decor
: R.layout.desktop_mode_focused_window_decor;
@@ -730,6 +753,10 @@
@Override
int getCaptionHeightId(@WindowingMode int windowingMode) {
+ return getCaptionHeightIdStatic(windowingMode);
+ }
+
+ private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
return windowingMode == WINDOWING_MODE_FULLSCREEN
? R.dimen.desktop_mode_fullscreen_decor_caption_height
: R.dimen.desktop_mode_freeform_decor_caption_height;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 677c7f1..5afbd54 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -26,9 +26,7 @@
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.window.WindowContainerTransaction;
-import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
/**
@@ -130,8 +128,7 @@
Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t,
float x, float y) {
updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y);
- t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left,
- repositionTaskBounds.top);
+ t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
}
private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -188,18 +185,6 @@
}
}
- /**
- * Apply a bounds change to a task.
- * @param windowDecoration decor of task we are changing bounds for
- * @param taskBounds new bounds of this task
- * @param taskOrganizer applies the provided WindowContainerTransaction
- */
- static void applyTaskBoundsChange(WindowContainerTransaction wct,
- WindowDecoration windowDecoration, Rect taskBounds, ShellTaskOrganizer taskOrganizer) {
- wct.setBounds(windowDecoration.mTaskInfo.token, taskBounds);
- taskOrganizer.applyTransaction(wct);
- }
-
private static float getMinWidth(DisplayController displayController,
WindowDecoration windowDecoration) {
return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize(displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 5d006fb..6bfc7cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -16,23 +16,42 @@
package com.android.wm.shell.windowdecor;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
import android.graphics.PointF;
import android.graphics.Rect;
+import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.transition.Transitions;
import java.util.function.Supplier;
/**
* A task positioner that resizes/relocates task contents as it is dragged.
* Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds.
+ *
+ * This positioner applies the final bounds after a resize or drag using a shell transition in order
+ * to utilize the startAnimation callback to set the final task position and crop. In most cases,
+ * the transition will be aborted since the final bounds are usually the same bounds set in the
+ * final {@link #onDragPositioningMove} call. In this case, the cropping and positioning would be
+ * set by {@link WindowDecoration#relayout} due to the final bounds change; however, it is important
+ * that we send the final shell transition since we still utilize the {@link #onTransitionConsumed}
+ * callback.
*/
-class FluidResizeTaskPositioner implements DragPositioningCallback {
+class FluidResizeTaskPositioner implements DragPositioningCallback,
+ TaskDragResizer, Transitions.TransitionHandler {
private final ShellTaskOrganizer mTaskOrganizer;
+ private final Transitions mTransitions;
private final WindowDecoration mWindowDecoration;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private DisplayController mDisplayController;
@@ -45,21 +64,28 @@
// finalize the bounds there using WCT#setBounds
private final int mDisallowedAreaForEndBoundsHeight;
private boolean mHasDragResized;
+ private boolean mIsResizingOrAnimatingResize;
private int mCtrlType;
+ private IBinder mDragResizeEndTransition;
@Surface.Rotation private int mRotation;
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
- this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
- SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
+ WindowDecoration windowDecoration, DisplayController displayController,
+ int disallowedAreaForEndBoundsHeight) {
+ this(taskOrganizer, transitions, windowDecoration, displayController,
+ dragStartListener -> {}, SurfaceControl.Transaction::new,
+ disallowedAreaForEndBoundsHeight);
}
- FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
+ Transitions transitions,
+ WindowDecoration windowDecoration,
DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier,
int disallowedAreaForEndBoundsHeight) {
mTaskOrganizer = taskOrganizer;
+ mTransitions = transitions;
mWindowDecoration = windowDecoration;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
@@ -103,9 +129,10 @@
// This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
}
- DragPositioningCallbackUtility.applyTaskBoundsChange(wct, mWindowDecoration,
- mRepositionTaskBounds, mTaskOrganizer);
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTaskOrganizer.applyTransaction(wct);
mHasDragResized = true;
+ mIsResizingOrAnimatingResize = true;
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mWindowDecoration,
@@ -129,7 +156,7 @@
mWindowDecoration)) {
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
}
- mTaskOrganizer.applyTransaction(wct);
+ mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else if (mCtrlType == CTRL_TYPE_UNDEFINED
&& DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
@@ -139,7 +166,7 @@
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
mWindowDecoration.calculateValidDragArea());
wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mTaskBoundsAtDragStart.setEmpty();
@@ -154,4 +181,51 @@
|| (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
}
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
+ startTransaction.apply();
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ finishCallback.onTransitionFinished(null);
+ return true;
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+ @Nullable SurfaceControl.Transaction finishTransaction) {
+ if (transition.equals(mDragResizeEndTransition)) {
+ mIsResizingOrAnimatingResize = false;
+ mDragResizeEndTransition = null;
+ }
+ }
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
new file mode 100644
index 0000000..40421b5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -0,0 +1,29 @@
+/*
+ * 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.windowdecor;
+
+/**
+ * Holds the state of a drag resize.
+ */
+interface TaskDragResizer {
+
+ /**
+ * Returns true if task is currently being resized or animating the final transition after
+ * a resize is complete.
+ */
+ boolean isResizingOrAnimating();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4363558..c1b18f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -43,7 +43,7 @@
* If the drag is repositioning, we update in the typical manner.
*/
public class VeiledResizeTaskPositioner implements DragPositioningCallback,
- Transitions.TransitionHandler {
+ TaskDragResizer, Transitions.TransitionHandler {
private DesktopModeWindowDecoration mDesktopWindowDecoration;
private ShellTaskOrganizer mTaskOrganizer;
@@ -59,10 +59,12 @@
private final int mDisallowedAreaForEndBoundsHeight;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private int mCtrlType;
+ private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
@@ -71,12 +73,13 @@
}
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
- DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DesktopModeWindowDecoration windowDecoration,
+ DisplayController displayController,
DragPositioningCallbackUtility.DragStartListener dragStartListener,
Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
int disallowedAreaForEndBoundsHeight) {
- mTaskOrganizer = taskOrganizer;
mDesktopWindowDecoration = windowDecoration;
+ mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mDragStartListener = dragStartListener;
mTransactionSupplier = supplier;
@@ -117,6 +120,7 @@
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ mIsResizingOrAnimatingResize = true;
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -138,24 +142,22 @@
mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
- mTaskOrganizer.applyTransaction(wct);
- }
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
} else {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
+ mIsResizingOrAnimatingResize = false;
}
} else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
y)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
mDesktopWindowDecoration.calculateValidDragArea());
- DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
- mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
+ wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
}
mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -174,10 +176,20 @@
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (TransitionInfo.Change change: info.getChanges()) {
+ final SurfaceControl sc = change.getLeash();
+ final Rect endBounds = change.getEndAbsBounds();
+ startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endBounds.left, endBounds.top);
+ }
+
startTransaction.apply();
mDesktopWindowDecoration.hideResizeVeil();
mCtrlType = CTRL_TYPE_UNDEFINED;
finishCallback.onTransitionFinished(null);
+ mIsResizingOrAnimatingResize = false;
return true;
}
@@ -191,4 +203,9 @@
@NonNull TransitionRequestInfo request) {
return null;
}
+
+ @Override
+ public boolean isResizingOrAnimating() {
+ return mIsResizingOrAnimatingResize;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ee0e31e..b5373c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -124,6 +124,7 @@
private WindowlessWindowManager mCaptionWindowManager;
private SurfaceControlViewHost mViewHost;
private Configuration mWindowDecorConfig;
+ TaskDragResizer mTaskDragResizer;
private boolean mIsCaptionVisible;
private final Binder mOwner = new Binder();
@@ -311,25 +312,21 @@
float shadowRadius;
final Point taskPosition = mTaskInfo.positionInParent;
if (isFullscreen) {
- // Setting the task crop to the width/height stops input events from being sent to
- // some regions of the app window. See b/300324920
- // TODO(b/296921174): investigate whether crop/position needs to be set by window
- // decorations at all when transition handlers are already taking ownership of the task
- // surface placement/crop, especially when in fullscreen where tasks cannot be
- // drag-resized by the window decoration.
- startT.setWindowCrop(mTaskSurface, null);
- finishT.setWindowCrop(mTaskSurface, null);
// Shadow is not needed for fullscreen tasks
shadowRadius = 0;
} else {
- startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
- finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
shadowRadius = loadDimension(resources, params.mShadowRadiusId);
}
+
+ if (params.mSetTaskPositionAndCrop) {
+ startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+ finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
+ .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
+ }
+
startT.setShadowRadius(mTaskSurface, shadowRadius)
.show(mTaskSurface);
- finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setShadowRadius(mTaskSurface, shadowRadius);
+ finishT.setShadowRadius(mTaskSurface, shadowRadius);
if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
if (!DesktopModeStatus.isVeiledResizeEnabled()) {
// When fluid resize is enabled, add a background to freeform tasks
@@ -394,6 +391,10 @@
}
}
+ void setTaskDragResizer(TaskDragResizer taskDragResizer) {
+ mTaskDragResizer = taskDragResizer;
+ }
+
private void setCaptionVisibility(View rootView, boolean visible) {
if (rootView == null) {
return;
@@ -559,6 +560,7 @@
Configuration mWindowDecorConfig;
boolean mApplyStartTransactionOnDraw;
+ boolean mSetTaskPositionAndCrop;
void reset() {
mLayoutResId = Resources.ID_NULL;
@@ -572,6 +574,7 @@
mCaptionY = 0;
mApplyStartTransactionOnDraw = false;
+ mSetTaskPositionAndCrop = false;
mWindowDecorConfig = null;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 18fcdd0..193f16d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -16,16 +16,24 @@
package com.android.wm.shell.windowdecor;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.os.Handler;
+import android.os.SystemProperties;
import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
import android.view.Choreographer;
import android.view.Display;
import android.view.SurfaceControl;
@@ -34,14 +42,17 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -57,6 +68,13 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeWindowDecorationTests extends ShellTestCase {
+ private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_window_shadows";
+ private static final String FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_window_shadows_focused_window";
+ private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
+ "persist.wm.debug.desktop_use_rounded_corners";
+
@Mock
private DisplayController mMockDisplayController;
@Mock
@@ -79,14 +97,29 @@
private SurfaceControlViewHost mMockSurfaceControlViewHost;
@Mock
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+ @Mock
+ private TypedArray mMockRoundedCornersRadiusArray;
private final Configuration mConfiguration = new Configuration();
+ private TestableContext mTestableContext;
+
+ /** Set up run before test class. */
+ @BeforeClass
+ public static void setUpClass() {
+ // Reset the sysprop settings before running the test.
+ SystemProperties.set(USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+ SystemProperties.set(FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+ SystemProperties.set(USE_ROUNDED_CORNERS_SYSPROP_KEY, "");
+ }
+
@Before
public void setUp() {
doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
any(), any(), any());
doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
+ mTestableContext = new TestableContext(mContext);
+ mTestableContext.ensureTestableResources();
}
@Test
@@ -105,6 +138,52 @@
}
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+ }
+
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ fillRoundedCornersResources(/* fillValue= */ 30);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams,
+ mTestableContext,
+ taskInfo,
+ /* applyStartTransactionOnDraw= */ true,
+ /* shouldSetTaskPositionAndCrop */ false);
+
+ assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
+ }
+
+ private void fillRoundedCornersResources(int fillValue) {
+ when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
+ .thenReturn(fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius, fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerTopRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius_top, fillValue);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.array.config_roundedCornerBottomRadiusArray, mMockRoundedCornersRadiusArray);
+ mTestableContext.getOrCreateTestableResources().addOverride(
+ R.dimen.rounded_corner_radius_bottom, fillValue);
+ }
+
+
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 2ce49cf..de6903d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -10,6 +10,7 @@
import android.view.Surface.ROTATION_270
import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
+import android.view.WindowManager
import android.window.WindowContainerToken
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
@@ -18,13 +19,17 @@
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito
@@ -34,6 +39,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
import java.util.function.Supplier
import org.mockito.Mockito.`when` as whenever
@@ -50,6 +56,8 @@
@Mock
private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock
+ private lateinit var mockTransitions: Transitions
+ @Mock
private lateinit var mockWindowDecoration: WindowDecoration<*>
@Mock
private lateinit var mockDragStartListener: DragPositioningCallbackUtility.DragStartListener
@@ -69,6 +77,8 @@
private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
@Mock
private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock
+ private lateinit var mockTransitionBinder: IBinder
private lateinit var taskPositioner: FluidResizeTaskPositioner
@@ -106,9 +116,12 @@
`when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
mockWindowDecoration.mDisplay = mockDisplay
whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ whenever(mockTransitions.startTransition(anyInt(), any(), any()))
+ .doReturn(mockTransitionBinder)
taskPositioner = FluidResizeTaskPositioner(
mockShellTaskOrganizer,
+ mockTransitions,
mockWindowDecoration,
mockDisplayController,
mockDragStartListener,
@@ -118,7 +131,7 @@
}
@Test
- fun testDragResize_notMove_skipsTransactionOnEnd() {
+ fun testDragResize_notMove_skipsTransitionOnEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -130,16 +143,16 @@
STARTING_BOUNDS.top.toFloat() + 10
)
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
+ }}, eq(taskPositioner))
}
@Test
- fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+ fun testDragResize_noEffectiveMove_skipsTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -151,21 +164,28 @@
STARTING_BOUNDS.top.toFloat()
)
- taskPositioner.onDragPositioningEnd(
- STARTING_BOUNDS.left.toFloat() + 10,
- STARTING_BOUNDS.top.toFloat() + 10
- )
-
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
}
})
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+ }}, eq(taskPositioner))
}
@Test
- fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+ fun testDragResize_hasEffectiveMove_issuesTransitionOnMoveAndEnd() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
@@ -192,13 +212,13 @@
)
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.top += 10
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
- return@argThat wct.changes.any { (token, change) ->
- token == taskBinder &&
- (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- })
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }}, eq(taskPositioner))
}
@Test
@@ -226,6 +246,13 @@
change.dragResizing
}
})
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }}, eq(taskPositioner))
}
@Test
@@ -253,13 +280,13 @@
change.dragResizing
}
})
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
!change.dragResizing
- }
- })
+ }}, eq(taskPositioner))
}
@Test
@@ -270,7 +297,7 @@
STARTING_BOUNDS.top.toFloat()
)
- // Resize to width of 95px and height of 5px with min width of 10px
+ // Resize to width of 95px and height of 5px with min height of 10px
val newX = STARTING_BOUNDS.right.toFloat() - 5
val newY = STARTING_BOUNDS.top.toFloat() + 95
taskPositioner.onDragPositioningMove(
@@ -566,12 +593,12 @@
taskPositioner.onDragPositioningEnd(newX, newY)
- verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ verify(mockTransitions, never()).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
- }
- })
+ }}, eq(taskPositioner))
}
private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
@@ -650,14 +677,14 @@
)
// Verify task's top bound is set to stable bounds top since dragged outside stable bounds
// but not in disallowed end bounds area.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
STABLE_BOUNDS_LANDSCAPE.top
- }
- })
+ }}, eq(taskPositioner))
}
@Test
@@ -680,7 +707,8 @@
newX,
newY
)
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(
+ eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
@@ -688,8 +716,7 @@
VALID_DRAG_AREA.bottom &&
change.configuration.windowConfiguration.bounds.left ==
VALID_DRAG_AREA.left
- }
- })
+ }}, eq(taskPositioner))
}
@Test
@@ -741,6 +768,59 @@
verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
}
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() {
+ assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ assertTrue(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterAbortedTransition() {
+ performDrag(STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.onTransitionConsumed(mockTransitionBinder, true /* aborted */,
+ mockTransaction)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterNonAbortedTransition() {
+ performDrag(STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.onTransitionConsumed(mockTransitionBinder, false /* aborted */,
+ mockTransaction)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
private fun performDrag(
startX: Float,
startY: Float,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index a759b53..0841210 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -26,6 +26,7 @@
import android.view.Surface.ROTATION_90
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTaskOrganizer
@@ -33,10 +34,12 @@
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -85,6 +88,12 @@
@Mock
private lateinit var mockTransaction: SurfaceControl.Transaction
@Mock
+ private lateinit var mockTransitionBinder: IBinder
+ @Mock
+ private lateinit var mockTransitionInfo: TransitionInfo
+ @Mock
+ private lateinit var mockFinishCallback: TransitionFinishCallback
+ @Mock
private lateinit var mockTransitions: Transitions
private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -188,13 +197,12 @@
verify(mockDesktopWindowDecoration, never()).createResizeVeil()
verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
- change.configuration.windowConfiguration.bounds == rectAfterEnd
- }
- })
+ change.configuration.windowConfiguration.bounds == rectAfterEnd }},
+ eq(taskPositioner))
}
@Test
@@ -369,14 +377,13 @@
)
// Verify task's top bound is set to stable bounds top since dragged outside stable bounds
// but not in disallowed end bounds area.
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
- STABLE_BOUNDS_LANDSCAPE.top
- }
- })
+ STABLE_BOUNDS_LANDSCAPE.top }},
+ eq(taskPositioner))
}
@Test
@@ -399,16 +406,15 @@
newX,
newY
)
- verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds.top ==
VALID_DRAG_AREA.bottom &&
change.configuration.windowConfiguration.bounds.left ==
- VALID_DRAG_AREA.left
- }
- })
+ VALID_DRAG_AREA.left }},
+ eq(taskPositioner))
}
@Test
@@ -456,6 +462,47 @@
verify(mockDisplayLayout, times(2)).getStableBounds(any())
}
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() {
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() {
+ performDrag(
+ STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(),
+ STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+ taskPositioner.startAnimation(mockTransitionBinder, mockTransitionInfo, mockTransaction,
+ mockTransaction, mockFinishCallback)
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
private fun performDrag(
startX: Float,
startY: Float,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fe508e2..32a91461 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
@@ -261,11 +262,6 @@
eq(new Rect(100, 300, 400, 364)));
}
- verify(mMockSurfaceControlFinishT)
- .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
- TASK_POSITION_IN_PARENT.y);
- verify(mMockSurfaceControlFinishT)
- .setWindowCrop(mMockTaskSurface, 300, 100);
verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
verify(mMockSurfaceControlStartT)
@@ -642,6 +638,66 @@
eq(0) /* index */, eq(mandatorySystemGestures()));
}
+ @Test
+ public void testTaskPositionAndCropNotSetWhenFalse() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+
+ mRelayoutParams.mSetTaskPositionAndCrop = false;
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT, never()).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ verify(mMockSurfaceControlFinishT, never()).setPosition(
+ eq(mMockTaskSurface), anyFloat(), anyFloat());
+ verify(mMockSurfaceControlFinishT, never()).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testTaskPositionAndCropSetWhenSetTrue() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+ mRelayoutParams.mSetTaskPositionAndCrop = true;
+ windowDecor.relayout(taskInfo);
+
+ verify(mMockSurfaceControlStartT).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ verify(mMockSurfaceControlFinishT).setPosition(
+ eq(mMockTaskSurface), anyFloat(), anyFloat());
+ verify(mMockSurfaceControlFinishT).setWindowCrop(
+ eq(mMockTaskSurface), anyInt(), anyInt());
+ }
+
+
private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
taskInfo, mMockTaskSurface, mWindowConfiguration,
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 6a052db..260547c 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -90,11 +90,6 @@
mOutput << mIdent << "drawTextBlob" << std::endl;
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
- mOutput << mIdent << "drawImage" << std::endl;
- }
-
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SrcRectConstraint) override {
mOutput << mIdent << "drawImageRect" << std::endl;
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index dc36a2e..df5f04f99 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -109,12 +109,6 @@
drawPoints++;
}
- int drawImageCount = 0;
- void onDrawImage2(const SkImage* image, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint* paint) override {
- drawImageCount++;
- }
-
int drawImageRectCount = 0;
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SkCanvas::SrcRectConstraint) override {
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 18c5047..4ae76e2 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -492,7 +492,7 @@
CallCountingCanvas canvas;
EXPECT_EQ(0, canvas.sumTotalDrawCalls());
rasterizeCanvasBuffer(buffer, &canvas);
- EXPECT_EQ(1, canvas.drawImageCount);
+ EXPECT_EQ(1, canvas.drawImageRectCount);
EXPECT_EQ(1, canvas.sumTotalDrawCalls());
}
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 96a0c61..8b95e0c 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -69,10 +69,6 @@
void onDrawPath(const SkPath&, const SkPaint&) {
ADD_FAILURE() << "onDrawPath not expected in this test";
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) {
- ADD_FAILURE() << "onDrawImage not expected in this test";
- }
void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
const SkPaint*, SrcRectConstraint) {
ADD_FAILURE() << "onDrawImageRect not expected in this test";
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 073a835..ca54087 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -941,8 +941,9 @@
void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
EXPECT_EQ(0, mDrawCounter++);
}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(1, mDrawCounter++);
}
};
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 3ded540..785e286 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -303,8 +303,9 @@
class ClippedTestCanvas : public SkCanvas {
public:
ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(0, mDrawCounter++);
EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(this));
EXPECT_TRUE(getTotalMatrix().isIdentity());
@@ -338,8 +339,9 @@
class ClippedTestCanvas : public SkCanvas {
public:
ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
- void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
- const SkPaint*) override {
+ void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+ const SkSamplingOptions&, const SkPaint*,
+ SrcRectConstraint) override {
EXPECT_EQ(0, mDrawCounter++);
// Expect clip to be rotated.
EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft,
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index a8464d3..794a555 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -34,3 +34,10 @@
description: "Flag for location validation"
bug: "314328533"
}
+
+flag {
+ name: "gnss_configuration_from_resource"
+ namespace: "location"
+ description: "Flag for GNSS configuration from resource"
+ bug: "317734846"
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 46a0b99..0f6cbff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2499,14 +2499,12 @@
* </ul>
*/
public static int getPlatformType(Context context) {
- if (((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
- .isVoiceCapable()) {
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+ return PLATFORM_AUTOMOTIVE;
+ } else if ((context.getSystemService(TelephonyManager.class)).isVoiceCapable()) {
return PLATFORM_VOICE;
} else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
return PLATFORM_TELEVISION;
- } else if (context.getPackageManager().hasSystemFeature(
- PackageManager.FEATURE_AUTOMOTIVE)) {
- return PLATFORM_AUTOMOTIVE;
} else {
return PLATFORM_DEFAULT;
}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index e0df794..193728a 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -18,21 +18,21 @@
AImageDecoder_getRepeatCount; # introduced=31
AImageDecoder_advanceFrame; # introduced=31
AImageDecoder_rewind; # introduced=31
- AImageDecoder_getFrameInfo; # introduced = 31
- AImageDecoder_setInternallyHandleDisposePrevious; # introduced = 31
+ AImageDecoder_getFrameInfo; # introduced=31
+ AImageDecoder_setInternallyHandleDisposePrevious; # introduced=31
AImageDecoderHeaderInfo_getWidth; # introduced=30
AImageDecoderHeaderInfo_getHeight; # introduced=30
AImageDecoderHeaderInfo_getMimeType; # introduced=30
AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30
AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30
AImageDecoderHeaderInfo_getDataSpace; # introduced=30
- AImageDecoderFrameInfo_create; # introduced = 31
- AImageDecoderFrameInfo_delete; # introduced = 31
- AImageDecoderFrameInfo_getDuration; # introduced = 31
- AImageDecoderFrameInfo_getFrameRect; # introduced = 31
- AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced = 31
- AImageDecoderFrameInfo_getDisposeOp; # introduced = 31
- AImageDecoderFrameInfo_getBlendOp; # introduced = 31
+ AImageDecoderFrameInfo_create; # introduced=31
+ AImageDecoderFrameInfo_delete; # introduced=31
+ AImageDecoderFrameInfo_getDuration; # introduced=31
+ AImageDecoderFrameInfo_getFrameRect; # introduced=31
+ AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced=31
+ AImageDecoderFrameInfo_getDisposeOp; # introduced=31
+ AImageDecoderFrameInfo_getBlendOp; # introduced=31
AndroidBitmap_getInfo;
AndroidBitmap_getDataSpace;
AndroidBitmap_lockPixels;
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 0ff1c7f..dfa5735 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -19,16 +19,18 @@
import android.R
import android.app.assist.AssistStructure
import android.content.Context
-import android.credentials.CredentialManager
-import android.credentials.CredentialOption
-import android.credentials.GetCandidateCredentialsException
-import android.credentials.GetCandidateCredentialsResponse
+import android.app.PendingIntent
+import android.credentials.GetCredentialResponse
import android.credentials.GetCredentialRequest
-import android.credentials.ui.GetCredentialProviderData
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.CredentialOption
import android.graphics.drawable.Icon
+import android.credentials.ui.GetCredentialProviderData
import android.os.Bundle
import android.os.CancellationSignal
import android.os.OutcomeReceiver
+import android.credentials.Credential
import android.service.autofill.AutofillService
import android.service.autofill.Dataset
import android.service.autofill.Field
@@ -41,8 +43,11 @@
import android.service.autofill.SaveRequest
import android.service.credentials.CredentialProviderService
import android.util.Log
+import android.view.autofill.AutofillValue
+import android.view.autofill.IAutoFillManagerClient
import android.view.autofill.AutofillId
import android.widget.inline.InlinePresentationSpec
+import android.credentials.CredentialManager
import androidx.autofill.inline.v1.InlineSuggestionUi
import androidx.credentials.provider.CustomCredentialEntry
import androidx.credentials.provider.PasswordCredentialEntry
@@ -58,11 +63,13 @@
import org.json.JSONObject
import java.util.concurrent.Executors
+
class CredentialAutofillService : AutofillService() {
companion object {
private const val TAG = "CredAutofill"
+ private const val SESSION_ID_KEY = "session_id"
private const val CRED_HINT_PREFIX = "credential="
private const val REQUEST_DATA_KEY = "requestData"
private const val CANDIDATE_DATA_KEY = "candidateQueryData"
@@ -77,10 +84,27 @@
cancellationSignal: CancellationSignal,
callback: FillCallback
) {
+ }
+
+ override fun onFillCredentialRequest(
+ request: FillRequest,
+ cancellationSignal: CancellationSignal,
+ callback: FillCallback,
+ autofillCallback: IAutoFillManagerClient
+ ) {
val context = request.fillContexts
val structure = context[context.size - 1].structure
val callingPackage = structure.activityComponent.packageName
- Log.i(TAG, "onFillRequest called for $callingPackage")
+ Log.i(TAG, "onFillCredentialRequest called for $callingPackage")
+
+ var sessionId = request.clientState?.getInt(SESSION_ID_KEY)
+
+ Log.i(TAG, "Autofill sessionId: " + sessionId)
+ if (sessionId == null) {
+ Log.i(TAG, "Session Id not found")
+ callback.onFailure("Session Id not found")
+ return
+ }
val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
if (getCredRequest == null) {
@@ -95,7 +119,31 @@
GetCandidateCredentialsException> {
override fun onResult(result: GetCandidateCredentialsResponse) {
Log.i(TAG, "getCandidateCredentials onResponse")
- val fillResponse = convertToFillResponse(result, request)
+
+ if (result.getCredentialResponse != null) {
+ val autofillId: AutofillId? = result.getCredentialResponse
+ .credential.data.getParcelable(
+ CredentialProviderService.EXTRA_AUTOFILL_ID,
+ AutofillId::class.java)
+ Log.i(TAG, "getCandidateCredentials final response, autofillId: " +
+ autofillId)
+
+ if (autofillId != null) {
+ autofillCallback.autofill(
+ sessionId,
+ mutableListOf(autofillId),
+ mutableListOf(
+ AutofillValue.forText(
+ convertResponseToJson(result.getCredentialResponse)
+ )
+ ),
+ false)
+ }
+ return
+ }
+
+ val fillResponse = convertToFillResponse(result, request,
+ this@CredentialAutofillService)
if (fillResponse != null) {
callback.onSuccess(fillResponse)
} else {
@@ -115,10 +163,62 @@
callingPackage,
CancellationSignal(),
Executors.newSingleThreadExecutor(),
- outcome
+ outcome,
+ autofillCallback
)
}
+ // TODO(b/318118018): Use from Jetpack
+ private fun convertResponseToJson(response: GetCredentialResponse): String? {
+ try {
+ val jsonObject = JSONObject()
+ jsonObject.put("type", "get")
+ val jsonCred = JSONObject()
+ jsonCred.put("type", response.credential.type)
+ jsonCred.put("data", credentialToJSON(
+ response.credential))
+ jsonObject.put("credential", jsonCred)
+ return jsonObject.toString()
+ } catch (e: JSONException) {
+ Log.i(
+ TAG, "Exception while constructing response JSON: " +
+ e.message
+ )
+ }
+ return null
+ }
+
+ // TODO(b/318118018): Replace with calls to Jetpack
+ private fun credentialToJSON(credential: Credential): JSONObject? {
+ Log.i(TAG, "credentialToJSON")
+ try {
+ if (credential.type == "android.credentials.TYPE_PASSWORD_CREDENTIAL") {
+ Log.i(TAG, "toJSON PasswordCredential")
+
+ val json = JSONObject()
+ val id = credential.data.getString("androidx.credentials.BUNDLE_KEY_ID")
+ val pass = credential.data.getString("androidx.credentials.BUNDLE_KEY_PASSWORD")
+ json.put("androidx.credentials.BUNDLE_KEY_ID", id)
+ json.put("androidx.credentials.BUNDLE_KEY_PASSWORD", pass)
+ return json
+ } else if (credential.type == "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL") {
+ Log.i(TAG, "toJSON PublicKeyCredential")
+
+ val json = JSONObject()
+ val responseJson = credential
+ .data
+ .getString("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON")
+ json.put("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON",
+ responseJson)
+ return json
+ }
+ } catch (e: JSONException) {
+ Log.i(TAG, "issue while converting credential response to JSON")
+ }
+ Log.i(TAG, "Unsupported credential type")
+ return null
+ }
+
private fun getEntryToIconMap(
candidateProviderDataList: MutableList<GetCredentialProviderData>
): Map<String, Icon> {
@@ -150,14 +250,16 @@
private fun convertToFillResponse(
getCredResponse: GetCandidateCredentialsResponse,
- filLRequest: FillRequest
+ filLRequest: FillRequest,
+ context: Context
): FillResponse? {
val providerList = GetFlowUtils.toProviderList(
getCredResponse.candidateProviderDataList,
- this@CredentialAutofillService)
+ context)
if (providerList.isEmpty()) {
return null
}
+
val entryIconMap: Map<String, Icon> =
getEntryToIconMap(getCredResponse.candidateProviderDataList)
val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
@@ -166,7 +268,8 @@
var validFillResponse = false
autofillIdToProvidersMap.forEach { (autofillId, providers) ->
validFillResponse = processProvidersForAutofillId(
- filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder)
+ filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
+ getCredResponse.pendingIntent)
.or(validFillResponse)
}
if (!validFillResponse) {
@@ -180,7 +283,8 @@
autofillId: AutofillId,
providerList: List<ProviderInfo>,
entryIconMap: Map<String, Icon>,
- fillResponseBuilder: FillResponse.Builder
+ fillResponseBuilder: FillResponse.Builder,
+ bottomSheetPendingIntent: PendingIntent?
): Boolean {
if (providerList.isEmpty()) {
return false
@@ -227,9 +331,9 @@
?: getDefaultIcon()
}
// Create inline presentation
- var inlinePresentation: InlinePresentation? = null;
+ var inlinePresentation: InlinePresentation? = null
+ var spec: InlinePresentationSpec?
if (inlinePresentationSpecs != null) {
- val spec: InlinePresentationSpec
if (i < inlinePresentationSpecsCount) {
spec = inlinePresentationSpecs[i]
} else {
@@ -265,9 +369,52 @@
.build())
datasetAdded = true
}
+ val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
+ inlinePresentationSpecsCount)
+ if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
+ addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
+ fillResponseBuilder)
+ }
return datasetAdded
}
+ private fun getLastInlinePresentationSpec(
+ inlinePresentationSpecs: List<InlinePresentationSpec>?,
+ inlinePresentationSpecsCount: Int
+ ): InlinePresentationSpec? {
+ if (inlinePresentationSpecs != null) {
+ return inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+ }
+ return null
+ }
+
+ private fun addPinnedInlineSuggestion(
+ bottomSheetPendingIntent: PendingIntent,
+ spec: InlinePresentationSpec,
+ autofillId: AutofillId,
+ fillResponseBuilder: FillResponse.Builder
+ ) {
+ val dataSetBuilder = Dataset.Builder()
+ val sliceBuilder = InlineSuggestionUi
+ .newContentBuilder(bottomSheetPendingIntent)
+ .setStartIcon(Icon.createWithResource(this,
+ com.android.credentialmanager.R.drawable.ic_other_sign_in_24))
+ val presentationBuilder = Presentations.Builder()
+ .setInlinePresentation(InlinePresentation(
+ sliceBuilder.build().slice, spec, /* pinned= */ true))
+
+ fillResponseBuilder.addDataset(
+ dataSetBuilder
+ .setField(
+ autofillId,
+ Field.Builder().setPresentations(
+ presentationBuilder.build())
+ .build())
+ .setAuthentication(bottomSheetPendingIntent.intentSender)
+ .build()
+ )
+ }
+
/**
* Maps Autofill Id to provider list. For example, passing in a provider info
*
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/CheckboxPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/CheckboxPreference.kt
new file mode 100644
index 0000000..93d77d4
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/CheckboxPreference.kt
@@ -0,0 +1,181 @@
+/*
+ * 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.settingslib.spa.widget.preference
+
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.util.EntryHighlight
+import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
+import com.android.settingslib.spa.widget.ui.SettingsCheckbox
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+
+/**
+ * The widget model for [CheckboxPreference] widget.
+ */
+interface CheckboxPreferenceModel {
+ /**
+ * The title of this [CheckboxPreference].
+ */
+ val title: String
+
+ /**
+ * The summary of this [CheckboxPreference].
+ */
+ val summary: () -> String
+ get() = { "" }
+
+ /**
+ * The icon of this [Preference].
+ *
+ * Default is `null` which means no icon.
+ */
+ val icon: (@Composable () -> Unit)?
+ get() = null
+
+ /**
+ * Indicates whether this [CheckboxPreference] is checked.
+ *
+ * This can be `null` during the data loading before the data is available.
+ */
+ val checked: () -> Boolean?
+
+ /**
+ * Indicates whether this [CheckboxPreference] is changeable.
+ *
+ * Not changeable [CheckboxPreference] will be displayed in disabled style.
+ */
+ val changeable: () -> Boolean
+ get() = { true }
+
+ /**
+ * The checkbox change handler of this [CheckboxPreference].
+ *
+ * If `null`, this [CheckboxPreference] is not [toggleable].
+ */
+ val onCheckedChange: ((newChecked: Boolean) -> Unit)?
+}
+
+/**
+ * CheckboxPreference widget.
+ *
+ * Data is provided through [CheckboxPreferenceModel].
+ */
+@Composable
+fun CheckboxPreference(model: CheckboxPreferenceModel) {
+ EntryHighlight {
+ InternalCheckboxPreference(
+ title = model.title,
+ summary = model.summary,
+ icon = model.icon,
+ checked = model.checked(),
+ changeable = model.changeable(),
+ onCheckedChange = model.onCheckedChange,
+ )
+ }
+}
+
+@Composable
+internal fun InternalCheckboxPreference(
+ title: String,
+ summary: () -> String = { "" },
+ icon: @Composable (() -> Unit)? = null,
+ checked: Boolean?,
+ changeable: Boolean = true,
+ paddingStart: Dp = SettingsDimension.itemPaddingStart,
+ paddingEnd: Dp = SettingsDimension.itemPaddingEnd,
+ paddingVertical: Dp = SettingsDimension.itemPaddingVertical,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)?,
+) {
+ val indication = LocalIndication.current
+ val onChangeWithLog = wrapOnSwitchWithLog(onCheckedChange)
+ val interactionSource = remember { MutableInteractionSource() }
+ val modifier = remember(checked, changeable) {
+ if (checked != null && onChangeWithLog != null) {
+ Modifier.toggleable(
+ value = checked,
+ interactionSource = interactionSource,
+ indication = indication,
+ enabled = changeable,
+ role = Role.Checkbox,
+ onValueChange = onChangeWithLog,
+ )
+ } else Modifier
+ }
+ BasePreference(
+ title = title,
+ summary = summary,
+ modifier = modifier,
+ enabled = { changeable },
+ paddingStart = paddingStart,
+ paddingEnd = paddingEnd,
+ paddingVertical = paddingVertical,
+ icon = icon,
+ ) {
+ Spacer(Modifier.width(SettingsDimension.itemPaddingEnd))
+ SettingsCheckbox(
+ checked = checked,
+ changeable = { changeable },
+ // The onCheckedChange is handled on the whole CheckboxPreference.
+ // DO NOT set it on SettingsCheckbox.
+ onCheckedChange = null,
+ interactionSource = interactionSource,
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun CheckboxPreferencePreview() {
+ SettingsTheme {
+ Column {
+ InternalCheckboxPreference(
+ title = "Use Dark theme",
+ checked = true,
+ onCheckedChange = {},
+ )
+ InternalCheckboxPreference(
+ title = "Use Dark theme",
+ summary = { "Summary" },
+ checked = false,
+ onCheckedChange = {},
+ )
+ InternalCheckboxPreference(
+ title = "Use Dark theme",
+ summary = { "Summary" },
+ checked = true,
+ onCheckedChange = {},
+ icon = @Composable {
+ SettingsIcon(imageVector = Icons.Outlined.AirplanemodeActive)
+ },
+ )
+ }
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Checkbox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Checkbox.kt
new file mode 100644
index 0000000..7a9d46c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Checkbox.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.material3.Checkbox
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import com.android.settingslib.spa.framework.util.wrapOnSwitchWithLog
+
+@Composable
+internal fun SettingsCheckbox(
+ checked: Boolean?,
+ changeable: () -> Boolean,
+ onCheckedChange: ((newChecked: Boolean) -> Unit)? = null,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+) {
+ if (checked != null) {
+ Checkbox(
+ checked = checked,
+ onCheckedChange = wrapOnSwitchWithLog(onCheckedChange),
+ enabled = changeable(),
+ interactionSource = interactionSource,
+ )
+ } else {
+ Checkbox(
+ checked = false,
+ onCheckedChange = null,
+ enabled = false,
+ interactionSource = interactionSource,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/CheckboxPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/CheckboxPreferenceTest.kt
new file mode 100644
index 0000000..7375923
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/CheckboxPreferenceTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsOff
+import androidx.compose.ui.test.assertIsOn
+import androidx.compose.ui.test.isToggleable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class CheckboxPreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ testCheckboxPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("CheckboxPreference").assertIsDisplayed()
+ }
+
+ @Test
+ fun toggleable_initialStateIsCorrect() {
+ composeTestRule.setContent {
+ testCheckboxPreference(changeable = true)
+ }
+
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+
+ @Test
+ fun click_changeable_withEffect() {
+ composeTestRule.setContent {
+ testCheckboxPreference(changeable = true)
+ }
+
+ composeTestRule.onNodeWithText("CheckboxPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOn()
+ }
+
+ @Test
+ fun click_notChangeable_noEffect() {
+ composeTestRule.setContent {
+ testCheckboxPreference(changeable = false)
+ }
+
+ composeTestRule.onNodeWithText("CheckboxPreference").performClick()
+ composeTestRule.onNode(isToggleable()).assertIsOff()
+ }
+}
+
+@Composable
+private fun testCheckboxPreference(changeable: Boolean) {
+ var checked by rememberSaveable { mutableStateOf(false) }
+ CheckboxPreference(remember {
+ object : CheckboxPreferenceModel {
+ override val title = "CheckboxPreference"
+ override val checked = { checked }
+ override val changeable = { changeable }
+ override val onCheckedChange = { newChecked: Boolean -> checked = newChecked }
+ }
+ })
+}
\ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d9286b3..558a910 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -284,6 +284,13 @@
}
flag {
+ name: "new_volume_panel"
+ namespace: "systemui"
+ description: "Switches to the new volume panel (without Slices)."
+ bug: "202262476"
+}
+
+flag {
name: "screenshare_notification_hiding"
namespace: "systemui"
description: "Enable hiding of notifications during screenshare"
@@ -296,3 +303,10 @@
description: "Displays the auto on toggle in the bluetooth QS tile dialog"
bug: "316985153"
}
+
+flag {
+ name: "smartspace_relocate_to_bottom"
+ namespace: "systemui"
+ description: "Relocate Smartspace to bottom of the Lock Screen"
+ bug: "316212788"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 55fc3a2..17c4e02 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -194,6 +194,7 @@
items(
count = list.size,
key = { index -> list[index].key },
+ contentType = { index -> list[index].key },
span = { index -> GridItemSpan(list[index].size.span) },
) { index ->
val cardModifier = Modifier.width(Dimensions.CardWidth)
@@ -361,6 +362,8 @@
.createView(context, model.appWidgetId, model.providerInfo)
.apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
},
+ // For reusing composition in lazy lists.
+ onReset = {},
)
}
}
@@ -376,7 +379,7 @@
FrameLayout(context).apply { addView(model.remoteViews.apply(context, this)) }
},
// For reusing composition in lazy lists.
- onReset = {}
+ onReset = {},
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
index 976161b..8119d2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
@@ -31,6 +31,7 @@
import androidx.core.view.isVisible
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.qualifiers.KeyguardRootView
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.notifications.ui.composable.NotificationStack
import com.android.systemui.res.R
@@ -47,8 +48,9 @@
class ViewBasedLockscreenContent
@Inject
constructor(
- private val viewModel: LockscreenSceneViewModel,
+ private val lockscreenSceneViewModel: LockscreenSceneViewModel,
@KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
+ private val keyguardRootViewModel: KeyguardRootViewModel,
) {
@Composable
fun SceneScope.Content(
@@ -59,7 +61,7 @@
}
LockscreenLongPress(
- viewModel = viewModel.longPress,
+ viewModel = lockscreenSceneViewModel.longPress,
modifier = modifier,
) { onSettingsMenuPlaced ->
AndroidView(
@@ -74,7 +76,7 @@
)
val notificationStackPosition by
- viewModel.keyguardRoot.notificationBounds.collectAsState()
+ keyguardRootViewModel.notificationBounds.collectAsState()
Layout(
modifier =
@@ -92,7 +94,7 @@
},
content = {
NotificationStack(
- viewModel = viewModel.notifications,
+ viewModel = lockscreenSceneViewModel.notifications,
isScrimVisible = false,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
new file mode 100644
index 0000000..c418490
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalDensity
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.plugins.clocks.ClockController
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/** Produces a [BurnInState] that can be used to query the `LockscreenBurnInViewModel` flows. */
+@Composable
+fun rememberBurnIn(
+ clockInteractor: KeyguardClockInteractor,
+): BurnInState {
+ val clock by clockInteractor.currentClock.collectAsState()
+
+ val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) }
+ val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) }
+
+ val topmostTop =
+ when {
+ smartspaceTop != null && smallClockTop != null -> min(smartspaceTop, smallClockTop)
+ smartspaceTop != null -> smartspaceTop
+ smallClockTop != null -> smallClockTop
+ else -> 0f
+ }.roundToInt()
+
+ val params = rememberBurnInParameters(clock, topmostTop)
+
+ return remember(params, onSmartspaceTopChanged, onSmallClockTopChanged) {
+ BurnInState(
+ parameters = params,
+ onSmartspaceTopChanged = onSmartspaceTopChanged,
+ onSmallClockTopChanged = onSmallClockTopChanged,
+ )
+ }
+}
+
+@Composable
+private fun rememberBurnInParameters(
+ clock: ClockController?,
+ topmostTop: Int,
+): BurnInParameters {
+ val density = LocalDensity.current
+ val topInset = WindowInsets.systemBars.union(WindowInsets.displayCutout).getTop(density)
+
+ return remember(clock, topInset, topmostTop) {
+ BurnInParameters(
+ clockControllerProvider = { clock },
+ topInset = topInset,
+ statusViewTop = topmostTop,
+ )
+ }
+}
+
+data class BurnInState(
+ /** Parameters for use with the `LockscreenBurnInViewModel. */
+ val parameters: BurnInParameters,
+ /**
+ * Callback to invoke when the top coordinate of the smartspace element is updated, pass `null`
+ * when the element is not shown.
+ */
+ val onSmartspaceTopChanged: (Float?) -> Unit,
+ /**
+ * Callback to invoke when the top coordinate of the small clock element is updated, pass `null`
+ * when the element is not shown.
+ */
+ val onSmallClockTopChanged: (Float?) -> Unit,
+)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d9d98cb..7385a25 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -55,6 +56,7 @@
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
) : LockscreenSceneBlueprint {
override val id: String = "default"
@@ -62,6 +64,7 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -74,8 +77,19 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) {
+ SmallClock(
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
with(notificationSection) {
Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4704f5c..acd4779 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -24,6 +24,7 @@
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.IntRect
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -55,6 +56,7 @@
private val ambientIndicationSection: AmbientIndicationSection,
private val bottomAreaSection: BottomAreaSection,
private val settingsMenuSection: SettingsMenuSection,
+ private val clockInteractor: KeyguardClockInteractor,
) : LockscreenSceneBlueprint {
override val id: String = "shortcuts-besides-udfps"
@@ -62,6 +64,7 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
val isUdfpsVisible = viewModel.isUdfpsVisible
+ val burnIn = rememberBurnIn(clockInteractor)
LockscreenLongPress(
viewModel = viewModel.longPress,
@@ -74,8 +77,19 @@
modifier = Modifier.fillMaxWidth(),
) {
with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
- with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
- with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+ with(clockSection) {
+ SmallClock(
+ onTopChanged = burnIn.onSmallClockTopChanged,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+ with(smartSpaceSection) {
+ SmartSpace(
+ burnInParams = burnIn.parameters,
+ onTopChanged = burnIn.onSmartspaceTopChanged,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
with(notificationSection) {
Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
new file mode 100644
index 0000000..f9dd04b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.keyguard.ui.composable.modifier
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+
+/**
+ * Modifies the composable to account for anti-burn in translation, alpha, and scaling.
+ *
+ * Please override [isClock] as `true` if the composable is an element that's part of a clock.
+ */
+@Composable
+fun Modifier.burnInAware(
+ viewModel: AodBurnInViewModel,
+ params: BurnInParameters,
+ isClock: Boolean = false,
+): Modifier {
+ val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
+ val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
+ val alpha by viewModel.alpha.collectAsState(initial = 1f)
+ val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+
+ return this.graphicsLayer {
+ val scale =
+ when {
+ scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale
+ !scaleViewModel.scaleClockOnly -> scaleViewModel.scale
+ else -> 1f
+ }
+
+ this.translationX = translationX
+ this.translationY = translationY
+ this.alpha = alpha
+ this.scaleX = scale
+ this.scaleY = scale
+ }
+}
+
+/** Reports the "top" coordinate of the modified composable to the given [consumer]. */
+@Composable
+fun Modifier.onTopPlacementChanged(
+ consumer: (Float) -> Unit,
+): Modifier {
+ return onPlaced { coordinates -> consumer(coordinates.boundsInWindow().top) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index db20f65..4f3498e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -35,10 +35,10 @@
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -55,7 +55,7 @@
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val alphaViewModel: AodAlphaViewModel,
) {
/**
* Renders a single lockscreen shortcut.
@@ -101,7 +101,7 @@
) {
IndicationArea(
indicationAreaViewModel = indicationAreaViewModel,
- keyguardRootViewModel = keyguardRootViewModel,
+ alphaViewModel = alphaViewModel,
indicationController = indicationController,
)
}
@@ -179,7 +179,7 @@
@Composable
private fun IndicationArea(
indicationAreaViewModel: KeyguardIndicationAreaViewModel,
- keyguardRootViewModel: KeyguardRootViewModel,
+ alphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
modifier: Modifier = Modifier,
) {
@@ -192,7 +192,7 @@
KeyguardIndicationAreaBinder.bind(
view = view,
viewModel = indicationAreaViewModel,
- keyguardRootViewModel = keyguardRootViewModel,
+ aodAlphaViewModel = alphaViewModel,
indicationController = indicationController,
)
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index eaf8063..0b49922 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -26,6 +26,7 @@
import androidx.compose.ui.graphics.Color
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import javax.inject.Inject
@@ -35,8 +36,12 @@
private val viewModel: KeyguardClockViewModel,
) {
@Composable
- fun SceneScope.SmallClock(modifier: Modifier = Modifier) {
+ fun SceneScope.SmallClock(
+ onTopChanged: (top: Float?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
if (viewModel.useLargeClock) {
+ onTopChanged(null)
return
}
@@ -45,7 +50,10 @@
modifier = modifier,
) {
Box(
- modifier = Modifier.fillMaxWidth().background(Color.Magenta),
+ modifier =
+ Modifier.fillMaxWidth()
+ .background(Color.Magenta)
+ .onTopPlacementChanged(onTopChanged)
) {
Text(
text = "TODO(b/316211368): Small clock",
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 3c49cbc..9b71844 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -36,6 +36,10 @@
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
@@ -47,11 +51,16 @@
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+ private val aodBurnInViewModel: AodBurnInViewModel,
) {
@Composable
- fun SceneScope.SmartSpace(modifier: Modifier = Modifier) {
+ fun SceneScope.SmartSpace(
+ burnInParams: BurnInParameters,
+ onTopChanged: (top: Float?) -> Unit,
+ modifier: Modifier = Modifier,
+ ) {
Column(
- modifier = modifier.element(SmartSpaceElementKey),
+ modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
) {
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
return
@@ -71,9 +80,21 @@
start = paddingBelowClockStart,
),
) {
- Date()
+ Date(
+ modifier =
+ Modifier.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
Spacer(modifier = Modifier.width(4.dp))
- Weather()
+ Weather(
+ modifier =
+ Modifier.burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
+ )
}
}
@@ -84,6 +105,10 @@
start = paddingBelowClockStart,
end = paddingBelowClockEnd,
)
+ .burnInAware(
+ viewModel = aodBurnInViewModel,
+ params = burnInParams,
+ ),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index 6811eb4..5727e34 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -21,9 +21,11 @@
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
@@ -78,7 +80,7 @@
view
},
modifier =
- Modifier.fillMaxWidth().height {
+ Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
Utils.getStatusBarHeaderHeightKeyguard(context)
},
)
diff --git a/packages/SystemUI/docs/imgs/ribbon.png b/packages/SystemUI/docs/imgs/ribbon.png
new file mode 100644
index 0000000..9f57652
--- /dev/null
+++ b/packages/SystemUI/docs/imgs/ribbon.png
Binary files differ
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
new file mode 100644
index 0000000..3e4a1b4
--- /dev/null
+++ b/packages/SystemUI/docs/scene.md
@@ -0,0 +1,297 @@
+# The Scene Framework
+
+Known internally as "Flexiglass", this framework defines a graph where each node
+is a "scene" and each edge between the scenes is a transition. The scenes are
+the main components of System UI, on phones these are: the lockscreen, bouncer,
+shade, and quick settings panels/views/screens). Each scene is a standalone
+experience.
+
+The **main goal** of the framework is to increase code health by applying
+[Separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns)
+over several dimensions:
+
+1. Each scene is a standalone piece of UI; their code doesn't need to concern
+ itself with either transition animations or anything in other scenes. This
+ frees the developer to be able to focus only on the content of the UI for
+ that scene.
+2. Transition definitions (which scene leads to which other scene following
+ which user action) are pulled out and separated from the content of the UI.
+3. Transition animations (the effects that happen alongside the gradual change
+ from one scene to another) are also pulled out and separated from the
+ content of the UI.
+
+In addition to the above, some of the **secondary goals** are: 4. Make
+**customization easier**: by separating scenes to standalone pieces, it becomes
+possible for variant owners and OEMs to exclude or replace certain scenes or to
+add brand-new scenes. 5. **Enable modularization**: by separating scenes to
+standalone pieces, it becomes possible to break down System UI into smaller
+codebases, each one of which could be built on its own. Note: this isn't part of
+the scene framework itself but is something that can be done more easily once
+the scene framework is in place.
+
+## Terminology
+
+* **Scene** a collection of UI elements in a layout that, together, make up a
+ "screen" or "page" that is as large as the container. Scenes can be
+ navigated between / transition to/from. To learn more, please see
+ [this section](#Defining-a-scene).
+* **Element** (or "UI element") a single unit of UI within a scene. One scene
+ can arrange multiple elements within a layout structure.
+* **Transition** the gradual switching from one scene to another scene. There
+ are two kinds: [user-driven](Scene-navigation) and
+ [automatic](Automatic-scene-transitions) scene transitions.
+* **Transition animation** the set of UI effects that occurs while/during a
+ transition. These can apply to the entire scene or to specific elements in
+ the scene. To learn more, please see
+ [this section](#Scene-transition-animations).
+* **Scene container** (or just "container") the root piece of UI (typically a
+ `@Composable` function) that sets up all the scenes, their transitions, etc.
+ To learn more, please see [this section](#Scene-container).
+* **Container configuration** (or just "configuration") the collection of
+ scenes and some added information about the desired behaviour of a
+ container. To learn more, please see
+ [this section](#Scene-container-configuration).
+
+## Enabling the framework
+
+As of the end of 2023, the scene framework is under development; as such, it is
+disabled by default. For those who are interested in a preview, please follow
+the instructions below to turn it on.
+
+NOTE: in case these instructions become stale and don't actually enable the
+framework, please make sure `SceneContainerFlag.isEnabled` in the
+[`SceneContainerFlags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt)
+file evalutes to `true`.
+
+1. Set **`SCENE_CONTAINER_ENABLED`** to `true` in the
+ [`Flags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/flags/Flags.kt)
+ file
+2. Set the **`migrate_keyguard_status_bar_view`** classic flag to `true` by
+ running: `console $ adb shell statusbar cmd migrate_keyguard_status_bar_view
+ true`
+3. Set a collection of **aconfig flags** to `true` by running the following
+ commands: `console $ adb shell device_config put systemui
+ com.android.systemui.scene_container true $ adb shell device_config put
+ systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell
+ device_config put systemui
+ com.android.systemui.keyguard_shade_migration_nssl true $ adb shell
+ device_config put systemui com.android.systemui.media_in_scene_container
+ true`
+4. **Restart** System UI by issuing the following command: `console $ adb shell
+ am crash com.android.systemui`
+5. **Verify** that the scene framework was turned on. There are two ways to do
+ this:
+
+ *(a)* look for the sash/ribbon UI at the bottom-right corner of the display:
+ 
+
+ NOTE: this will be removed proper to the actual release of the framework.
+
+ *(b)* Turn on logging and look for the logging statements in `logcat`:
+ ```console
+
+ # Turn on logging from the framework:
+
+ $ adb shell cmd statusbar echo -b SceneFramework:verbose
+
+# Look for the log statements from the framework:
+
+$ adb logcat -v time SceneFramework:* *:S ```
+
+To **disable** the framework, simply turn off the main aconfig flag: `console $
+adb shell device_config put systemui com.android.systemui.scene_container false`
+
+## Defining a scene
+
+Each scene is defined as an implementation of the
+[`ComposableScene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt)
+interface, which has three parts: 1. The `key` property returns the
+[`SceneKey`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt)
+that uniquely identifies that scene 2. The `destinationScenes` `Flow` returns
+the (potentially ever-changing) set of navigation edges to other scenes, based
+on user-actions, which is how the navigation graph is defined (see
+[the Scene navigation](#Scene-navigation) section for more) 3. The `Content`
+function which uses
+[Jetpack Compose](https://developer.android.com/jetpack/compose) to declare of
+the UI itself. This is the UI "at rest", e.g. once there is no transition
+between any two scenes. The Scene Framework has other ways to define how the
+content of your UI changes with and throughout a transition to learn more please
+see the [Scene transition animations](#Scene-transition-animations) section
+
+For example: ```kotlin @SysUISingleton class YourScene @Inject constructor( //
+your dependencies here ) : ComposableScene { override val key =
+SceneKey.YourScene
+
+```
+override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+ MutableStateFlow<Map<UserAction, SceneModel>>(
+ mapOf(
+ // This is where scene navigation is defined, more on that below.
+ )
+ ).asStateFlow()
+
+@Composable
+override fun SceneScope.Content(
+ modifier: Modifier,
+) {
+ // This is where the UI is defined using Jetpack Compose.
+}
+```
+
+} ```
+
+### Injecting scenes
+
+Scenes are injected into the Dagger dependency graph from the
+[`SceneModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt;l=35-50;drc=564f233d5b597aedf06961c76e582464eebe8ba6).
+
+## Scene navigation
+
+As seen above, each scene is responsible for providing an observable `Flow` of a
+`Map` that connects `UserAction` (for example: swipe down, swipe up, back
+button/gesture, etc.) keys to `SceneModel` destinations. This is how the scene
+navigation graph is defined.
+
+NOTE: this controls *only* user-input based navigation. To learn about the other
+type of scene navigation, please see the
+[Automatic scene transitions](#Automatic-scene-transitions) section.
+
+Because this is a `Flow`, scene implemetations should feel free to emit new
+values over time. For example, the `Lockscreen` scene ties the "swipe up" user
+action to go to the `Bouncer` scene if the device is still locked or to go to
+the `Gone` scene if the device is unlocked, allowing the user to dismiss the
+lockscreen UI when not locked.
+
+## Scene transition animations
+
+The Scene Framework separates transition animations from content UI declaration
+by placing the definition of the former in a different location. This way,
+there's no longer a need to contaminate the content UI declaration with
+animation logic, a practice that becomes unscalable over time.
+
+Under the hood, the Scene Framework uses
+[`SceneTransitionLayout`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt),
+a `@Composable` function designed with scene graph and transitions in mind. In
+fact, the Scene Framework is merely a shallow wrapper around
+`SceneTransitionLayout`.
+
+The `SceneTransitionLayout` API requires the transitions to be passed-in
+separately from the scenes themselves. In System UI, the transitions can be
+found in
+[`SceneContainerTransitions`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt).
+As you can see, each possible scene-to-scene transition has its own builder,
+here's one example:
+
+```kotlin
+fun TransitionBuilder.lockscreenToShadeTransition() {
+ spec = tween(durationMillis = 500)
+
+ punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+ translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+ fractionRange(end = 0.5f) {
+ fade(Shade.Elements.ScrimBackground)
+ translate(
+ QuickSettings.Elements.CollapsedGrid,
+ Edge.Top,
+ startsOutsideLayoutBounds = false,
+ )
+ }
+ fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
+```
+
+Going through the example code: * The `spec` is the animation that should be
+invoked, in the example above, we use a `tween` animation with a duration of 500
+milliseconds * Then there's a series of function calls: `punchHole` applies a
+clip mask to the `Scrim` element in the destination scene (in this case it's the
+`Shade` scene) which has the position and size determined by the `bounds`
+parameter and the shape passed into the `shape` parameter. This lets the
+`Lockscreen` scene render "through" the `Shade` scene * The `translate` call
+shifts the `Scrim` element to/from the `Top` edge of the scene container * The
+first `fractionRange` wrapper tells the system to apply its contained functions
+only during the first half of the transition. Inside of it, we see a `fade` of
+the `ScrimBackground` element and a `translate` o the `CollpasedGrid` element
+to/from the `Top` edge * The second `fractionRange` only starts at the second
+half of the transition (e.g. when the previous one ends) and applies a `fade` on
+the `Notifications` element
+
+You can find the actual documentation for this API
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt).
+
+### Tagging elements
+
+As demonstrated above, elements within a scene can be addressed from transition
+defintions. In order to "tag" an element with a specific `ElementKey`, the
+[`element` modifier](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt)
+must be used on the composable that declared that element's UI:
+
+```kotlin
+Text(
+ text = "Some text",
+ modifier = Modifier.element(MyElements.SomeText),
+)
+```
+
+In addition to the ability to refer to a tagged element in transition
+definitions, if the same `ElementKey` is used for one element in the current
+scene and another element in the destination scene, the element is considered to
+be a **shared element**. As such, the framework automatically translates and
+scales the bounds of the shared element from its current bounds in the source
+scene to its final bounds in the destination scene.
+
+## Scene container
+
+To set up a scene framework instance, a scene container must be declared. This
+is the root of an entire scene graph that puts together the scenes, their
+transitions, and the configuration. The container is then added to a parent
+`@Composable` or `View` so it can be displayed.
+
+The default scene container in System UI is defined in the
+[`SceneContainer.kt` file](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt).
+
+### Scene container configuration
+
+The `SceneContainer` function is passed a few parameters including a view-model
+and a set of scenes. The exact details of what gets passed in depends on the
+[`SceneContainerConfig` object](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt)
+which is injected into the Dagger dependency graph
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt).
+
+## Automatic scene transitions
+
+The scene framework supports the ability for scenes to change automatically
+based on device state or events other than direct user input. For example: when
+the device is locked, there's an automatic scene transition to the `Lockscreen`
+scene.
+
+This logic is contained within the
+[`SceneContainerStartable`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt)
+class.
+
+## Side-effects
+
+Similarly to [the above](#Automatic-scene-transitions), the
+`SceneContainerStartable` also handles side-effects by updating other parts of
+the System UI codebase whenever internal scene framework state changes. As an
+example: the visibility of the `View` that contains our
+[scene container](#Scene-container) is updated every time there's a transition
+to or from the `Gone` scene.
+
+## Observing scene transition state
+
+There are a couple of ways to observe the transition state:
+
+1. [Easiest] using the `SceneScope` of the scene container, simply use the
+ `animateSharedXAsState` API, the full list is
+ [here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt).
+2. [Harder] if outside the `SceneScope` of the scene container, observe
+ [`SceneInteractor.transitionState`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt;l=88;drc=af57d5e49431c6728e7cf192bada88e0541ebf0c).
+
+## Dependency Injection
+
+The entire framework is provided into the Dagger dependency graph from the
+top-level Dagger module at
+[`SceneContainerFrameworkModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt)
+this puts together the scenes from `SceneModule`, the configuration from
+`SceneContainerConfigModule`, and the startable from
+`SceneContainerStartableModule`.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 1f8e29a..62084aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -169,6 +170,109 @@
}
@Test
+ fun smartspaceDynamicSizing_oneCard_fullSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 1,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.FULL,
+ )
+ )
+
+ @Test
+ fun smartspace_dynamicSizing_twoCards_halfSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 2,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.HALF,
+ CommunalContentSize.HALF,
+ )
+ )
+
+ @Test
+ fun smartspace_dynamicSizing_threeCards_thirdSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 3,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ )
+ )
+
+ @Test
+ fun smartspace_dynamicSizing_fourCards_oneFullAndThreeThirdSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 4,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.FULL,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ )
+ )
+
+ @Test
+ fun smartspace_dynamicSizing_fiveCards_twoHalfAndThreeThirdSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 5,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.HALF,
+ CommunalContentSize.HALF,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ )
+ )
+
+ @Test
+ fun smartspace_dynamicSizing_sixCards_allThirdSize() =
+ testSmartspaceDynamicSizing(
+ totalTargets = 6,
+ expectedSizes =
+ listOf(
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ CommunalContentSize.THIRD,
+ )
+ )
+
+ private fun testSmartspaceDynamicSizing(
+ totalTargets: Int,
+ expectedSizes: List<CommunalContentSize>,
+ ) =
+ testScope.runTest {
+ // Keyguard showing, and tutorial completed.
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+ tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+ val targets = mutableListOf<SmartspaceTarget>()
+ for (index in 0 until totalTargets) {
+ val target = mock(SmartspaceTarget::class.java)
+ whenever(target.smartspaceTargetId).thenReturn("target$index")
+ whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
+ targets.add(target)
+ }
+
+ smartspaceRepository.setCommunalSmartspaceTargets(targets)
+
+ val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+ assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
+ for (index in 0 until totalTargets) {
+ assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
+ }
+ }
+
+ @Test
fun umo_mediaPlaying_showsUmo() =
testScope.runTest {
// Tutorial completed.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
new file mode 100644
index 0000000..83782e2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodAlphaViewModelTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var occludedToLockscreenTransitionViewModel:
+ OccludedToLockscreenTransitionViewModel
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val occludedToLockscreenAlpha = MutableStateFlow(0f)
+
+ private lateinit var underTest: AodAlphaViewModel
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha)
+ .thenReturn(occludedToLockscreenAlpha)
+ kosmos.occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel
+
+ underTest = kosmos.aodAlphaViewModel
+ }
+
+ @Test
+ fun alpha() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ keyguardRepository.setKeyguardAlpha(0.1f)
+ assertThat(alpha).isEqualTo(0.1f)
+ keyguardRepository.setKeyguardAlpha(0.5f)
+ assertThat(alpha).isEqualTo(0.5f)
+ keyguardRepository.setKeyguardAlpha(0.2f)
+ assertThat(alpha).isEqualTo(0.2f)
+ keyguardRepository.setKeyguardAlpha(0f)
+ assertThat(alpha).isEqualTo(0f)
+ occludedToLockscreenAlpha.value = 0.8f
+ assertThat(alpha).isEqualTo(0.8f)
+ }
+
+ @Test
+ fun alpha_whenGone_equalsZero() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ keyguardRepository.setKeyguardAlpha(0.1f)
+ assertThat(alpha).isEqualTo(0f)
+ keyguardRepository.setKeyguardAlpha(0.5f)
+ assertThat(alpha).isEqualTo(0f)
+ keyguardRepository.setKeyguardAlpha(1f)
+ assertThat(alpha).isEqualTo(0f)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
new file mode 100644
index 0000000..0543bc2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.burnInInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodBurnInViewModelTest : SysuiTestCase() {
+
+ @Mock private lateinit var burnInInteractor: BurnInInteractor
+ @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private lateinit var underTest: AodBurnInViewModel
+
+ private var burnInParameters =
+ BurnInParameters(
+ clockControllerProvider = { clockController },
+ )
+ private val burnInFlow = MutableStateFlow(BurnInModel())
+ private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+ kosmos.burnInInteractor = burnInInteractor
+ whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+ .thenReturn(enterFromTopAnimationAlpha)
+ whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+ .thenReturn(emptyFlow())
+ kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel
+
+ underTest = kosmos.aodBurnInViewModel
+ }
+
+ @Test
+ fun translationY_initializedToZero() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ assertThat(translationY).isEqualTo(0)
+ }
+
+ @Test
+ fun translationAndScale_whenNotDozing() =
+ testScope.runTest {
+ val translationX by collectLastValue(underTest.translationX(burnInParameters))
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ val scale by collectLastValue(underTest.scale(burnInParameters))
+
+ // Set to not dozing (on lockscreen)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ ),
+ validateStep = false,
+ )
+
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = 30,
+ scale = 0.5f,
+ )
+
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 1f,
+ scaleClockOnly = true,
+ )
+ )
+ }
+
+ @Test
+ fun translationAndScale_whenFullyDozing() =
+ testScope.runTest {
+ burnInParameters = burnInParameters.copy(statusViewTop = 100)
+ val translationX by collectLastValue(underTest.translationX(burnInParameters))
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ val scale by collectLastValue(underTest.scale(burnInParameters))
+
+ // Set to dozing (on AOD)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ ),
+ validateStep = false,
+ )
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = 30,
+ scale = 0.5f,
+ )
+
+ assertThat(translationX).isEqualTo(20)
+ assertThat(translationY).isEqualTo(30)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 0.5f,
+ scaleClockOnly = true,
+ )
+ )
+
+ // Set to the beginning of GONE->AOD transition
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED
+ ),
+ validateStep = false,
+ )
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 1f,
+ scaleClockOnly = true,
+ )
+ )
+ }
+
+ @Test
+ fun translationAndScale_whenFullyDozing_staysOutOfTopInset() =
+ testScope.runTest {
+ burnInParameters =
+ burnInParameters.copy(
+ statusViewTop = 100,
+ topInset = 80,
+ )
+ val translationX by collectLastValue(underTest.translationX(burnInParameters))
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ val scale by collectLastValue(underTest.scale(burnInParameters))
+
+ // Set to dozing (on AOD)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ ),
+ validateStep = false,
+ )
+
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = -30,
+ scale = 0.5f,
+ )
+ assertThat(translationX).isEqualTo(20)
+ // -20 instead of -30, due to inset of 80
+ assertThat(translationY).isEqualTo(-20)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 0.5f,
+ scaleClockOnly = true,
+ )
+ )
+
+ // Set to the beginning of GONE->AOD transition
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED
+ ),
+ validateStep = false,
+ )
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale)
+ .isEqualTo(
+ BurnInScaleViewModel(
+ scale = 1f,
+ scaleClockOnly = true,
+ )
+ )
+ }
+
+ @Test
+ fun translationAndScale_useScaleOnly() =
+ testScope.runTest {
+ whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
+
+ val translationX by collectLastValue(underTest.translationX(burnInParameters))
+ val translationY by collectLastValue(underTest.translationY(burnInParameters))
+ val scale by collectLastValue(underTest.scale(burnInParameters))
+
+ // Set to dozing (on AOD)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GONE,
+ to = KeyguardState.AOD,
+ value = 1f,
+ transitionState = TransitionState.FINISHED
+ ),
+ validateStep = false,
+ )
+
+ // Trigger a change to the burn-in model
+ burnInFlow.value =
+ BurnInModel(
+ translationX = 20,
+ translationY = 30,
+ scale = 0.5f,
+ )
+
+ assertThat(translationX).isEqualTo(0)
+ assertThat(translationY).isEqualTo(0)
+ assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+ }
+
+ @Test
+ fun alpha() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.alpha)
+
+ enterFromTopAnimationAlpha.value = 0.2f
+ assertThat(alpha).isEqualTo(0.2f)
+
+ enterFromTopAnimationAlpha.value = 1f
+ assertThat(alpha).isEqualTo(1f)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
new file mode 100644
index 0000000..7c3dc97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardRootViewModelTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val screenOffAnimationController = kosmos.screenOffAnimationController
+ private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+ private val fakeNotificationsKeyguardViewStateRepository =
+ kosmos.fakeNotificationsKeyguardViewStateRepository
+ private val dozeParameters = kosmos.dozeParameters
+ private val underTest = kosmos.keyguardRootViewModel
+
+ @Before
+ fun setUp() {
+ mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+ mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
+ mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+ }
+
+ @Test
+ fun burnInLayerVisibility() =
+ testScope.runTest {
+ val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ value = 0f,
+ transitionState = TransitionState.STARTED
+ ),
+ validateStep = false,
+ )
+ assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.OFF,
+ to = KeyguardState.GONE,
+ testScope,
+ )
+ whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
+ runCurrent()
+
+ assertThat(isVisible?.value).isFalse()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_bypassEnabled() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ deviceEntryRepository.setBypassEnabled(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ }
+
+ @Test
+ fun iconContainer_isNotVisible_pulseExpanding_notBypassing() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
+ deviceEntryRepository.setBypassEnabled(false)
+ runCurrent()
+
+ assertThat(isVisible?.value).isEqualTo(false)
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(true)
+ fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParameters.alwaysOn).thenReturn(false)
+ fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParameters.alwaysOn).thenReturn(true)
+ whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
+ fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isFalse()
+ }
+
+ @Test
+ fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParameters.alwaysOn).thenReturn(true)
+ whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
+ fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.value).isTrue()
+ assertThat(isVisible?.isAnimating).isTrue()
+ }
+
+ @Test
+ fun isIconContainerVisible_stopAnimation() =
+ testScope.runTest {
+ val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+ runCurrent()
+ fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+ deviceEntryRepository.setBypassEnabled(false)
+ whenever(dozeParameters.alwaysOn).thenReturn(true)
+ whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
+ fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+ runCurrent()
+
+ assertThat(isVisible?.isAnimating).isEqualTo(true)
+ isVisible?.stopAnimating()
+ runCurrent()
+
+ assertThat(isVisible?.isAnimating).isEqualTo(false)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index d07836d..74d309c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -94,7 +97,6 @@
KeyguardLongPressViewModel(
interactor = mock(),
),
- keyguardRoot = utils.keyguardRootViewModel(),
notifications = utils.notificationsPlaceholderViewModel(),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 224903f..efd4f9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -153,7 +153,6 @@
KeyguardLongPressViewModel(
interactor = mock(),
),
- keyguardRoot = utils.keyguardRootViewModel(),
notifications = utils.notificationsPlaceholderViewModel(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8a1a2da..a4f90eb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -106,6 +106,7 @@
import javax.inject.Provider;
import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
@@ -136,6 +137,7 @@
private final Provider<UdfpsController> mUdfpsControllerFactory;
private final Provider<SideFpsController> mSidefpsControllerFactory;
private final CoroutineScope mApplicationCoroutineScope;
+ private Job mBiometricContextListenerJob = null;
// TODO: these should be migrated out once ready
@NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
@@ -914,7 +916,11 @@
@Override
public void setBiometricContextListener(IBiometricContextListener listener) {
- mLogContextInteractor.get().addBiometricContextListener(listener);
+ if (mBiometricContextListenerJob != null) {
+ mBiometricContextListenerJob.cancel(null);
+ }
+ mBiometricContextListenerJob =
+ mLogContextInteractor.get().addBiometricContextListener(listener);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 0f4e583..18fb895 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -24,6 +24,9 @@
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
+import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
+import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -133,12 +136,11 @@
target.featureType == SmartspaceTarget.FEATURE_TIMER &&
target.remoteViews != null
}
- .map Target@{ target ->
+ .mapIndexed Target@{ index, target ->
return@Target CommunalContentModel.Smartspace(
smartspaceTargetId = target.smartspaceTargetId,
remoteViews = target.remoteViews!!,
- // Smartspace always as HALF for now.
- size = CommunalContentSize.HALF,
+ size = dynamicContentSize(targets.size, index),
)
}
}
@@ -147,23 +149,50 @@
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
val tutorialContent: List<CommunalContentModel.Tutorial> =
listOf(
- CommunalContentModel.Tutorial(id = 0, CommunalContentSize.FULL),
- CommunalContentModel.Tutorial(id = 1, CommunalContentSize.THIRD),
- CommunalContentModel.Tutorial(id = 2, CommunalContentSize.THIRD),
- CommunalContentModel.Tutorial(id = 3, CommunalContentSize.THIRD),
- CommunalContentModel.Tutorial(id = 4, CommunalContentSize.HALF),
- CommunalContentModel.Tutorial(id = 5, CommunalContentSize.HALF),
- CommunalContentModel.Tutorial(id = 6, CommunalContentSize.HALF),
- CommunalContentModel.Tutorial(id = 7, CommunalContentSize.HALF),
+ CommunalContentModel.Tutorial(id = 0, FULL),
+ CommunalContentModel.Tutorial(id = 1, THIRD),
+ CommunalContentModel.Tutorial(id = 2, THIRD),
+ CommunalContentModel.Tutorial(id = 3, THIRD),
+ CommunalContentModel.Tutorial(id = 4, HALF),
+ CommunalContentModel.Tutorial(id = 5, HALF),
+ CommunalContentModel.Tutorial(id = 6, HALF),
+ CommunalContentModel.Tutorial(id = 7, HALF),
)
val umoContent: Flow<List<CommunalContentModel.Umo>> =
mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
if (mediaPlaying) {
// TODO(b/310254801): support HALF and FULL layouts
- flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD)))
+ flowOf(listOf(CommunalContentModel.Umo(THIRD)))
} else {
flowOf(emptyList())
}
}
+
+ companion object {
+ /**
+ * Calculates the content size dynamically based on the total number of contents of that
+ * type.
+ *
+ * Contents with the same type are expected to fill each column evenly. Currently there are
+ * three possible sizes. When the total number is 1, size for that content is [FULL], when
+ * the total number is 2, size for each is [HALF], and 3, size for each is [THIRD].
+ *
+ * When dynamic contents fill in multiple columns, the first column follows the algorithm
+ * above, and the remaining contents are packed in [THIRD]s. For example, when the total
+ * number if 4, the first one is [FULL], filling the column, and the remaining 3 are
+ * [THIRD].
+ *
+ * @param size The total number of contents of this type.
+ * @param index The index of the current content of this type.
+ */
+ private fun dynamicContentSize(size: Int, index: Int): CommunalContentSize {
+ val remainder = size % CommunalContentSize.entries.size
+ return CommunalContentSize.toSize(
+ span =
+ FULL.span /
+ if (index > remainder - 1) CommunalContentSize.entries.size else remainder
+ )
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index c903709..572794d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -30,5 +30,13 @@
HALF(3),
/** Content takes a third of the height of the column. */
- THIRD(2),
+ THIRD(2);
+
+ companion object {
+ /** Converts from span to communal content size. */
+ fun toSize(span: Int): CommunalContentSize {
+ return entries.find { it.span == span }
+ ?: throw Exception("Invalid span for communal content size")
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index af5d48d..50836fe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -39,6 +39,7 @@
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -83,6 +84,7 @@
private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
private val vibratorHelper: VibratorHelper,
private val falsingManager: FalsingManager,
+ private val aodAlphaViewModel: AodAlphaViewModel,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -126,7 +128,7 @@
KeyguardIndicationAreaBinder.bind(
notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- keyguardRootViewModel,
+ aodAlphaViewModel,
indicationController,
)
}
@@ -148,7 +150,6 @@
keyguardRootView,
keyguardRootViewModel,
configuration,
- featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
screenOffAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
new file mode 100644
index 0000000..70c2e6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+
+@SysUISingleton
+class FromGlanceableHubTransitionInteractor
+@Inject
+constructor(
+ override val transitionRepository: KeyguardTransitionRepository,
+ override val transitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(fromState = KeyguardState.GLANCEABLE_HUB) {
+ override fun start() {
+ if (!Flags.communalHub()) {
+ return
+ }
+ }
+
+ override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
+ return ValueAnimator().apply {
+ interpolator = Interpolators.LINEAR
+ duration = DEFAULT_DURATION.inWholeMilliseconds
+ }
+ }
+
+ companion object {
+ const val TAG = "FromGlanceableHubTransitionInteractor"
+ val DEFAULT_DURATION = 500.milliseconds
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index ba7b987..91f8420 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -42,6 +42,7 @@
is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+ is FromGlanceableHubTransitionInteractor -> Log.d(TAG, "Started $it")
is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
is FromAlternateBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 2d43897..fbf6936 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -96,6 +96,7 @@
KeyguardState.AOD -> false
KeyguardState.DREAMING -> true
KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+ KeyguardState.GLANCEABLE_HUB -> true
KeyguardState.ALTERNATE_BOUNCER -> true
KeyguardState.PRIMARY_BOUNCER -> true
KeyguardState.LOCKSCREEN -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 56f5529..d95c38e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -67,4 +67,10 @@
abstract fun fromAlternateBouncer(
impl: FromAlternateBouncerTransitionInteractor
): TransitionInteractor
+
+ @Binds
+ @IntoSet
+ abstract fun fromGlanceableHub(
+ impl: FromGlanceableHubTransitionInteractor
+ ): TransitionInteractor
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index f5bcab9..92612b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -62,6 +62,12 @@
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
*/
LOCKSCREEN,
+ /**
+ * Device is locked or on dream and user has swiped from the right edge to enter the glanceable
+ * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
+ * or dream, as well as swipe down for the notifications and up for the bouncer.
+ */
+ GLANCEABLE_HUB,
/*
* Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
* is being removed, but there are other cases where the user is swiping away keyguard, such as
@@ -95,6 +101,7 @@
DOZING -> false
DREAMING -> false
DREAMING_LOCKSCREEN_HOSTED -> false
+ GLANCEABLE_HUB -> true
AOD -> false
ALTERNATE_BOUNCER -> true
PRIMARY_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 4c33d90..7c1368a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,8 +23,8 @@
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
@@ -51,7 +51,7 @@
fun bind(
view: ViewGroup,
viewModel: KeyguardIndicationAreaViewModel,
- keyguardRootViewModel: KeyguardRootViewModel,
+ aodAlphaViewModel: AodAlphaViewModel,
indicationController: KeyguardIndicationController,
): DisposableHandle {
indicationController.setIndicationArea(view)
@@ -69,7 +69,7 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
if (keyguardBottomAreaRefactor()) {
- keyguardRootViewModel.alpha.collect { alpha ->
+ aodAlphaViewModel.alpha.collect { alpha ->
view.apply {
this.importantForAccessibility =
if (alpha == 0f) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index fad0370..2aebd99 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -42,9 +42,9 @@
import com.android.systemui.common.shared.model.TintedIcon
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -68,7 +68,10 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
/** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@@ -81,7 +84,6 @@
view: ViewGroup,
viewModel: KeyguardRootViewModel,
configuration: ConfigurationState,
- featureFlags: FeatureFlagsClassic,
occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
chipbarCoordinator: ChipbarCoordinator,
screenOffAnimationController: ScreenOffAnimationController,
@@ -108,6 +110,8 @@
}
}
+ val burnInParams = MutableStateFlow(BurnInParameters())
+
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -164,35 +168,41 @@
// large clock isn't added to burnInLayer due to its scale transition
// so we also need to add translation to it here
// same as translationX
- viewModel.translationY.collect { y ->
- childViews[burnInLayerId]?.translationY = y
- childViews[largeClockId]?.translationY = y
- }
- }
-
- launch {
- viewModel.translationX.collect { x ->
- childViews[burnInLayerId]?.translationX = x
- childViews[largeClockId]?.translationX = x
- }
- }
-
- launch {
- viewModel.scale.collect { (scale, scaleClockOnly) ->
- if (scaleClockOnly) {
- // For clocks except weather clock, we have scale transition
- // besides translate
- childViews[largeClockId]?.let {
- it.scaleX = scale
- it.scaleY = scale
- }
- } else {
- // For weather clock, large clock should have only scale
- // transition with other parts in burnInLayer
- childViews[burnInLayerId]?.scaleX = scale
- childViews[burnInLayerId]?.scaleY = scale
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationY(params) }
+ .collect { y ->
+ childViews[burnInLayerId]?.translationY = y
+ childViews[largeClockId]?.translationY = y
}
- }
+ }
+
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.translationX(params) }
+ .collect { x ->
+ childViews[burnInLayerId]?.translationX = x
+ childViews[largeClockId]?.translationX = x
+ }
+ }
+
+ launch {
+ burnInParams
+ .flatMapLatest { params -> viewModel.scale(params) }
+ .collect { scaleViewModel ->
+ if (scaleViewModel.scaleClockOnly) {
+ // For clocks except weather clock, we have scale transition
+ // besides translate
+ childViews[largeClockId]?.let {
+ it.scaleX = scaleViewModel.scale
+ it.scaleY = scaleViewModel.scale
+ }
+ } else {
+ // For weather clock, large clock should have only scale
+ // transition with other parts in burnInLayer
+ childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+ childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+ }
+ }
}
if (NotificationIconContainerRefactor.isEnabled) {
@@ -274,10 +284,12 @@
}
if (!migrateClocksToBlueprint()) {
- viewModel.clockControllerProvider = clockControllerProvider
+ burnInParams.update { current ->
+ current.copy(clockControllerProvider = clockControllerProvider)
+ }
}
- onLayoutChangeListener = OnLayoutChange(viewModel)
+ onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
view.addOnLayoutChangeListener(onLayoutChangeListener)
// Views will be added or removed after the call to bind(). This is needed to avoid many
@@ -296,7 +308,9 @@
view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
- viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top
+ burnInParams.update { current ->
+ current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+ }
insets
}
@@ -333,8 +347,10 @@
)
}
- private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) :
- OnLayoutChangeListener {
+ private class OnLayoutChange(
+ private val viewModel: KeyguardRootViewModel,
+ private val burnInParams: MutableStateFlow<BurnInParameters>,
+ ) : OnLayoutChangeListener {
override fun onLayoutChange(
view: View,
left: Int,
@@ -355,7 +371,7 @@
}
view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
- viewModel.statusViewTop = statusView.top
+ burnInParams.update { current -> current.copy(statusViewTop = statusView.top) }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 03e45fd..eb3afb7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -367,7 +367,6 @@
keyguardRootView,
keyguardRootViewModel,
configuration,
- featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
screenOffAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 66c137f..ea05c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -25,8 +25,8 @@
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import javax.inject.Inject
@@ -37,7 +37,7 @@
constructor(
private val context: Context,
private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
- private val keyguardRootViewModel: KeyguardRootViewModel,
+ private val aodAlphaViewModel: AodAlphaViewModel,
private val indicationController: KeyguardIndicationController,
) : KeyguardSection() {
private val indicationAreaViewId = R.id.keyguard_indication_area
@@ -56,7 +56,7 @@
KeyguardIndicationAreaBinder.bind(
constraintLayout.requireViewById(R.id.keyguard_indication_area),
keyguardIndicationAreaViewModel,
- keyguardRootViewModel,
+ aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
new file mode 100644
index 0000000..d4ea728
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+
+/** Models UI state for the alpha of the AOD (always-on display). */
+@SysUISingleton
+class AodAlphaViewModel
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+) {
+
+ /** The alpha level for the entire lockscreen while in AOD. */
+ val alpha: Flow<Float> =
+ combine(
+ keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
+ emit(0f)
+ },
+ merge(
+ keyguardInteractor.keyguardAlpha,
+ occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+ )
+ ) { transitionToGone, alpha ->
+ if (transitionToGone == 1f) {
+ // Ensures content is not visible when in GONE state
+ 0f
+ } else {
+ alpha
+ }
+ }
+ .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
new file mode 100644
index 0000000..780e323
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.Flags
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Models UI state for elements that need to apply anti-burn-in tactics when showing in AOD
+ * (always-on display).
+ */
+@SysUISingleton
+class AodBurnInViewModel
+@Inject
+constructor(
+ private val burnInInteractor: BurnInInteractor,
+ private val configurationInteractor: ConfigurationInteractor,
+ private val keyguardInteractor: KeyguardInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+ private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ private val keyguardClockViewModel: KeyguardClockViewModel,
+) {
+ /** Alpha for elements that appear and move during the animation -> AOD */
+ val alpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+
+ /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
+ fun translationX(
+ params: BurnInParameters,
+ ): Flow<Float> {
+ return burnIn(params).map { it.translationX.toFloat() }
+ }
+
+ /** Vertical translation for elements that need to apply anti-burn-in tactics. */
+ fun translationY(
+ params: BurnInParameters,
+ ): Flow<Float> {
+ return configurationInteractor
+ .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
+ .flatMapLatest { enterFromTopAmount ->
+ combine(
+ keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+ burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+ goneToAodTransitionViewModel
+ .enterFromTopTranslationY(enterFromTopAmount)
+ .onStart { emit(0f) },
+ occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
+ emit(0f)
+ },
+ ) {
+ keyguardTransitionY,
+ burnInTranslationY,
+ goneToAodTransitionTranslationY,
+ occludedToLockscreenTransitionTranslationY ->
+
+ // All values need to be combined for a smooth translation
+ keyguardTransitionY +
+ burnInTranslationY +
+ goneToAodTransitionTranslationY +
+ occludedToLockscreenTransitionTranslationY
+ }
+ }
+ .distinctUntilChanged()
+ }
+
+ /** Scale for elements that need to apply anti-burn-in tactics. */
+ fun scale(
+ params: BurnInParameters,
+ ): Flow<BurnInScaleViewModel> {
+ return burnIn(params).map {
+ BurnInScaleViewModel(
+ scale = it.scale,
+ scaleClockOnly = it.scaleClockOnly,
+ )
+ }
+ }
+
+ private fun burnIn(
+ params: BurnInParameters,
+ ): Flow<BurnInModel> {
+ return combine(
+ merge(
+ keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+ keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+ )
+ .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
+ burnInInteractor.keyguardBurnIn,
+ ) { interpolated, burnIn ->
+ val useScaleOnly =
+ (clockController(params.clockControllerProvider)
+ ?.get()
+ ?.config
+ ?.useAlternateSmartspaceAODTransition
+ ?: false) && keyguardClockViewModel.clockSize.value == KeyguardClockSwitch.LARGE
+
+ if (useScaleOnly) {
+ BurnInModel(
+ translationX = 0,
+ translationY = 0,
+ scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated),
+ )
+ } else {
+ // Ensure the desired translation doesn't encroach on the top inset
+ val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
+ val translationY =
+ if (Flags.migrateClocksToBlueprint()) {
+ burnInY
+ } else {
+ max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop
+ }
+
+ BurnInModel(
+ translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
+ translationY = translationY,
+ scale =
+ MathUtils.lerp(
+ /* start= */ burnIn.scale,
+ /* stop= */ 1f,
+ /* amount= */ 1f - interpolated,
+ ),
+ scaleClockOnly = true,
+ )
+ }
+ }
+ }
+
+ private fun clockController(
+ provider: Provider<ClockController>?,
+ ): Provider<ClockController>? {
+ return if (Flags.migrateClocksToBlueprint()) {
+ Provider { keyguardClockViewModel.clock }
+ } else {
+ provider
+ }
+ }
+}
+
+/** UI-sourced parameters to pass into the various methods of [AodBurnInViewModel]. */
+data class BurnInParameters(
+ val clockControllerProvider: Provider<ClockController>? = null,
+ /** System insets that keyguard needs to stay out of */
+ val topInset: Int = 0,
+ /** Status view top, without translation added in */
+ val statusViewTop: Int = 0,
+)
+
+/**
+ * Models UI state of the scaling to apply to elements that need to be scaled for anti-burn-in
+ * purposes.
+ */
+data class BurnInScaleViewModel(
+ val scale: Float = 1f,
+ /** Whether the scale only applies to clock UI elements. */
+ val scaleClockOnly: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 26dace0..5059e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -18,27 +18,17 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.graphics.Point
-import android.util.MathUtils
import android.view.View.VISIBLE
-import com.android.app.animation.Interpolators
-import com.android.keyguard.KeyguardClockSwitch.LARGE
-import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.Flags.newAodTransition
import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -49,51 +39,29 @@
import com.android.systemui.util.ui.toAnimatedValueFlow
import com.android.systemui.util.ui.zip
import javax.inject.Inject
-import javax.inject.Provider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
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.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardRootViewModel
@Inject
constructor(
- configurationInteractor: ConfigurationInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val dozeParameters: DozeParameters,
private val keyguardInteractor: KeyguardInteractor,
- private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
- private val burnInInteractor: BurnInInteractor,
- private val keyguardClockViewModel: KeyguardClockViewModel,
- private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
- private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
- private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+ aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
screenOffAnimationController: ScreenOffAnimationController,
- // TODO(b/310989341): remove after changing migrate_clocks_to_blueprint to aconfig
- private val featureFlags: FeatureFlagsClassic,
+ private val aodBurnInViewModel: AodBurnInViewModel,
+ aodAlphaViewModel: AodAlphaViewModel,
) {
- var clockControllerProvider: Provider<ClockController>? = null
- get() {
- if (migrateClocksToBlueprint()) {
- return Provider { keyguardClockViewModel.clock }
- } else {
- return field
- }
- }
-
- /** System insets that keyguard needs to stay out of */
- var topInset: Int = 0
- /** Status view top, without translation added in */
- var statusViewTop: Int = 0
val burnInLayerVisibility: Flow<Int> =
keyguardTransitionInteractor.startedKeyguardState
@@ -110,96 +78,25 @@
keyguardInteractor.notificationContainerBounds
/** An observable for the alpha level for the entire keyguard root view. */
- val alpha: Flow<Float> =
- combine(
- keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
- merge(
- keyguardInteractor.keyguardAlpha,
- occludedToLockscreenTransitionViewModel.lockscreenAlpha,
- )
- ) { transitionToGone, alpha ->
- if (transitionToGone == 1f) {
- // Ensures content is not visible when in GONE state
- 0f
- } else {
- alpha
- }
- }
- .distinctUntilChanged()
-
- private fun burnIn(): Flow<BurnInModel> {
- val dozingAmount: Flow<Float> =
- merge(
- keyguardTransitionInteractor.goneToAodTransition.map { it.value },
- keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
- )
-
- return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
- val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
- val useScaleOnly =
- (clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition
- ?: false) && keyguardClockViewModel.clockSize.value == LARGE
- if (useScaleOnly) {
- BurnInModel(
- translationX = 0,
- translationY = 0,
- scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
- )
- } else {
- // Ensure the desired translation doesn't encroach on the top inset
- val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt()
- val translationY =
- if (migrateClocksToBlueprint()) {
- burnInY
- } else {
- -(statusViewTop - Math.max(topInset, statusViewTop + burnInY))
- }
- BurnInModel(
- translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(),
- translationY = translationY,
- scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
- scaleClockOnly = true,
- )
- }
- }
- }
+ val alpha: Flow<Float> = aodAlphaViewModel.alpha
/** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
/** For elements that appear and move during the animation -> AOD */
- val burnInLayerAlpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+ val burnInLayerAlpha: Flow<Float> = aodBurnInViewModel.alpha
- val translationY: Flow<Float> =
- configurationInteractor
- .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
- .flatMapLatest { enterFromTopAmount ->
- combine(
- keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
- burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
- goneToAodTransitionViewModel
- .enterFromTopTranslationY(enterFromTopAmount)
- .onStart { emit(0f) },
- occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
- emit(0f)
- },
- ) {
- keyguardTransitionY,
- burnInTranslationY,
- goneToAodTransitionTranslationY,
- occludedToLockscreenTransitionTranslationY ->
- // All values need to be combined for a smooth translation
- keyguardTransitionY +
- burnInTranslationY +
- goneToAodTransitionTranslationY +
- occludedToLockscreenTransitionTranslationY
- }
- }
- .distinctUntilChanged()
+ fun translationY(params: BurnInParameters): Flow<Float> {
+ return aodBurnInViewModel.translationY(params)
+ }
- val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() }
+ fun translationX(params: BurnInParameters): Flow<Float> {
+ return aodBurnInViewModel.translationX(params)
+ }
- val scale: Flow<Pair<Float, Boolean>> = burnIn().map { Pair(it.scale, it.scaleClockOnly) }
+ fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
+ return aodBurnInViewModel.scale(params)
+ }
/** Is the notification icon container visible? */
val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 539db7f..2b28a71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -38,7 +38,6 @@
deviceEntryInteractor: DeviceEntryInteractor,
communalInteractor: CommunalInteractor,
val longPress: KeyguardLongPressViewModel,
- val keyguardRoot: KeyguardRootViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
/** The key of the scene we should switch to when swiping up. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ab69acb..3be60b7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -157,7 +157,10 @@
// If the hub is fully visible, send all touch events to it.
val communalVisible = hubShowing && !hubOccluded
if (communalVisible) {
- return communalContainerView.dispatchTouchEvent(ev)
+ communalContainerView.dispatchTouchEvent(ev)
+ // Return true regardless of dispatch result as some touches at the start of a gesture
+ // may return false from dispatchTouchEvent.
+ return true
}
if (edgeSwipeRegionWidth == 0) {
@@ -172,13 +175,19 @@
x >= communalContainerView.width - edgeSwipeRegionWidth
if (inOpeningSwipeRegion && !hubOccluded) {
isTrackingOpenGesture = true
- return communalContainerView.dispatchTouchEvent(ev)
+ communalContainerView.dispatchTouchEvent(ev)
+ // Return true regardless of dispatch result as some touches at the start of a
+ // gesture may return false from dispatchTouchEvent.
+ return true
}
} else if (isTrackingOpenGesture) {
if (isUp || isCancel) {
isTrackingOpenGesture = false
}
- return communalContainerView.dispatchTouchEvent(ev)
+ communalContainerView.dispatchTouchEvent(ev)
+ // Return true regardless of dispatch result as some touches at the start of a gesture
+ // may return false from dispatchTouchEvent.
+ return true
}
return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index be2c21b..f44401b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -325,8 +325,13 @@
override val cdmaRoaming: StateFlow<Boolean> =
telephonyPollingEvent
.mapLatest {
- val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
- cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+ try {
+ val cdmaEri = telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber
+ cdmaEri == ERI_ON || cdmaEri == ERI_FLASH
+ } catch (e: UnsupportedOperationException) {
+ // Handles the same as a function call failure
+ false
+ }
}
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 8dd33d5..1205dce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -21,11 +21,11 @@
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.res.R
import com.android.systemui.statusbar.KeyguardIndicationController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -38,8 +38,9 @@
@RunWith(JUnit4::class)
@SmallTest
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
+
@Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
- @Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel
+ @Mock private lateinit var aodAlphaViewModel: AodAlphaViewModel
@Mock private lateinit var indicationController: KeyguardIndicationController
private lateinit var underTest: DefaultIndicationAreaSection
@@ -51,7 +52,7 @@
DefaultIndicationAreaSection(
context,
keyguardIndicationAreaViewModel,
- keyguardRootViewModel,
+ aodAlphaViewModel,
indicationController,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
deleted file mode 100644
index ee1be10..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ /dev/null
@@ -1,498 +0,0 @@
-/*
- * 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.
- *
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.featureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
-import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.phone.screenOffAnimationController
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.isAnimating
-import com.android.systemui.util.ui.stopAnimating
-import com.android.systemui.util.ui.value
-import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.mockito.Answers
-import org.mockito.Mock
-import org.mockito.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.anyInt
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardRootViewModelTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val repository = kosmos.fakeKeyguardRepository
- private val configurationRepository = kosmos.fakeConfigurationRepository
- private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
- private val screenOffAnimationController = kosmos.screenOffAnimationController
- private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
- private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
- private val fakeNotificationsKeyguardViewStateRepository =
- kosmos.fakeNotificationsKeyguardViewStateRepository
- private val dozeParameters = kosmos.dozeParameters
- private lateinit var underTest: KeyguardRootViewModel
-
- @Mock private lateinit var burnInInteractor: BurnInInteractor
- private val burnInFlow = MutableStateFlow(BurnInModel())
-
- @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
- private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
-
- @Mock
- private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
- @Mock
- private lateinit var occludedToLockscreenTransitionViewModel:
- OccludedToLockscreenTransitionViewModel
- private val occludedToLockscreenTranslationY = MutableStateFlow(0f)
- private val occludedToLockscreenAlpha = MutableStateFlow(0f)
-
- @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
- mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
- mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
- whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
- .thenReturn(emptyFlow<Float>())
- whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
- .thenReturn(enterFromTopAnimationAlpha)
-
- whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
-
- whenever(occludedToLockscreenTransitionViewModel.lockscreenTranslationY)
- .thenReturn(occludedToLockscreenTranslationY)
- whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha)
- .thenReturn(occludedToLockscreenAlpha)
-
- underTest =
- KeyguardRootViewModel(
- configurationInteractor = kosmos.configurationInteractor,
- deviceEntryInteractor = kosmos.deviceEntryInteractor,
- dozeParameters = kosmos.dozeParameters,
- keyguardInteractor = kosmos.keyguardInteractor,
- keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
- notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor,
- burnInInteractor = burnInInteractor,
- keyguardClockViewModel = kosmos.keyguardClockViewModel,
- goneToAodTransitionViewModel = goneToAodTransitionViewModel,
- aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
- occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
- screenOffAnimationController = screenOffAnimationController,
- // TODO(b/310989341): remove after change to aconfig
- featureFlags = kosmos.featureFlagsClassic
- )
-
- underTest.clockControllerProvider = Provider { clockController }
- }
-
- @Test
- fun alpha() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
-
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.OFF,
- to = KeyguardState.LOCKSCREEN,
- testScope = testScope,
- )
-
- repository.setKeyguardAlpha(0.1f)
- assertThat(alpha).isEqualTo(0.1f)
- repository.setKeyguardAlpha(0.5f)
- assertThat(alpha).isEqualTo(0.5f)
- repository.setKeyguardAlpha(0.2f)
- assertThat(alpha).isEqualTo(0.2f)
- repository.setKeyguardAlpha(0f)
- assertThat(alpha).isEqualTo(0f)
- occludedToLockscreenAlpha.value = 0.8f
- assertThat(alpha).isEqualTo(0.8f)
- }
-
- @Test
- fun alphaWhenGoneEqualsZero() =
- testScope.runTest {
- val alpha by collectLastValue(underTest.alpha)
-
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope = testScope,
- )
-
- repository.setKeyguardAlpha(0.1f)
- assertThat(alpha).isEqualTo(0f)
- repository.setKeyguardAlpha(0.5f)
- assertThat(alpha).isEqualTo(0f)
- repository.setKeyguardAlpha(1f)
- assertThat(alpha).isEqualTo(0f)
- }
-
- @Test
- fun translationYInitialValueIsZero() =
- testScope.runTest {
- val translationY by collectLastValue(underTest.translationY)
- assertThat(translationY).isEqualTo(0)
- }
-
- @Test
- fun translationAndScaleFromBurnInNotDozing() =
- testScope.runTest {
- val translationX by collectLastValue(underTest.translationX)
- val translationY by collectLastValue(underTest.translationY)
- val scale by collectLastValue(underTest.scale)
-
- // Set to not dozing (on lockscreen)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
- value = 1f,
- transitionState = TransitionState.FINISHED
- ),
- validateStep = false,
- )
-
- // Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
-
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
- }
-
- @Test
- fun translationAndScaleFromBurnFullyDozing() =
- testScope.runTest {
- val translationX by collectLastValue(underTest.translationX)
- val translationY by collectLastValue(underTest.translationY)
- val scale by collectLastValue(underTest.scale)
-
- underTest.statusViewTop = 100
-
- // Set to dozing (on AOD)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED
- ),
- validateStep = false,
- )
- // Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
-
- assertThat(translationX).isEqualTo(20)
- assertThat(translationY).isEqualTo(30)
- assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
-
- // Set to the beginning of GONE->AOD transition
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED
- ),
- validateStep = false,
- )
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
- }
-
- @Test
- fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() =
- testScope.runTest {
- val translationX by collectLastValue(underTest.translationX)
- val translationY by collectLastValue(underTest.translationY)
- val scale by collectLastValue(underTest.scale)
-
- underTest.statusViewTop = 100
- underTest.topInset = 80
-
- // Set to dozing (on AOD)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED
- ),
- validateStep = false,
- )
-
- // Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = -30,
- scale = 0.5f,
- )
- assertThat(translationX).isEqualTo(20)
- // -20 instead of -30, due to inset of 80
- assertThat(translationY).isEqualTo(-20)
- assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
-
- // Set to the beginning of GONE->AOD transition
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED
- ),
- validateStep = false,
- )
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
- }
-
- @Test
- fun translationAndScaleFromBurnInUseScaleOnly() =
- testScope.runTest {
- whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
-
- val translationX by collectLastValue(underTest.translationX)
- val translationY by collectLastValue(underTest.translationY)
- val scale by collectLastValue(underTest.scale)
-
- // Set to dozing (on AOD)
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.GONE,
- to = KeyguardState.AOD,
- value = 1f,
- transitionState = TransitionState.FINISHED
- ),
- validateStep = false,
- )
-
- // Trigger a change to the burn-in model
- burnInFlow.value =
- BurnInModel(
- translationX = 20,
- translationY = 30,
- scale = 0.5f,
- )
-
- assertThat(translationX).isEqualTo(0)
- assertThat(translationY).isEqualTo(0)
- assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
- }
-
- @Test
- fun burnInLayerVisibility() =
- testScope.runTest {
- val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
-
- keyguardTransitionRepository.sendTransitionStep(
- TransitionStep(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- value = 0f,
- transitionState = TransitionState.STARTED
- ),
- validateStep = false,
- )
- assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
- }
-
- @Test
- fun burnInLayerAlpha() =
- testScope.runTest {
- val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
-
- enterFromTopAnimationAlpha.value = 0.2f
- assertThat(burnInLayerAlpha).isEqualTo(0.2f)
-
- enterFromTopAnimationAlpha.value = 1f
- assertThat(burnInLayerAlpha).isEqualTo(1f)
- }
-
- @Test
- fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.OFF,
- to = KeyguardState.GONE,
- testScope,
- )
- whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
- runCurrent()
-
- assertThat(isVisible?.value).isFalse()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun iconContainer_isVisible_bypassEnabled() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- deviceEntryRepository.setBypassEnabled(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- }
-
- @Test
- fun iconContainer_isNotVisible_pulseExpanding_notBypassing() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
- deviceEntryRepository.setBypassEnabled(false)
- runCurrent()
-
- assertThat(isVisible?.value).isEqualTo(false)
- }
-
- @Test
- fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(true)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isTrue()
- }
-
- @Test
- fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParameters.alwaysOn).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParameters.alwaysOn).thenReturn(true)
- whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isFalse()
- }
-
- @Test
- fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParameters.alwaysOn).thenReturn(true)
- whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.value).isTrue()
- assertThat(isVisible?.isAnimating).isTrue()
- }
-
- @Test
- fun isIconContainerVisible_stopAnimation() =
- testScope.runTest {
- val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
- runCurrent()
- fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
- deviceEntryRepository.setBypassEnabled(false)
- whenever(dozeParameters.alwaysOn).thenReturn(true)
- whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
- fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
- runCurrent()
-
- assertThat(isVisible?.isAnimating).isEqualTo(true)
- isVisible?.stopAnimating()
- runCurrent()
-
- assertThat(isVisible?.isAnimating).isEqualTo(false)
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
index b0d941d..a9d89a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.burnInInteractor by Fixture {
+var Kosmos.burnInInteractor by Fixture {
BurnInInteractor(
context = applicationContext,
burnInHelperWrapper = burnInHelperWrapper,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
new file mode 100644
index 0000000..a3955f7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.keyguardBottomAreaInteractor by Fixture {
+ KeyguardBottomAreaInteractor(
+ repository = keyguardRepository,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
new file mode 100644
index 0000000..6b89e0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.aodAlphaViewModel by Fixture {
+ AodAlphaViewModel(
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
new file mode 100644
index 0000000..35cfa89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.burnInInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.aodBurnInViewModel by Fixture {
+ AodBurnInViewModel(
+ burnInInteractor = burnInInteractor,
+ configurationInteractor = configurationInteractor,
+ keyguardInteractor = keyguardInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+ occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+ keyguardClockViewModel = keyguardClockViewModel,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
index 14e2cff..00ece14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
@@ -25,7 +25,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.goneToAodTransitionViewModel by Fixture {
+var Kosmos.goneToAodTransitionViewModel by Fixture {
GoneToAodTransitionViewModel(
interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 13ee747..933f50c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -18,10 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.burnInInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
@@ -33,18 +30,14 @@
val Kosmos.keyguardRootViewModel by Fixture {
KeyguardRootViewModel(
- configurationInteractor = configurationInteractor,
deviceEntryInteractor = deviceEntryInteractor,
dozeParameters = dozeParameters,
keyguardInteractor = keyguardInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
notificationsKeyguardInteractor = notificationsKeyguardInteractor,
- burnInInteractor = burnInInteractor,
- goneToAodTransitionViewModel = goneToAodTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
- occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
screenOffAnimationController = screenOffAnimationController,
- keyguardClockViewModel = keyguardClockViewModel,
- featureFlags = FakeFeatureFlagsClassic(),
+ aodBurnInViewModel = aodBurnInViewModel,
+ aodAlphaViewModel = aodAlphaViewModel,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index 5bbde2b..93ecb79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -26,7 +26,7 @@
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlinx.coroutines.ExperimentalCoroutinesApi
-val Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
+var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
OccludedToLockscreenTransitionViewModel(
interactor = keyguardTransitionInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4688658..6d0915b 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -38,6 +38,7 @@
import android.service.autofill.SaveRequest;
import android.text.format.DateUtils;
import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.ServiceConnector;
@@ -56,12 +57,22 @@
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+ private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+ new ComponentName("com.android.credentialmanager",
+ "com.android.credentialmanager.autofill.CredentialAutofillService");
+
private final FillServiceCallbacks mCallbacks;
private final Object mLock = new Object();
private CompletableFuture<FillResponse> mPendingFillRequest;
private int mPendingFillRequestId = INVALID_REQUEST_ID;
private final ComponentName mComponentName;
+ private final boolean mIsCredentialAutofillService;
+
+ public boolean isCredentialAutofillService() {
+ return mIsCredentialAutofillService;
+ }
+
public interface FillServiceCallbacks
extends AbstractRemoteService.VultureCallback<RemoteFillService> {
void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@@ -83,6 +94,7 @@
userId, IAutoFillService.Stub::asInterface);
mCallbacks = callbacks;
mComponentName = componentName;
+ mIsCredentialAutofillService = mComponentName.equals(CREDMAN_SERVICE_COMPONENT_NAME);
}
@Override // from ServiceConnector.Impl
@@ -117,6 +129,10 @@
super.addLast(iAutoFillServiceJob);
}
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
/**
* Cancel the currently pending request.
*
@@ -134,6 +150,78 @@
}
}
+ public void onFillCredentialRequest(@NonNull FillRequest request,
+ IAutoFillManagerClient autofillCallback) {
+ if (sVerbose) {
+ Slog.v(TAG, "onFillRequest:" + request);
+ }
+ AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+ AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>();
+
+ CompletableFuture<FillResponse> connectThenFillRequest = postAsync(remoteService -> {
+ if (sVerbose) {
+ Slog.v(TAG, "calling onFillRequest() for id=" + request.getId());
+ }
+
+ CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
+ remoteService.onFillCredentialRequest(request, new IFillCallback.Stub() {
+ @Override
+ public void onCancellable(ICancellationSignal cancellation) {
+ CompletableFuture<FillResponse> future = futureRef.get();
+ if (future != null && future.isCancelled()) {
+ dispatchCancellationSignal(cancellation);
+ } else {
+ cancellationSink.set(cancellation);
+ }
+ }
+
+ @Override
+ public void onSuccess(FillResponse response) {
+ fillRequest.complete(response);
+ }
+
+ @Override
+ public void onFailure(int requestId, CharSequence message) {
+ String errorMessage = message == null ? "" : String.valueOf(message);
+ fillRequest.completeExceptionally(
+ new RuntimeException(errorMessage));
+ }
+ }, autofillCallback);
+ return fillRequest;
+ }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+ futureRef.set(connectThenFillRequest);
+
+ synchronized (mLock) {
+ mPendingFillRequest = connectThenFillRequest;
+ mPendingFillRequestId = request.getId();
+ }
+
+ connectThenFillRequest.whenComplete((res, err) -> Handler.getMain().post(() -> {
+ synchronized (mLock) {
+ mPendingFillRequest = null;
+ mPendingFillRequestId = INVALID_REQUEST_ID;
+ }
+ if (mCallbacks == null) {
+ Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+ return;
+ }
+ if (err == null) {
+ mCallbacks.onFillRequestSuccess(request.getId(), res,
+ mComponentName.getPackageName(), request.getFlags());
+ } else {
+ Slog.e(TAG, "Error calling on fill request", err);
+ if (err instanceof TimeoutException) {
+ dispatchCancellationSignal(cancellationSink.get());
+ mCallbacks.onFillRequestTimeout(request.getId());
+ } else if (err instanceof CancellationException) {
+ dispatchCancellationSignal(cancellationSink.get());
+ } else {
+ mCallbacks.onFillRequestFailure(request.getId(), err.getMessage());
+ }
+ }
+ }));
+ }
+
public void onFillRequest(@NonNull FillRequest request) {
if (sVerbose) {
Slog.v(TAG, "onFillRequest:" + request);
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 4a6d5c9b..553ba12 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,14 +16,19 @@
package com.android.server.autofill;
+import static com.android.server.autofill.Session.SESSION_ID_KEY;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.IntentSender;
+import android.os.Bundle;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.inputmethod.InlineSuggestionsRequest;
/**
* Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -95,10 +100,33 @@
/**
* Requests a new fill response.
*/
- public void onFillRequest(FillRequest pendingFillRequest, int flag) {
+ public void onFillRequest(FillRequest pendingFillRequest,
+ InlineSuggestionsRequest pendingInlineSuggestionsRequest, int flag, int id,
+ IAutoFillManagerClient client) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
mLastFlag = flag;
- mRemoteFillService.onFillRequest(pendingFillRequest);
+ if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
+ Slog.v(TAG, "About to call CredAutofill service as secondary provider");
+ addSessionIdToClientState(pendingFillRequest, pendingInlineSuggestionsRequest, id);
+ mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+ } else {
+ mRemoteFillService.onFillRequest(pendingFillRequest);
+ }
+ }
+
+ private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
+ InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+ if (pendingFillRequest.getClientState() == null) {
+ pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
+ pendingFillRequest.getFillContexts(),
+ pendingFillRequest.getHints(),
+ new Bundle(),
+ pendingFillRequest.getFlags(),
+ pendingInlineSuggestionsRequest,
+ pendingFillRequest.getDelayedFillIntentSender());
+ }
+ pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+ return pendingFillRequest;
}
public void destroy() {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d71258a..a49f9db 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -234,6 +234,8 @@
new ComponentName("com.android.credentialmanager",
"com.android.credentialmanager.autofill.CredentialAutofillService");
+ static final String SESSION_ID_KEY = "session_id";
+
final Object mLock;
private final AutofillManagerServiceImpl mService;
@@ -383,7 +385,7 @@
*/
private boolean mHasCallback;
- /** Whether the session has credential provider as the primary provider. */
+ /** Whether the session has credential manager provider as the primary provider. */
private boolean mIsPrimaryCredential;
@GuardedBy("mLock")
@@ -723,9 +725,16 @@
&& mSecondaryProviderHandler != null) {
Slog.v(TAG, "Requesting fill response to secondary provider.");
mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
- mPendingFillRequest.getFlags());
+ mPendingInlineSuggestionsRequest,
+ mPendingFillRequest.getFlags(), id, mClient);
} else if (mRemoteFillService != null) {
- mRemoteFillService.onFillRequest(mPendingFillRequest);
+ if (mIsPrimaryCredential) {
+ mPendingFillRequest = addSessionIdToClientState(mPendingFillRequest,
+ mPendingInlineSuggestionsRequest, id);
+ mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
+ } else {
+ mRemoteFillService.onFillRequest(mPendingFillRequest);
+ }
}
mPendingInlineSuggestionsRequest = null;
mWaitForInlineRequest = false;
@@ -868,6 +877,21 @@
}
}
+ private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
+ InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+ if (pendingFillRequest.getClientState() == null) {
+ pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
+ pendingFillRequest.getFillContexts(),
+ pendingFillRequest.getHints(),
+ new Bundle(),
+ pendingFillRequest.getFlags(),
+ pendingInlineSuggestionsRequest,
+ pendingFillRequest.getDelayedFillIntentSender());
+ }
+ pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+ return pendingFillRequest;
+ }
+
/**
* Get the list of valid autofill hint types from Device flags
* Returns empty list if PCC is off or no types available
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 6964763..52a8f9e 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -37,6 +37,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociatedDevice;
import android.companion.AssociationInfo;
@@ -398,7 +399,11 @@
pendingIntent = PendingIntent.getActivityAsUser(
mContext, /*requestCode */ packageUid, intent,
FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
- /* options= */ null, UserHandle.CURRENT);
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
+ UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index bd646fa..4e471f5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
@@ -186,7 +187,11 @@
final long token = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent,
- FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null,
+ FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(),
UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 848a2b0..57c52c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -402,7 +402,7 @@
case "get-bg-restriction-level":
return runGetBgRestrictionLevel(pw);
case "observe-foreground-process":
- return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
+ return runGetCurrentForegroundProcess(pw, mInternal);
case "reset-dropbox-rate-limiter":
return runResetDropboxRateLimiter();
case "list-displays-for-starting-users":
@@ -3690,11 +3690,10 @@
return -1;
}
- private int runGetCurrentForegroundProcess(PrintWriter pw,
- IActivityManager iam, IActivityTaskManager iatm)
+ private int runGetCurrentForegroundProcess(PrintWriter pw, IActivityManager iam)
throws RemoteException {
- ProcessObserver observer = new ProcessObserver(pw, iam, iatm, mInternal);
+ ProcessObserver observer = new ProcessObserver(pw, iam);
iam.registerProcessObserver(observer);
final InputStream mInput = getRawInputStream();
@@ -3729,15 +3728,10 @@
private PrintWriter mPw;
private IActivityManager mIam;
- private IActivityTaskManager mIatm;
- private ActivityManagerService mInternal;
- ProcessObserver(PrintWriter mPw, IActivityManager mIam,
- IActivityTaskManager mIatm, ActivityManagerService ams) {
+ ProcessObserver(PrintWriter mPw, IActivityManager mIam) {
this.mPw = mPw;
this.mIam = mIam;
- this.mIatm = mIatm;
- this.mInternal = ams;
}
@Override
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0d647c..0185c22 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -163,6 +163,7 @@
"pixel_audio_android",
"pixel_bluetooth",
"pixel_system_sw_touch",
+ "pixel_system_sw_usb",
"pixel_watch",
"platform_security",
"power",
@@ -181,6 +182,7 @@
"tv_system_ui",
"usb",
"vibrator",
+ "virtualization",
"virtual_devices",
"wallet_integration",
"wear_calling_messaging",
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d80638a..203ac2c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -2849,6 +2850,11 @@
verifyIncomingUid(uid);
verifyIncomingOp(code);
if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+ + "package: " + packageName + " and uid: " + uid + " is invalid");
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -2877,6 +2883,13 @@
}
} catch (SecurityException e) {
logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " verifyAndGetBypass returned a SecurityException for package: "
+ + packageName + " and uid: " + uid + " and attributionTag: "
+ + attributionTag, e);
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -2890,6 +2903,11 @@
if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+ " package " + packageName + "flags: " +
AppOpsManager.flagsToString(flags));
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " #getOpsLocked returned null");
+ }
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
@@ -2930,6 +2948,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
uidMode);
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " uid mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
}
} else {
@@ -2949,6 +2972,11 @@
attributedOp.rejected(uidState.getState(), flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
mode);
+ // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+ if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+ Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+ + " package mode is MODE_ERRORED");
+ }
return new SyncNotedAppOp(mode, code, attributionTag, packageName);
}
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 5ef89ad..a5939e9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -17,6 +17,7 @@
package com.android.server.location.gnss;
import android.content.Context;
+import android.location.flags.Flags;
import android.os.PersistableBundle;
import android.os.SystemProperties;
import android.telephony.CarrierConfigManager;
@@ -36,6 +37,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
@@ -275,6 +277,11 @@
}
loadPropertiesFromCarrierConfig(inEmergency, activeSubId);
+ if (Flags.gnssConfigurationFromResource()) {
+ // Overlay carrier properties from resources.
+ loadPropertiesFromResource(mContext, mProperties);
+ }
+
if (isSimAbsent(mContext)) {
// Use the default SIM's LPP profile when SIM is absent.
String lpp_prof = SystemProperties.get(LPP_PROFILE);
@@ -382,7 +389,7 @@
if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
String key = configKey
.substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
- .toUpperCase();
+ .toUpperCase(Locale.ROOT);
Object value = configs.get(configKey);
if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
if (value instanceof String) {
@@ -410,6 +417,24 @@
}
}
+ private void loadPropertiesFromResource(Context context,
+ Properties properties) {
+ String[] configValues = context.getResources().getStringArray(
+ com.android.internal.R.array.config_gnssParameters);
+ for (String item : configValues) {
+ if (DEBUG) Log.d(TAG, "GnssParamsResource: " + item);
+ // We need to support "KEY =", but not "=VALUE".
+ int index = item.indexOf("=");
+ if (index > 0 && index + 1 < item.length()) {
+ String key = item.substring(0, index);
+ String value = item.substring(index + 1);
+ properties.setProperty(key.trim().toUpperCase(Locale.ROOT), value);
+ } else {
+ Log.w(TAG, "malformed contents: " + item);
+ }
+ }
+ }
+
private int getRangeCheckedConfigEsExtensionSec() {
int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 000b6d2..06a8d98 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2051,6 +2051,7 @@
String indent = prefix + " ";
pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
+ pw.println(indent + "mTargetPackageName=" + mTargetPackageName);
pw.println(indent + "mManagerId=" + mManagerId);
pw.println(indent + "mOwnerUid=" + mOwnerUid);
pw.println(indent + "mOwnerPid=" + mOwnerPid);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2305d6c..75b4531 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2285,6 +2285,11 @@
throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
+ " is a demo user");
}
+
+ if (SystemProperties.getBoolean("ro.boot.arc_demo_mode", false)) {
+ return true;
+ }
+
synchronized (mUsersLock) {
UserInfo userInfo = getUserInfoLU(userId);
return userInfo != null && userInfo.isDemo();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 10e6edc..d683855 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1229,11 +1229,6 @@
sPlatformPermissions.put(permission, permissionInfo);
}
} catch (PackageManager.NameNotFoundException ignored) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
- + " not found when retrieving permission info");
- }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
}
@@ -1353,34 +1348,17 @@
// way we can avoid the datasource creating an attribution context for every call.
if (!(fromDatasource && current.equals(attributionSource))
&& next != null && !current.isTrusted(context)) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
- + current + " attribution source isn't a data source and "
- + current + " isn't trusted");
- }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
// If we already checked the permission for this one, skip the work
if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
permission, current)) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
- + " aren't skipping permission checks and permission check returns"
- + " false for " + current);
- }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
if (next != null && !checkPermission(context, permissionManagerServiceInt,
permission, next)) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
- + " permission check returns false for next source " + next);
- }
return PermissionChecker.PERMISSION_HARD_DENIED;
}
@@ -1697,12 +1675,6 @@
final AttributionSource resolvedAttributionSource = resolveAttributionSource(
context, accessorSource);
if (resolvedAttributionSource.getPackageName() == null) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (op == OP_BLUETOOTH_CONNECT) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
- + "package name for " + resolvedAttributionSource + " returned"
- + " null");
- }
return AppOpsManager.MODE_ERRORED;
}
int notedOp = op;
@@ -1716,13 +1688,6 @@
if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
if (checkedOpResult == MODE_ERRORED) {
- // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
- if (op == OP_BLUETOOTH_CONNECT) {
- Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
- + " checkOp for resolvedAttributionSource "
- + resolvedAttributionSource + " and op " + op
- + " returned MODE_ERRORED");
- }
return checkedOpResult;
}
notedOp = attributedOp;
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 59b55bf7..0a7872f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,11 +17,13 @@
package com.android.server.vibrator;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.os.IExternalVibratorService;
import android.os.VibrationEffect;
import android.os.Vibrator;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.Slog;
@@ -56,6 +58,8 @@
private final VibrationSettings mSettingsController;
private final int mDefaultVibrationAmplitude;
+ private SparseArray<Float> mAdaptiveHapticsScales;
+
VibrationScaler(Context context, VibrationSettings settingsController) {
mSettingsController = settingsController;
mDefaultVibrationAmplitude = context.getResources().getInteger(
@@ -140,6 +144,15 @@
if (scaleLevel != null) {
segment = segment.scale(scaleLevel.factor);
}
+
+ // If adaptive haptics scaling is available for this usage, apply it to the segment.
+ if (Flags.adaptiveHapticsEnabled()
+ && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
+ && mAdaptiveHapticsScales.contains(usageHint)) {
+ float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
+ segment = segment.scale(adaptiveScale);
+ }
+
segments.set(i, segment);
}
if (segments.equals(composedEffect.getSegments())) {
@@ -173,6 +186,16 @@
return prebaked.applyEffectStrength(newEffectStrength);
}
+ /**
+ * Updates the adaptive haptics scales.
+ * @param scales the new vibration scales to apply.
+ *
+ * @hide
+ */
+ public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
+ mAdaptiveHapticsScales = scales;
+ }
+
/** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
private static int intensityToEffectStrength(int intensity) {
switch (intensity) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 2eeb903..9d75249 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -16,14 +16,26 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.frameworks.vibrator.IVibratorControlService;
import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.ScaleParam;
import android.frameworks.vibrator.VibrationParam;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
+import android.util.SparseArray;
import java.util.Objects;
@@ -37,10 +49,13 @@
private static final String TAG = "VibratorControlService";
private final VibratorControllerHolder mVibratorControllerHolder;
+ private final VibrationScaler mVibrationScaler;
private final Object mLock;
- public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
+ public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
+ VibrationScaler vibrationScaler, Object lock) {
mVibratorControllerHolder = vibratorControllerHolder;
+ mVibrationScaler = vibrationScaler;
mLock = lock;
}
@@ -70,25 +85,62 @@
+ "controller doesn't match the registered one. " + this);
return;
}
+ updateAdaptiveHapticsScales(/* params= */ null);
mVibratorControllerHolder.setVibratorController(null);
}
}
@Override
- public void setVibrationParams(
- @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
- throws RemoteException {
- // TODO(b/305939964): Add set vibration implementation.
+ public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
+ @NonNull IVibratorController token) throws RemoteException {
+ Objects.requireNonNull(token);
+
+ synchronized (mLock) {
+ if (mVibratorControllerHolder.getVibratorController() == null) {
+ Slog.w(TAG, "Received request to set VibrationParams for IVibratorController = "
+ + token + ", but no controller was previously registered. Request "
+ + "Ignored.");
+ return;
+ }
+ if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+ token.asBinder())) {
+ Slog.wtf(TAG, "Failed to set new VibrationParams. The provided "
+ + "controller doesn't match the registered one. " + this);
+ return;
+ }
+
+ updateAdaptiveHapticsScales(params);
+ }
}
@Override
- public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
- // TODO(b/305939964): Add clear vibration implementation.
+ public void clearVibrationParams(int types, @NonNull IVibratorController token)
+ throws RemoteException {
+ Objects.requireNonNull(token);
+
+ synchronized (mLock) {
+ if (mVibratorControllerHolder.getVibratorController() == null) {
+ Slog.w(TAG, "Received request to clear VibrationParams for IVibratorController = "
+ + token + ", but no controller was previously registered. Request "
+ + "Ignored.");
+ return;
+ }
+ if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+ token.asBinder())) {
+ Slog.wtf(TAG, "Failed to clear VibrationParams. The provided "
+ + "controller doesn't match the registered one. " + this);
+ return;
+ }
+ //TODO(305942827): Update this method to only clear the specified vibration types.
+ // Perhaps look into whether it makes more sense to have this clear all scales and
+ // rely on setVibrationParams for clearing the scales for specific vibrations.
+ updateAdaptiveHapticsScales(/* params= */ null);
+ }
}
@Override
public void onRequestVibrationParamsComplete(
- IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
+ @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
throws RemoteException {
// TODO(305942827): Cache the vibration params in VibrationScaler
}
@@ -102,4 +154,52 @@
public String getInterfaceHash() throws RemoteException {
return this.HASH;
}
+
+ /**
+ * Extracts the vibration scales and caches them in {@link VibrationScaler}.
+ *
+ * @param params the new vibration params to cache.
+ */
+ private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+ if (params == null || params.length == 0) {
+ mVibrationScaler.updateAdaptiveHapticsScales(null);
+ return;
+ }
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ for (int i = 0; i < params.length; i++) {
+ ScaleParam scaleParam = params[i].getScale();
+ extractVibrationScales(scaleParam, vibrationScales);
+ }
+ mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
+ }
+
+ /**
+ * Extracts the vibration scales and map them to their corresponding
+ * {@link android.os.VibrationAttributes} usages.
+ */
+ private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
+ if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
+ vibrationScales.put(USAGE_ALARM, scaleParam.scale);
+ }
+
+ if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
+ vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
+ vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
+ }
+
+ if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
+ vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
+ }
+
+ if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
+ vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
+ vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
+ }
+
+ if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
+ vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
+ vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index fc824ab..2c1ab95 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -273,7 +273,8 @@
injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
injector.addService(VIBRATOR_CONTROL_SERVICE,
- new VibratorControlService(new VibratorControllerHolder(), mLock));
+ new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
+ mLock));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1e003da..a43e7d5 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -495,10 +495,7 @@
private final boolean componentSpecified; // did caller specify an explicit component?
final boolean rootVoiceInteraction; // was this the root activity of a voice interaction?
- private CharSequence nonLocalizedLabel; // the label information from the package mgr.
- private int labelRes; // the label information from the package mgr.
- private int icon; // resource identifier of activity's icon.
- private int theme; // resource identifier of activity's theme.
+ private final int theme; // resource identifier of activity's theme.
private Task task; // the task this is in.
private long createTime = System.currentTimeMillis();
long lastVisibleTime; // last time this activity became visible
@@ -506,7 +503,7 @@
long launchTickTime; // base time for launch tick messages
long topResumedStateLossTime; // last time we reported top resumed state loss to an activity
// Last configuration reported to the activity in the client process.
- private MergedConfiguration mLastReportedConfiguration;
+ private final MergedConfiguration mLastReportedConfiguration;
private int mLastReportedDisplayId;
boolean mLastReportedMultiWindowMode;
boolean mLastReportedPictureInPictureMode;
@@ -1058,17 +1055,15 @@
pw.print(prefix); pw.print("taskAffinity="); pw.println(taskAffinity);
pw.print(prefix); pw.print("mActivityComponent=");
pw.println(mActivityComponent.flattenToShortString());
- if (info != null && info.applicationInfo != null) {
- final ApplicationInfo appInfo = info.applicationInfo;
- pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir);
- if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
- pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir);
- }
- pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir);
- if (appInfo.splitSourceDirs != null) {
- pw.print(prefix); pw.print("splitDir=");
- pw.println(Arrays.toString(appInfo.splitSourceDirs));
- }
+ final ApplicationInfo appInfo = info.applicationInfo;
+ pw.print(prefix); pw.print("baseDir="); pw.println(appInfo.sourceDir);
+ if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+ pw.print(prefix); pw.print("resDir="); pw.println(appInfo.publicSourceDir);
+ }
+ pw.print(prefix); pw.print("dataDir="); pw.println(appInfo.dataDir);
+ if (appInfo.splitSourceDirs != null) {
+ pw.print(prefix); pw.print("splitDir=");
+ pw.println(Arrays.toString(appInfo.splitSourceDirs));
}
pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded);
pw.print(" componentSpecified="); pw.print(componentSpecified);
@@ -1079,8 +1074,6 @@
}
pw.print(prefix); pw.print("compat=");
pw.print(mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo));
- pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
- pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
pw.println(prefix + "mLastReportedConfigurations:");
mLastReportedConfiguration.dump(pw, prefix + " ");
@@ -2157,14 +2150,6 @@
sConstrainDisplayApisConfig = new ConstrainDisplayApisConfig();
}
stateNotNeeded = (aInfo.flags & FLAG_STATE_NOT_NEEDED) != 0;
- nonLocalizedLabel = aInfo.nonLocalizedLabel;
- labelRes = aInfo.labelRes;
- if (nonLocalizedLabel == null && labelRes == 0) {
- ApplicationInfo app = aInfo.applicationInfo;
- nonLocalizedLabel = app.nonLocalizedLabel;
- labelRes = app.labelRes;
- }
- icon = aInfo.getIconResource();
theme = aInfo.getThemeResource();
if ((aInfo.flags & FLAG_MULTIPROCESS) != 0 && _caller != null
&& (aInfo.applicationInfo.uid == SYSTEM_UID
@@ -8079,8 +8064,7 @@
// The {@link ActivityRecord} should only specify an orientation when it is not closing.
// Allowing closing {@link ActivityRecord} to participate can lead to an Activity in another
// task being started in the wrong orientation during the transition.
- if (!getDisplayContent().mClosingApps.contains(this)
- && (isVisibleRequested() || getDisplayContent().mOpeningApps.contains(this))) {
+ if (isVisibleRequested()) {
return getOverrideOrientation();
}
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index e4eb7b3..c7df83a 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -24,6 +24,7 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.Trace;
import android.util.ArrayMap;
import android.util.Slog;
@@ -146,9 +147,10 @@
/** Executes all the pending transactions. */
void dispatchPendingTransactions() {
- if (!Flags.bundleClientTransactionFlag()) {
+ if (!Flags.bundleClientTransactionFlag() || mPendingTransactions.isEmpty()) {
return;
}
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "clientTransactionsDispatched");
final int size = mPendingTransactions.size();
for (int i = 0; i < size; i++) {
final ClientTransaction transaction = mPendingTransactions.valueAt(i);
@@ -159,6 +161,7 @@
}
}
mPendingTransactions.clear();
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
/**
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 97cc982..68bd326 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -173,7 +173,7 @@
// Corresponds to OVERRIDE_ANY_ORIENTATION
private final boolean mIsOverrideAnyOrientationEnabled;
// Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
- private final boolean mIsOverrideToUserOrientationEnabled;
+ private final boolean mIsSystemOverrideToFullscreenEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
private final boolean mIsOverrideToPortraitOrientationEnabled;
// Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -358,7 +358,7 @@
PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
- mIsOverrideToUserOrientationEnabled =
+ mIsSystemOverrideToFullscreenEnabled =
isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
mIsOverrideToPortraitOrientationEnabled =
isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -671,8 +671,7 @@
final DisplayContent displayContent = mActivityRecord.mDisplayContent;
final boolean isIgnoreOrientationRequestEnabled = displayContent != null
&& displayContent.getIgnoreOrientationRequest();
- if (shouldApplyUserFullscreenOverride()
- && isIgnoreOrientationRequestEnabled) {
+ if (shouldApplyUserFullscreenOverride() && isIgnoreOrientationRequestEnabled) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -707,8 +706,7 @@
// mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
// which will always come first before this check as user override > device
// manufacturer override.
- if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
- && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+ if (isSystemOverrideToFullscreenEnabled() && isIgnoreOrientationRequestEnabled) {
Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
+ mActivityRecord + " is overridden to "
+ screenOrientationToString(SCREEN_ORIENTATION_USER));
@@ -1202,6 +1200,13 @@
return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
}
+ boolean isSystemOverrideToFullscreenEnabled() {
+ return mIsSystemOverrideToFullscreenEnabled
+ && !FALSE.equals(mBooleanPropertyAllowOrientationOverride)
+ && (mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+ || mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+ }
+
float getUserMinAspectRatio() {
switch (mUserAspectRatio) {
case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c674176..d556f09 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3506,6 +3506,8 @@
appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
appCompatTaskInfo.isUserFullscreenOverrideEnabled = top != null
&& top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
+ appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top != null
+ && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
&& top.mLetterboxUiController.isFromDoubleTap();
if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d425bdf..4ad4b0c 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -926,10 +926,14 @@
boolean sleepIfPossible(boolean shuttingDown) {
boolean shouldSleep = true;
if (mResumedActivity != null) {
- // Still have something resumed; can't sleep until it is paused.
- ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
- startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
- "sleep");
+ if (!shuttingDown && mResumedActivity.canTurnScreenOn()) {
+ ProtoLog.v(WM_DEBUG_STATES, "Waiting for screen on due to %s", mResumedActivity);
+ } else {
+ // Still have something resumed; can't sleep until it is paused.
+ ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
+ startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
+ "sleep");
+ }
shouldSleep = false;
} else if (mPausingActivity != null) {
// Still waiting for something to pause; can't sleep yet.
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 949025c..315c00f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5641,12 +5641,6 @@
// Skip sync for invisible app windows which are not managed by activity lifecycle.
return false;
}
- if (mActivityRecord != null && mViewVisibility != View.VISIBLE
- && mWinAnimator.mAttrType != TYPE_BASE_APPLICATION
- && mWinAnimator.mAttrType != TYPE_APPLICATION_STARTING) {
- // Skip sync for invisible app windows which are not managed by activity lifecycle.
- return false;
- }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 686b2a8..dfb5a57 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -63,6 +63,7 @@
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.annotations.GuardedBy;
import com.android.server.credentials.metrics.ApiName;
@@ -483,6 +484,7 @@
public ICancellationSignal getCandidateCredentials(
GetCredentialRequest request,
IGetCandidateCredentialsCallback callback,
+ IAutoFillManagerClient clientCallback,
final String callingPackage) {
Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
+ callingPackage);
@@ -503,7 +505,8 @@
request,
constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
getEnabledProvidersForUser(userId),
- CancellationSignal.fromTransport(cancelTransport)
+ CancellationSignal.fromTransport(cancelTransport),
+ clientCallback
);
addSessionLocked(userId, session);
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 6d9b7e8..ca5600e 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -16,6 +16,7 @@
package com.android.server.credentials;
+import android.Manifest;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
@@ -23,6 +24,7 @@
import android.credentials.GetCandidateCredentialsException;
import android.credentials.GetCandidateCredentialsResponse;
import android.credentials.GetCredentialRequest;
+import android.credentials.GetCredentialResponse;
import android.credentials.IGetCandidateCredentialsCallback;
import android.credentials.ui.GetCredentialProviderData;
import android.credentials.ui.ProviderData;
@@ -30,7 +32,9 @@
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
+import android.service.credentials.PermissionUtils;
import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
import java.util.ArrayList;
import java.util.List;
@@ -42,18 +46,22 @@
*/
public class GetCandidateRequestSession extends RequestSession<GetCredentialRequest,
IGetCandidateCredentialsCallback, GetCandidateCredentialsResponse>
- implements ProviderSession.ProviderInternalCallback<GetCandidateCredentialsResponse> {
+ implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
private static final String TAG = "GetCandidateRequestSession";
+ private final IAutoFillManagerClient mAutoFillCallback;
+
public GetCandidateRequestSession(
Context context, SessionLifetime sessionCallback,
Object lock, int userId, int callingUid,
IGetCandidateCredentialsCallback callback, GetCredentialRequest request,
CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
- CancellationSignal cancellationSignal) {
+ CancellationSignal cancellationSignal,
+ IAutoFillManagerClient autoFillCallback) {
super(context, sessionCallback, lock, userId, callingUid, request, callback,
RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
cancellationSignal, 0L);
+ mAutoFillCallback = autoFillCallback;
}
/**
@@ -92,12 +100,25 @@
return;
}
+ cancelExistingPendingIntent();
+ mPendingIntent = mCredentialManagerUi.createPendingIntent(
+ RequestInfo.newGetRequestInfo(
+ mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
+ PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
+ Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+ providerDataList);
+
List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
for (ProviderData providerData : providerDataList) {
candidateProviderDataList.add((GetCredentialProviderData) (providerData));
}
- respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(
- candidateProviderDataList));
+
+ try {
+ invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
+ candidateProviderDataList, mPendingIntent));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Issue while responding to client with error : " + e);
+ }
}
@Override
@@ -151,7 +172,8 @@
@Override
public void onFinalResponseReceived(ComponentName componentName,
- GetCandidateCredentialsResponse response) {
- // Not applicable for session without UI
+ GetCredentialResponse response) {
+ Slog.d(TAG, "onFinalResponseReceived");
+ respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt
new file mode 100644
index 0000000..dfdb0c7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.pm
+
+import android.content.res.Configuration
+import android.os.Looper
+import android.os.SystemProperties
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.LockGuard
+import com.android.server.extendedtestutils.wheneverStatic
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class UserManagerServiceDemoModeTest {
+ private lateinit var ums: UserManagerService
+
+ @Rule
+ @JvmField
+ val rule = MockSystemRule()
+
+ @Before
+ @Throws(Exception::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ rule.system().stageNominalSystemState()
+
+ if (Looper.myLooper() == null) {
+ Looper.prepare()
+ }
+
+ wheneverStatic { LockGuard.installNewLock(LockGuard.INDEX_USER) }.thenReturn(Object())
+ whenever(rule.mocks().systemConfig.getAndClearPackageToUserTypeWhitelist()).thenReturn(ArrayMap<String, Set<String>>())
+ whenever(rule.mocks().systemConfig.getAndClearPackageToUserTypeBlacklist()).thenReturn(ArrayMap<String, Set<String>>())
+ whenever(rule.mocks().resources.getStringArray(com.android.internal.R.array.config_defaultFirstUserRestrictions)).thenReturn(arrayOf<String>())
+ whenever(rule.mocks().resources.configuration).thenReturn(Configuration())
+
+ ums = UserManagerService(rule.mocks().context)
+ }
+
+ @Test
+ fun isDemoUser_returnsTrue_whenSystemPropertyIsSet() {
+ wheneverStatic { SystemProperties.getBoolean("ro.boot.arc_demo_mode", false) }.thenReturn(true)
+
+ assertThat(ums.isDemoUser(0)).isTrue()
+ }
+
+ @Test
+ fun isDemoUser_returnsFalse_whenSystemPropertyIsSet() {
+ wheneverStatic { SystemProperties.getBoolean("ro.boot.arc_demo_mode", false) }.thenReturn(false)
+
+ assertThat(ums.isDemoUser(0)).isFalse()
+ }
+
+ @Test
+ fun isDemoUser_returnsFalse_whenSystemPropertyIsNotSet() {
+ assertThat(ums.isDemoUser(0)).isFalse()
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
index 89a4961..59c94dc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapterTest.java
@@ -65,6 +65,7 @@
import com.android.server.biometrics.sensors.fingerprint.aidl.FingerprintResetLockoutClient;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -247,6 +248,7 @@
}
@Test
+ @Ignore("b/317403648")
public void lockoutPermanentResetViaClient() {
setLockoutPermanent();
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 835ccf0..6fffd75 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -112,7 +112,7 @@
testServiceDefaultValue_On(ServiceType.NULL);
}
- @Suppress
+ @Suppress // TODO: b/317823111 - Remove once test fixed.
@SmallTest
public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
testDefaultValue(
@@ -219,7 +219,7 @@
ServiceType.QUICK_DOZE);
}
- @Suppress
+ @Suppress // TODO: b/317823111 - Remove once test fixed.
@SmallTest
public void testUpdateConstants_getCorrectData() {
mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
@@ -327,6 +327,7 @@
}
}
+ @Suppress // TODO: b/317823111 - Remove once test fixed.
public void testSetPolicyLevel_Adaptive() {
mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_ADAPTIVE);
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 6f37967..66dcaff 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -31,13 +31,13 @@
"frameworks-base-testutils",
"frameworks-services-vibrator-testutils",
"junit",
- "mockito-target-minus-junit4",
+ "mockito-target-inline-minus-junit4",
"platform-test-annotations",
"service-permission.stubs.system_server",
"services.core",
"flag-junit",
],
-
+ jni_libs: ["libdexmakerjvmtiagent"],
platform_apis: true,
certificate: "platform",
dxflags: ["--multi-dex"],
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index bbca704e..f9fe6a9 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -43,12 +43,17 @@
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
import android.os.vibrator.PrebakedSegment;
import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationConfig;
import android.os.vibrator.VibrationEffectSegment;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
+import android.util.SparseArray;
import androidx.test.InstrumentationRegistry;
@@ -68,6 +73,9 @@
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
@Mock private PowerManagerInternal mPowerManagerInternalMock;
@Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -256,6 +264,29 @@
assertEquals(0.5, scaled.getScale(), 1e-5);
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+ public void scale_withAdaptiveHaptics_scalesVibrationsCorrectly() {
+ setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+ setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+ SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
+ adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
+ adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
+ mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);
+
+ StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+ // Ringtone scales down.
+ assertTrue(scaled.getAmplitude() < 0.5);
+
+ scaled = getFirstSegment(mVibrationScaler.scale(
+ VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+ USAGE_NOTIFICATION));
+ // Notification scales down.
+ assertTrue(scaled.getAmplitude() < 0.5);
+ }
+
private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
@Vibrator.VibrationIntensity int intensity) {
when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 49efd1b..1e0b1df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -16,21 +16,49 @@
package com.android.server.vibrator;
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
import android.os.RemoteException;
+import android.util.SparseArray;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
public class VibratorControlServiceTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ @Mock
+ private VibrationScaler mMockVibrationScaler;
+ @Captor
+ private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+
private VibratorControlService mVibratorControlService;
private final Object mLock = new Object();
@Before
public void setUp() throws Exception {
- mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+ mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
+ mMockVibrationScaler, mLock);
}
@Test
@@ -47,6 +75,8 @@
FakeVibratorController fakeController = new FakeVibratorController();
mVibratorControlService.registerVibratorController(fakeController);
mVibratorControlService.unregisterVibratorController(fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
assertThat(fakeController.isLinkedToDeath).isFalse();
}
@@ -56,8 +86,91 @@
FakeVibratorController fakeController1 = new FakeVibratorController();
FakeVibratorController fakeController2 = new FakeVibratorController();
mVibratorControlService.registerVibratorController(fakeController1);
-
mVibratorControlService.unregisterVibratorController(fakeController2);
+
+ verifyZeroInteractions(mMockVibrationScaler);
assertThat(fakeController1.isLinkedToDeath).isTrue();
}
+
+ @Test
+ public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+ mVibratorControlService.registerVibratorController(fakeController);
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
+ SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
+ assertThat(cachedVibrationScales.size()).isEqualTo(3);
+ assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
+ assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
+ assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
+ assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+ // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+ // notification and communication request usages.
+ assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
+ assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+ }
+
+ @Test
+ public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+
+ SparseArray<Float> vibrationScales = new SparseArray<>();
+ vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+ vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+ mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+ fakeController);
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ @Test
+ public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+ mVibratorControlService.registerVibratorController(fakeController);
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+ verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+ }
+
+ @Test
+ public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
+ throws RemoteException {
+ FakeVibratorController fakeController = new FakeVibratorController();
+
+ mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+ verifyZeroInteractions(mMockVibrationScaler);
+ }
+
+ private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
+ List<VibrationParam> vibrationParamList = new ArrayList<>();
+ for (int i = 0; i < vibrationScales.size(); i++) {
+ int type = vibrationScales.keyAt(i);
+ float scale = vibrationScales.valueAt(i);
+
+ vibrationParamList.add(generateVibrationParam(type, scale));
+ }
+
+ return vibrationParamList.toArray(new VibrationParam[0]);
+ }
+
+ private VibrationParam generateVibrationParam(int type, float scale) {
+ ScaleParam scaleParam = new ScaleParam();
+ scaleParam.typesMask = type;
+ scaleParam.scale = scale;
+ VibrationParam vibrationParam = new VibrationParam();
+ vibrationParam.setScale(scaleParam);
+
+ return vibrationParam;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 5363583..29467f2 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -16,6 +16,10 @@
package com.android.server.policy;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerGlobal.ADD_OKAY;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -26,11 +30,16 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.filters.SmallTest;
@@ -39,6 +48,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
/**
@@ -50,6 +60,9 @@
@SmallTest
public class PhoneWindowManagerTests {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
PhoneWindowManager mPhoneWindowManager;
@Before
@@ -85,6 +98,36 @@
verify(mPhoneWindowManager).createHomeDockIntent();
}
+ @Test
+ public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ int[] outAppOp = new int[1];
+ assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
+ }
+
+ @Test
+ public void testCheckAddPermission_withAccessibilityOverlay() {
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ int[] outAppOp = new int[1];
+ assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY);
+ }
+
+ @Test
+ public void testCheckAddPermission_withAccessibilityOverlay_flagDisabled() {
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+ .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ int[] outAppOp = new int[1];
+ assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
+ /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+ assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
+ }
+
private void mockStartDockOrHome() throws Exception {
doNothing().when(ActivityManager.getService()).stopAppSwitches();
ActivityTaskManagerInternal mMockActivityTaskManagerInternal =
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 985be42..4e4bbfe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -697,6 +697,31 @@
@Test
@EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutSystem_returnsUser()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+
+ // fullscreen override still applied
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+ public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutUser_returnsUser()
+ throws Exception {
+ mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+ /* value */ false);
+ prepareActivityThatShouldApplyUserFullscreenOverride();
+
+ // fullscreen override still applied
+ assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+ /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+ }
+
+ @Test
+ @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
throws Exception {
mDisplayContent.setIgnoreOrientationRequest(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index b5883b1..527ea0d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -140,7 +140,7 @@
final WindowContainer parent = activity1.getTask().getParent();
assertEquals(SCREEN_ORIENTATION_PORTRAIT, parent.getOrientation());
- mDisplayContent.mClosingApps.add(activity2);
+ activity2.setVisibleRequested(false);
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, parent.getOrientation());
}