Merge "Update tests and canvas subclasses to expect drawImageRect calls" 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/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/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/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/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/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/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 3b6be8a..2f51278 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;
@@ -195,46 +196,16 @@
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;
-
if (isHandleMenuActive()) {
mHandleMenu.relayout(startT);
}
+ updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw);
+
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 +244,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 +297,45 @@
}
}
+ @VisibleForTesting
+ static void updateRelayoutParams(
+ RelayoutParams relayoutParams,
+ Context context,
+ ActivityManager.RunningTaskInfo taskInfo,
+ boolean applyStartTransactionOnDraw) {
+ 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;
+ // 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 +697,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 +743,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/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..77667ca 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,50 @@
}
+ @Test
+ public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+ RelayoutParams relayoutParams = new RelayoutParams();
+
+ DesktopModeWindowDecoration.updateRelayoutParams(
+ relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true);
+
+ 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);
+
+ 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/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/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 14bcac2..558a910 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -303,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/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/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/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/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/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 72e62c3..8ad60e6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -243,7 +243,7 @@
/**
* The default value to {@link #KEY_ENABLE_NEW_OOMADJ}.
*/
- private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = false;
+ private static final boolean DEFAULT_ENABLE_NEW_OOM_ADJ = Flags.oomadjusterCorrectnessRewrite();
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
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/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/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));
}
}