summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/hardware/Camera.java24
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java43
-rw-r--r--core/java/android/window/TaskFragmentCreationParams.java66
-rw-r--r--core/jni/android_hardware_Camera.cpp144
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java59
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java11
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java25
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java15
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java2
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java10
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java6
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java4
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java9
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt231
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt (renamed from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt328
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java51
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java13
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java40
43 files changed, 1079 insertions, 349 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 93c0c4d7a024..70a23cdf106b 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1139,6 +1139,7 @@ package android.hardware.camera2 {
public final class CameraManager {
method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException;
method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
+ field public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L; // 0xef10e60L
}
public abstract static class CameraManager.AvailabilityCallback {
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 3bdd39f5d7d7..5291d2b73891 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -29,12 +29,14 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.app.compat.CompatChanges;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraManager;
import android.media.AudioAttributes;
import android.media.IAudioService;
import android.os.Build;
@@ -45,6 +47,7 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RSIllegalArgumentException;
@@ -281,6 +284,14 @@ public class Camera {
*/
public native static int getNumberOfCameras();
+ private static final boolean sLandscapeToPortrait =
+ SystemProperties.getBoolean(CameraManager.LANDSCAPE_TO_PORTRAIT_PROP, false);
+
+ private static boolean shouldOverrideToPortrait() {
+ return CompatChanges.isChangeEnabled(CameraManager.OVERRIDE_FRONT_CAMERA_APP_COMPAT)
+ && sLandscapeToPortrait;
+ }
+
/**
* Returns the information about a particular camera.
* If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1.
@@ -290,7 +301,9 @@ public class Camera {
* low-level failure).
*/
public static void getCameraInfo(int cameraId, CameraInfo cameraInfo) {
- _getCameraInfo(cameraId, cameraInfo);
+ boolean overrideToPortrait = shouldOverrideToPortrait();
+
+ _getCameraInfo(cameraId, overrideToPortrait, cameraInfo);
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
try {
@@ -303,7 +316,8 @@ public class Camera {
Log.e(TAG, "Audio service is unavailable for queries");
}
}
- private native static void _getCameraInfo(int cameraId, CameraInfo cameraInfo);
+ private native static void _getCameraInfo(int cameraId, boolean overrideToPortrait,
+ CameraInfo cameraInfo);
/**
* Information about a camera
@@ -484,8 +498,9 @@ public class Camera {
mEventHandler = null;
}
+ boolean overrideToPortrait = shouldOverrideToPortrait();
return native_setup(new WeakReference<Camera>(this), cameraId,
- ActivityThread.currentOpPackageName());
+ ActivityThread.currentOpPackageName(), overrideToPortrait);
}
/** used by Camera#open, Camera#open(int) */
@@ -555,7 +570,8 @@ public class Camera {
}
@UnsupportedAppUsage
- private native int native_setup(Object cameraThis, int cameraId, String packageName);
+ private native int native_setup(Object cameraThis, int cameraId, String packageName,
+ boolean overrideToPortrait);
private native final void native_release();
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index dff2f7ed1cf3..7fed2200d606 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -23,6 +23,10 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
@@ -104,6 +108,24 @@ public final class CameraManager {
private final boolean mHasOpenCloseListenerPermission;
/**
+ * Force camera output to be rotated to portrait orientation on landscape cameras.
+ * Many apps do not handle this situation and display stretched images otherwise.
+ * @hide
+ */
+ @ChangeId
+ @Overridable
+ @Disabled
+ @TestApi
+ public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
+
+ /**
+ * System property for allowing the above
+ * @hide
+ */
+ public static final String LANDSCAPE_TO_PORTRAIT_PROP =
+ "camera.enable_landscape_to_portrait";
+
+ /**
* @hide
*/
public CameraManager(Context context) {
@@ -520,7 +542,8 @@ public final class CameraManager {
for (String physicalCameraId : physicalCameraIds) {
CameraMetadataNative physicalCameraInfo =
cameraService.getCameraCharacteristics(physicalCameraId,
- mContext.getApplicationInfo().targetSdkVersion);
+ mContext.getApplicationInfo().targetSdkVersion,
+ /*overrideToPortrait*/false);
StreamConfiguration[] configs = physicalCameraInfo.get(
CameraCharacteristics.
SCALER_PHYSICAL_CAMERA_MULTI_RESOLUTION_STREAM_CONFIGURATIONS);
@@ -579,8 +602,9 @@ public final class CameraManager {
try {
Size displaySize = getDisplaySize();
+ boolean overrideToPortrait = shouldOverrideToPortrait();
CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
- mContext.getApplicationInfo().targetSdkVersion);
+ mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait);
try {
info.setCameraId(Integer.parseInt(cameraId));
} catch (NumberFormatException e) {
@@ -697,9 +721,12 @@ public final class CameraManager {
ICameraService.ERROR_DISCONNECTED,
"Camera service is currently unavailable");
}
+
+ boolean overrideToPortrait = shouldOverrideToPortrait();
cameraUser = cameraService.connectDevice(callbacks, cameraId,
- mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
- oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);
+ mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
+ oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
+ overrideToPortrait);
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1127,6 +1154,11 @@ public final class CameraManager {
return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId);
}
+ private static boolean shouldOverrideToPortrait() {
+ return CompatChanges.isChangeEnabled(OVERRIDE_FRONT_CAMERA_APP_COMPAT)
+ && CameraManagerGlobal.sLandscapeToPortrait;
+ }
+
/**
* A callback for camera devices becoming available or unavailable to open.
*
@@ -1573,6 +1605,9 @@ public final class CameraManager {
public static final boolean sCameraServiceDisabled =
SystemProperties.getBoolean("config.disable_cameraservice", false);
+ public static final boolean sLandscapeToPortrait =
+ SystemProperties.getBoolean(LANDSCAPE_TO_PORTRAIT_PROP, false);
+
public static CameraManagerGlobal get() {
return gCameraManager;
}
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 81ab7836435d..c9ddf92d3740 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WindowingMode;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.graphics.Rect;
import android.os.IBinder;
@@ -57,14 +58,33 @@ public final class TaskFragmentCreationParams implements Parcelable {
/** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
@WindowingMode
- private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private final int mWindowingMode;
+
+ /**
+ * The fragment token of the paired primary TaskFragment.
+ * When it is set, the new TaskFragment will be positioned right above the paired TaskFragment.
+ * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+ *
+ * This is different from {@link WindowContainerTransaction#setAdjacentTaskFragments} as we may
+ * set this when the pair of TaskFragments are stacked, while adjacent is only set on the pair
+ * of TaskFragments that are in split.
+ *
+ * This is needed in case we need to launch a placeholder Activity to split below a transparent
+ * always-expand Activity.
+ */
+ @Nullable
+ private final IBinder mPairedPrimaryFragmentToken;
private TaskFragmentCreationParams(
- @NonNull TaskFragmentOrganizerToken organizer,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
+ @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
+ @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
mOrganizer = organizer;
mFragmentToken = fragmentToken;
mOwnerToken = ownerToken;
+ mInitialBounds.set(initialBounds);
+ mWindowingMode = windowingMode;
+ mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
}
@NonNull
@@ -92,12 +112,22 @@ public final class TaskFragmentCreationParams implements Parcelable {
return mWindowingMode;
}
+ /**
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @Nullable
+ public IBinder getPairedPrimaryFragmentToken() {
+ return mPairedPrimaryFragmentToken;
+ }
+
private TaskFragmentCreationParams(Parcel in) {
mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
mFragmentToken = in.readStrongBinder();
mOwnerToken = in.readStrongBinder();
mInitialBounds.readFromParcel(in);
mWindowingMode = in.readInt();
+ mPairedPrimaryFragmentToken = in.readStrongBinder();
}
/** @hide */
@@ -108,6 +138,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
dest.writeStrongBinder(mOwnerToken);
mInitialBounds.writeToParcel(dest, flags);
dest.writeInt(mWindowingMode);
+ dest.writeStrongBinder(mPairedPrimaryFragmentToken);
}
@NonNull
@@ -132,6 +163,7 @@ public final class TaskFragmentCreationParams implements Parcelable {
+ " ownerToken=" + mOwnerToken
+ " initialBounds=" + mInitialBounds
+ " windowingMode=" + mWindowingMode
+ + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
+ "}";
}
@@ -159,6 +191,9 @@ public final class TaskFragmentCreationParams implements Parcelable {
@WindowingMode
private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ @Nullable
+ private IBinder mPairedPrimaryFragmentToken;
+
public Builder(@NonNull TaskFragmentOrganizerToken organizer,
@NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
mOrganizer = organizer;
@@ -180,14 +215,29 @@ public final class TaskFragmentCreationParams implements Parcelable {
return this;
}
+ /**
+ * Sets the fragment token of the paired primary TaskFragment.
+ * When it is set, the new TaskFragment will be positioned right above the paired
+ * TaskFragment. Otherwise, the new TaskFragment will be positioned on the top of the Task
+ * by default.
+ *
+ * This is needed in case we need to launch a placeholder Activity to split below a
+ * transparent always-expand Activity.
+ *
+ * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+ * @hide
+ */
+ @NonNull
+ public Builder setPairedPrimaryFragmentToken(@Nullable IBinder fragmentToken) {
+ mPairedPrimaryFragmentToken = fragmentToken;
+ return this;
+ }
+
/** Constructs the options to create TaskFragment with. */
@NonNull
public TaskFragmentCreationParams build() {
- final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
- mOrganizer, mFragmentToken, mOwnerToken);
- result.mInitialBounds.set(mInitialBounds);
- result.mWindowingMode = mWindowingMode;
- return result;
+ return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
+ mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
}
}
}
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 365a18d174c9..a8abe50a9755 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -529,9 +529,8 @@ static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz
return Camera::getNumberOfCameras();
}
-static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
- jint cameraId, jobject info_obj)
-{
+static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
+ jboolean overrideToPortrait, jobject info_obj) {
CameraInfo cameraInfo;
if (cameraId >= Camera::getNumberOfCameras() || cameraId < 0) {
ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId);
@@ -539,7 +538,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
return;
}
- status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
+ status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
if (rc != NO_ERROR) {
jniThrowRuntimeException(env, "Fail to get camera info");
return;
@@ -555,9 +554,9 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
}
// connect to camera service
-static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
- jobject weak_this, jint cameraId, jstring clientPackageName)
-{
+static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
+ jint cameraId, jstring clientPackageName,
+ jboolean overrideToPortrait) {
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
env->GetStringChars(clientPackageName, NULL));
@@ -567,8 +566,9 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
reinterpret_cast<const jchar*>(rawClientName));
int targetSdkVersion = android_get_application_target_sdk_version();
- sp<Camera> camera = Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID,
- Camera::USE_CALLING_PID, targetSdkVersion);
+ sp<Camera> camera =
+ Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
+ targetSdkVersion, overrideToPortrait);
if (camera == NULL) {
return -EACCES;
}
@@ -596,7 +596,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
// Update default display orientation in case the sensor is reverse-landscape
CameraInfo cameraInfo;
- status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo);
+ status_t rc = Camera::getCameraInfo(cameraId, overrideToPortrait, &cameraInfo);
if (rc != NO_ERROR) {
ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
return rc;
@@ -1051,93 +1051,43 @@ static int32_t android_hardware_Camera_getAudioRestriction(
//-------------------------------------------------
static const JNINativeMethod camMethods[] = {
- { "getNumberOfCameras",
- "()I",
- (void *)android_hardware_Camera_getNumberOfCameras },
- { "_getCameraInfo",
- "(ILandroid/hardware/Camera$CameraInfo;)V",
- (void*)android_hardware_Camera_getCameraInfo },
- { "native_setup",
- "(Ljava/lang/Object;ILjava/lang/String;)I",
- (void*)android_hardware_Camera_native_setup },
- { "native_release",
- "()V",
- (void*)android_hardware_Camera_release },
- { "setPreviewSurface",
- "(Landroid/view/Surface;)V",
- (void *)android_hardware_Camera_setPreviewSurface },
- { "setPreviewTexture",
- "(Landroid/graphics/SurfaceTexture;)V",
- (void *)android_hardware_Camera_setPreviewTexture },
- { "setPreviewCallbackSurface",
- "(Landroid/view/Surface;)V",
- (void *)android_hardware_Camera_setPreviewCallbackSurface },
- { "startPreview",
- "()V",
- (void *)android_hardware_Camera_startPreview },
- { "_stopPreview",
- "()V",
- (void *)android_hardware_Camera_stopPreview },
- { "previewEnabled",
- "()Z",
- (void *)android_hardware_Camera_previewEnabled },
- { "setHasPreviewCallback",
- "(ZZ)V",
- (void *)android_hardware_Camera_setHasPreviewCallback },
- { "_addCallbackBuffer",
- "([BI)V",
- (void *)android_hardware_Camera_addCallbackBuffer },
- { "native_autoFocus",
- "()V",
- (void *)android_hardware_Camera_autoFocus },
- { "native_cancelAutoFocus",
- "()V",
- (void *)android_hardware_Camera_cancelAutoFocus },
- { "native_takePicture",
- "(I)V",
- (void *)android_hardware_Camera_takePicture },
- { "native_setParameters",
- "(Ljava/lang/String;)V",
- (void *)android_hardware_Camera_setParameters },
- { "native_getParameters",
- "()Ljava/lang/String;",
- (void *)android_hardware_Camera_getParameters },
- { "reconnect",
- "()V",
- (void*)android_hardware_Camera_reconnect },
- { "lock",
- "()V",
- (void*)android_hardware_Camera_lock },
- { "unlock",
- "()V",
- (void*)android_hardware_Camera_unlock },
- { "startSmoothZoom",
- "(I)V",
- (void *)android_hardware_Camera_startSmoothZoom },
- { "stopSmoothZoom",
- "()V",
- (void *)android_hardware_Camera_stopSmoothZoom },
- { "setDisplayOrientation",
- "(I)V",
- (void *)android_hardware_Camera_setDisplayOrientation },
- { "_enableShutterSound",
- "(Z)Z",
- (void *)android_hardware_Camera_enableShutterSound },
- { "_startFaceDetection",
- "(I)V",
- (void *)android_hardware_Camera_startFaceDetection },
- { "_stopFaceDetection",
- "()V",
- (void *)android_hardware_Camera_stopFaceDetection},
- { "enableFocusMoveCallback",
- "(I)V",
- (void *)android_hardware_Camera_enableFocusMoveCallback},
- { "setAudioRestriction",
- "(I)V",
- (void *)android_hardware_Camera_setAudioRestriction},
- { "getAudioRestriction",
- "()I",
- (void *)android_hardware_Camera_getAudioRestriction},
+ {"getNumberOfCameras", "()I", (void *)android_hardware_Camera_getNumberOfCameras},
+ {"_getCameraInfo", "(IZLandroid/hardware/Camera$CameraInfo;)V",
+ (void *)android_hardware_Camera_getCameraInfo},
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;Z)I",
+ (void *)android_hardware_Camera_native_setup},
+ {"native_release", "()V", (void *)android_hardware_Camera_release},
+ {"setPreviewSurface", "(Landroid/view/Surface;)V",
+ (void *)android_hardware_Camera_setPreviewSurface},
+ {"setPreviewTexture", "(Landroid/graphics/SurfaceTexture;)V",
+ (void *)android_hardware_Camera_setPreviewTexture},
+ {"setPreviewCallbackSurface", "(Landroid/view/Surface;)V",
+ (void *)android_hardware_Camera_setPreviewCallbackSurface},
+ {"startPreview", "()V", (void *)android_hardware_Camera_startPreview},
+ {"_stopPreview", "()V", (void *)android_hardware_Camera_stopPreview},
+ {"previewEnabled", "()Z", (void *)android_hardware_Camera_previewEnabled},
+ {"setHasPreviewCallback", "(ZZ)V", (void *)android_hardware_Camera_setHasPreviewCallback},
+ {"_addCallbackBuffer", "([BI)V", (void *)android_hardware_Camera_addCallbackBuffer},
+ {"native_autoFocus", "()V", (void *)android_hardware_Camera_autoFocus},
+ {"native_cancelAutoFocus", "()V", (void *)android_hardware_Camera_cancelAutoFocus},
+ {"native_takePicture", "(I)V", (void *)android_hardware_Camera_takePicture},
+ {"native_setParameters", "(Ljava/lang/String;)V",
+ (void *)android_hardware_Camera_setParameters},
+ {"native_getParameters", "()Ljava/lang/String;",
+ (void *)android_hardware_Camera_getParameters},
+ {"reconnect", "()V", (void *)android_hardware_Camera_reconnect},
+ {"lock", "()V", (void *)android_hardware_Camera_lock},
+ {"unlock", "()V", (void *)android_hardware_Camera_unlock},
+ {"startSmoothZoom", "(I)V", (void *)android_hardware_Camera_startSmoothZoom},
+ {"stopSmoothZoom", "()V", (void *)android_hardware_Camera_stopSmoothZoom},
+ {"setDisplayOrientation", "(I)V", (void *)android_hardware_Camera_setDisplayOrientation},
+ {"_enableShutterSound", "(Z)Z", (void *)android_hardware_Camera_enableShutterSound},
+ {"_startFaceDetection", "(I)V", (void *)android_hardware_Camera_startFaceDetection},
+ {"_stopFaceDetection", "()V", (void *)android_hardware_Camera_stopFaceDetection},
+ {"enableFocusMoveCallback", "(I)V",
+ (void *)android_hardware_Camera_enableFocusMoveCallback},
+ {"setAudioRestriction", "(I)V", (void *)android_hardware_Camera_setAudioRestriction},
+ {"getAudioRestriction", "()I", (void *)android_hardware_Camera_getAudioRestriction},
};
struct field {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d7d43aa19757..b910287aa535 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -133,8 +133,18 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
}
// Create a TaskFragment for the secondary activity.
- createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
- secondaryFragmentBounds, windowingMode, activityIntent,
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), secondaryFragmentToken, ownerToken)
+ .setInitialBounds(secondaryFragmentBounds)
+ .setWindowingMode(windowingMode)
+ // Make sure to set the paired fragment token so that the new TaskFragment will be
+ // positioned right above the paired TaskFragment.
+ // This is needed in case we need to launch a placeholder Activity to split below a
+ // transparent always-expand Activity.
+ .setPairedPrimaryFragmentToken(launchingFragmentToken)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
activityOptions);
// Set adjacent to each other so that the containers below will be invisible.
@@ -173,8 +183,21 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
*/
void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentCreationParams fragmentOptions =
- createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+ final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+ getOrganizerToken(), fragmentToken, ownerToken)
+ .setInitialBounds(bounds)
+ .setWindowingMode(windowingMode)
+ .build();
+ createTaskFragment(wct, fragmentOptions);
+ }
+
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
+ throw new IllegalArgumentException(
+ "There is an existing TaskFragment with fragmentToken="
+ + fragmentOptions.getFragmentToken());
+ }
wct.createTaskFragment(fragmentOptions);
}
@@ -189,18 +212,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
}
- /**
- * @param ownerToken The token of the activity that creates this task fragment. It does not
- * have to be a child of this task fragment, but must belong to the same task.
- */
- private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
- @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
- @WindowingMode int windowingMode, @NonNull Intent activityIntent,
- @Nullable Bundle activityOptions) {
- createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
- wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
- }
-
void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
@@ -238,22 +249,6 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
}
- TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- if (mFragmentInfos.containsKey(fragmentToken)) {
- throw new IllegalArgumentException(
- "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
- }
-
- return new TaskFragmentCreationParams.Builder(
- getOrganizerToken(),
- fragmentToken,
- ownerToken)
- .setInitialBounds(bounds)
- .setWindowingMode(windowingMode)
- .build();
- }
-
void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
@Nullable Rect bounds) {
if (!mFragmentInfos.containsKey(fragmentToken)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 6f9a4ff8188a..d3dc05fb92d4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1218,14 +1218,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
@NonNull Activity activityInTask, int taskId) {
return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
@NonNull Activity activityInTask, int taskId) {
return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
- activityInTask, taskId);
+ activityInTask, taskId, null /* pairedPrimaryContainer */);
}
/**
@@ -1237,10 +1237,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @param activityInTask activity in the same Task so that we can get the Task bounds
* if needed.
* @param taskId parent Task of the new TaskFragment.
+ * @param pairedPrimaryContainer the paired primary {@link TaskFragmentContainer}. When it is
+ * set, the new container will be added right above it.
*/
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
- @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
+ @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if (activityInTask == null) {
throw new IllegalArgumentException("activityInTask must not be null,");
}
@@ -1249,7 +1252,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
final TaskContainer taskContainer = mTaskContainers.get(taskId);
final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
- pendingAppearedIntent, taskContainer, this);
+ pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
return container;
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 5395fb2ef5ed..47253d388f0d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@ import android.util.Size;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowMetrics;
+import android.window.TaskFragmentCreationParams;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
@@ -307,10 +308,13 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
final int taskId = primaryContainer.getTaskId();
- final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
- launchingActivity, taskId);
- final int windowingMode = mController.getTaskContainer(taskId)
- .getWindowingModeForSplitTaskFragment(primaryRectBounds);
+ final TaskFragmentContainer secondaryContainer = mController.newContainer(
+ null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+ // Pass in the primary container to make sure it is added right above the primary.
+ primaryContainer);
+ final TaskContainer taskContainer = mController.getTaskContainer(taskId);
+ final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+ primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -412,17 +416,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
@Override
- void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
- @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
- final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+ void createTaskFragment(@NonNull WindowContainerTransaction wct,
+ @NonNull TaskFragmentCreationParams fragmentOptions) {
+ final TaskFragmentContainer container = mController.getContainer(
+ fragmentOptions.getFragmentToken());
if (container == null) {
throw new IllegalStateException(
"Creating a task fragment that is not registered with controller.");
}
- container.setLastRequestedBounds(bounds);
- container.setLastRequestedWindowingMode(windowingMode);
- super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+ container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
+ container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
+ super.createTaskFragment(wct, fragmentOptions);
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e31792ad718f..fcf0ac78af38 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -118,10 +118,12 @@ class TaskFragmentContainer {
/**
* Creates a container with an existing activity that will be re-parented to it in a window
* container transaction.
+ * @param pairedPrimaryContainer when it is set, the new container will be add right above it
*/
TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
- @NonNull SplitController controller) {
+ @NonNull SplitController controller,
+ @Nullable TaskFragmentContainer pairedPrimaryContainer) {
if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
|| (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
throw new IllegalArgumentException(
@@ -130,7 +132,16 @@ class TaskFragmentContainer {
mController = controller;
mToken = new Binder("TaskFragmentContainer");
mTaskContainer = taskContainer;
- taskContainer.mContainers.add(this);
+ if (pairedPrimaryContainer != null) {
+ if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
+ throw new IllegalArgumentException(
+ "pairedPrimaryContainer must be in the same Task");
+ }
+ final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
+ taskContainer.mContainers.add(primaryIndex + 1, this);
+ } else {
+ taskContainer.mContainers.add(this);
+ }
if (pendingAppearedActivity != null) {
addPendingAppearedActivity(pendingAppearedActivity);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 31aa09c902b1..bbb454d31c38 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -102,7 +102,7 @@ public class JetpackTaskFragmentOrganizerTest {
public void testExpandTaskFragment() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockInfo(container);
mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
container.setInfo(mTransaction, info);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 221c7640ef0b..6725dfdb94e8 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -169,7 +169,7 @@ public class SplitControllerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// tf1 has no running activity so is not active.
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mSplitController);
+ new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
// tf2 has running activity so is active.
final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
doReturn(1).when(tf2).getRunningActivityCount();
@@ -375,7 +375,7 @@ public class SplitControllerTest {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- intent, taskContainer, mSplitController);
+ intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */);
final SplitController.ActivityStartMonitor monitor =
mSplitController.getActivityStartMonitor();
@@ -609,7 +609,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertFalse(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
}
@Test
@@ -771,7 +771,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
@@ -813,7 +813,7 @@ public class SplitControllerTest {
false /* isOnReparent */);
assertTrue(result);
- verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+ verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 95328ce700e3..13e709271221 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -122,7 +122,7 @@ public class TaskContainerTest {
assertTrue(taskContainer.isEmpty());
final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertFalse(taskContainer.isEmpty());
@@ -138,11 +138,11 @@ public class TaskContainerTest {
assertNull(taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
- new Intent(), taskContainer, mController);
+ new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 99f56b4ceb17..5c3ba72e2361 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -94,18 +94,21 @@ public class TaskFragmentContainerTest {
// One of the activity and the intent must be non-null
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(null, null, taskContainer, mController));
+ () -> new TaskFragmentContainer(null, null, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
// One of the activity and the intent must be null.
assertThrows(IllegalArgumentException.class,
- () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController));
+ () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */));
}
@Test
public void testFinish() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container).when(mController).getContainerWithActivity(mActivity);
// Only remove the activity, but not clear the reference until appeared.
@@ -137,12 +140,14 @@ public class TaskFragmentContainerTest {
public void testFinish_notFinishActivityThatIsReparenting() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
container0.setInfo(mTransaction, info);
// Request to reparent the activity to a new TaskFragment.
final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
doReturn(container1).when(mController).getContainerWithActivity(mActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -159,7 +164,8 @@ public class TaskFragmentContainerTest {
final TaskContainer taskContainer = createTestTaskContainer();
// Pending activity should be cleared when it has appeared on server side.
final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
- null /* pendingAppearedIntent */, taskContainer, mController);
+ null /* pendingAppearedIntent */, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
mActivity.getActivityToken()));
@@ -172,7 +178,8 @@ public class TaskFragmentContainerTest {
// Pending intent should be cleared when the container becomes non-empty.
final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
- null /* pendingAppearedActivity */, mIntent, taskContainer, mController);
+ null /* pendingAppearedActivity */, mIntent, taskContainer, mController,
+ null /* pairedPrimaryContainer */);
assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
@@ -187,7 +194,7 @@ public class TaskFragmentContainerTest {
public void testIsWaitingActivityAppear() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container.isWaitingActivityAppear());
@@ -209,7 +216,7 @@ public class TaskFragmentContainerTest {
doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertNull(container.mAppearEmptyTimeout);
@@ -249,7 +256,7 @@ public class TaskFragmentContainerTest {
public void testCollectNonFinishingActivities() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
List<Activity> activities = container.collectNonFinishingActivities();
assertTrue(activities.isEmpty());
@@ -277,7 +284,7 @@ public class TaskFragmentContainerTest {
public void testAddPendingActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(1, container.collectNonFinishingActivities().size());
@@ -291,9 +298,9 @@ public class TaskFragmentContainerTest {
public void testIsAbove() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
assertTrue(container1.isAbove(container0));
assertFalse(container0.isAbove(container1));
@@ -303,7 +310,7 @@ public class TaskFragmentContainerTest {
public void testGetBottomMostActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertEquals(mActivity, container.getBottomMostActivity());
@@ -320,7 +327,7 @@ public class TaskFragmentContainerTest {
public void testOnActivityDestroyed() {
final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
final List<IBinder> activities = new ArrayList<>();
activities.add(mActivity.getActivityToken());
@@ -340,7 +347,7 @@ public class TaskFragmentContainerTest {
// True if no info set.
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
spyOn(taskContainer);
doReturn(true).when(taskContainer).isVisible();
@@ -403,7 +410,7 @@ public class TaskFragmentContainerTest {
public void testHasAppearedActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
@@ -420,7 +427,7 @@ public class TaskFragmentContainerTest {
public void testHasPendingAppearedActivity() {
final TaskContainer taskContainer = createTestTaskContainer();
final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
container.addPendingAppearedActivity(mActivity);
assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
@@ -437,9 +444,9 @@ public class TaskFragmentContainerTest {
public void testHasActivity() {
final TaskContainer taskContainer = createTestTaskContainer(mController);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
- mIntent, taskContainer, mController);
+ mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
// Activity is pending appeared on container2.
container2.addPendingAppearedActivity(mActivity);
@@ -472,6 +479,27 @@ public class TaskFragmentContainerTest {
assertTrue(container2.hasActivity(mActivity.getActivityToken()));
}
+ @Test
+ public void testNewContainerWithPairedPrimaryContainer() {
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+ null /* pairedPrimaryTaskFragment */);
+ taskContainer.mContainers.add(tf0);
+ taskContainer.mContainers.add(tf1);
+
+ // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
+ // right above tf0.
+ final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+ null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0);
+ assertEquals(0, taskContainer.indexOf(tf0));
+ assertEquals(1, taskContainer.indexOf(tf2));
+ assertEquals(2, taskContainer.indexOf(tf1));
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index ad0adc6c84c4..c743582c3264 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -692,7 +692,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isProto1Enabled()) {
return desktopModeController.map(Lazy::get);
}
return Optional.empty();
@@ -709,7 +709,7 @@ public abstract class WMShellBaseModule {
// Use optional-of-lazy for the dependency that this provider relies on.
// Lazy ensures that this provider will not be the cause the dependency is created
// when it will not be returned due to the condition below.
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
return desktopModeTaskRepository.map(Lazy::get);
}
return Optional.empty();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 5824f51a1d33..7eb01a79f14f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -100,7 +100,7 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
mDesktopModeTaskRepository = desktopModeTaskRepository;
mMainExecutor = mainExecutor;
mSettingsObserver = new SettingsObserver(mContext, mainHandler);
- if (DesktopModeStatus.isSupported()) {
+ if (DesktopModeStatus.isProto1Enabled()) {
shellInit.addInitCallback(this::onInit, this);
}
}
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 e3eb2b746969..67f4a1914c49 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
@@ -33,17 +33,38 @@ public class DesktopModeStatus {
/**
* Flag to indicate whether desktop mode is available on the device
*/
- public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+ private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode", false);
/**
+ * Flag to indicate whether desktop mode proto 2 is available on the device
+ */
+ private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode_2", false);
+
+ /**
* Return {@code true} if desktop mode support is enabled
*/
- public static boolean isSupported() {
+ public static boolean isProto1Enabled() {
return IS_SUPPORTED;
}
/**
+ * Return {@code true} is desktop windowing proto 2 is enabled
+ */
+ public static boolean isProto2Enabled() {
+ return IS_PROTO2_ENABLED;
+ }
+
+ /**
+ * Return {@code true} if proto 1 or 2 is enabled.
+ * Can be used to guard logic that is common for both prototypes.
+ */
+ public static boolean isAnyEnabled() {
+ return isProto1Enabled() || isProto2Enabled();
+ }
+
+ /**
* Check if desktop mode is active
*
* @return {@code true} if active
@@ -61,5 +82,4 @@ public class DesktopModeStatus {
return false;
}
}
-
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 8a9b74fd72b1..793bad86d873 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -68,7 +68,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
private void onInit() {
mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mShellTaskOrganizer.addFocusListener(this);
}
}
@@ -90,7 +90,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
t.apply();
}
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
if (taskInfo.isVisible) {
@@ -110,7 +110,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.removeFreeformTask(taskInfo.taskId);
if (repository.removeActiveTask(taskInfo.taskId)) {
@@ -134,7 +134,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
taskInfo.taskId);
mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
- if (DesktopModeStatus.IS_SUPPORTED) {
+ if (DesktopModeStatus.isAnyEnabled()) {
mDesktopModeTaskRepository.ifPresent(repository -> {
if (taskInfo.isVisible) {
if (repository.addActiveTask(taskInfo.taskId)) {
@@ -152,7 +152,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
"Freeform Task Focus Changed: #%d focused=%b",
taskInfo.taskId, taskInfo.isFocused);
- if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isFocused) {
+ if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
mDesktopModeTaskRepository.ifPresent(repository -> {
repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
});
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 56554020f3cc..afefd5dc6344 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
@@ -517,7 +517,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
- return DesktopModeStatus.IS_SUPPORTED
+ return DesktopModeStatus.isAnyEnabled()
&& taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index dad913300711..b3c9e238a614 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -100,7 +100,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Before
public void setUp() {
mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
- when(DesktopModeStatus.isSupported()).thenReturn(true);
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
@@ -129,7 +129,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Test
public void instantiate_flagOff_doNotAddInitCallback() {
- when(DesktopModeStatus.isSupported()).thenReturn(false);
+ when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
clearInvocations(mShellInit);
createController();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index a2eae2c9579a..2b7bcbee79fd 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -85,7 +85,8 @@ public class CameraBinderTest extends AndroidTestCase {
public void testCameraInfo() throws Exception {
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
- CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId);
+ CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
+ /*overrideToPortrait*/false);
assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
assertTrue("Orientation was not set for camera " + cameraId,
info.info.orientation != -1);
@@ -159,7 +160,8 @@ public class CameraBinderTest extends AndroidTestCase {
.connect(dummyCallbacks, cameraId, clientPackageName,
ICameraService.USE_CALLING_UID,
ICameraService.USE_CALLING_PID,
- getContext().getApplicationInfo().targetSdkVersion);
+ getContext().getApplicationInfo().targetSdkVersion,
+ /*overrideToPortrait*/false);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -264,7 +266,8 @@ public class CameraBinderTest extends AndroidTestCase {
dummyCallbacks, String.valueOf(cameraId),
clientPackageName, clientAttributionTag,
ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
- getContext().getApplicationInfo().targetSdkVersion);
+ getContext().getApplicationInfo().targetSdkVersion,
+ /*overrideToPortrait*/false);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 0890346db198..9d09dcc5c440 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -244,7 +244,8 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
- /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion);
+ /*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
+ /*overrideToPortrait*/false);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
@@ -417,7 +418,7 @@ public class CameraDeviceBinderTest extends AndroidTestCase {
@SmallTest
public void testCameraCharacteristics() throws RemoteException {
CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
- getContext().getApplicationInfo().targetSdkVersion);
+ getContext().getApplicationInfo().targetSdkVersion, /*overrideToPortrait*/false);
assertFalse(info.isEmpty());
assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index c9ea79432360..5883b6c0e723 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -87,8 +87,8 @@ public class PreviewPositionHelper {
taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
? mSplitBounds.topTaskPercent
: (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
- // Scale portrait height to that of (actual screen - taskbar inset)
- fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+ // Scale portrait height to that of the actual screen
+ fullscreenTaskHeight = screenHeightPx * taskPercent;
if (mTaskbarInApp) {
canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 5a81bd3e01b6..419cf1faf1fd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -96,6 +96,7 @@ import java.util.Set;
import java.util.concurrent.Executor;
import javax.inject.Inject;
+import javax.inject.Provider;
import kotlin.Unit;
@@ -714,7 +715,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
- @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
+ @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
@NonNull @BiometricsBackground Executor biometricsExecutor,
@NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
@NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
@@ -746,7 +747,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
- mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+ mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null);
mSensorProps = new FingerprintSensorPropertiesInternal(
-1 /* sensorId */,
SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
index 77d0496e43db..27466d4f58bc 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,7 +19,7 @@ package com.android.systemui.controls.dagger
import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 9ae605e30a83..6d6410de1a91 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,8 +20,8 @@ import android.app.Activity
import android.content.pm.PackageManager
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsMetricsLoggerImpl
-import com.android.systemui.controls.ControlsSettingsRepository
-import com.android.systemui.controls.ControlsSettingsRepositoryImpl
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
import com.android.systemui.controls.ui.ControlsActivity
@@ -90,6 +92,11 @@ abstract class ControlsModule {
): ControlsSettingsRepository
@Binds
+ abstract fun provideDialogManager(
+ manager: ControlsSettingsDialogManagerImpl
+ ): ControlsSettingsDialogManager
+
+ @Binds
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
new file mode 100644
index 000000000000..bb2e2d701aa0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.settings
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+
+/**
+ * Manager to display a dialog to prompt user to enable controls related Settings:
+ *
+ * * [Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]
+ * * [Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS]
+ */
+interface ControlsSettingsDialogManager {
+
+ /**
+ * Shows the corresponding dialog. In order for a dialog to appear, the following must be true
+ *
+ * * At least one of the Settings in [ControlsSettingsRepository] are `false`.
+ * * The dialog has not been seen by the user too many times (as defined by
+ * [MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG]).
+ *
+ * When the dialogs are shown, the following outcomes are possible:
+ * * User cancels the dialog by clicking outside or going back: we register that the dialog was
+ * seen but the settings don't change.
+ * * User responds negatively to the dialog: we register that the user doesn't want to change
+ * the settings (dialog will not appear again) and the settings don't change.
+ * * User responds positively to the dialog: the settings are set to `true` and the dialog will
+ * not appear again.
+ * * SystemUI closes the dialogs (for example, the activity showing it is closed). In this case,
+ * we don't modify anything.
+ *
+ * Of those four scenarios, only the first three will cause [onAttemptCompleted] to be called.
+ * It will also be called if the dialogs are not shown.
+ */
+ fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit)
+
+ /**
+ * Closes the dialog without registering anything from the user. The state of the settings after
+ * this is called will be the same as before the dialogs were shown.
+ */
+ fun closeDialog()
+
+ companion object {
+ @VisibleForTesting internal const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+ @VisibleForTesting
+ internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
+ }
+}
+
+@SysUISingleton
+class ControlsSettingsDialogManagerImpl
+@VisibleForTesting
+internal constructor(
+ private val secureSettings: SecureSettings,
+ private val userFileManager: UserFileManager,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val userTracker: UserTracker,
+ private val activityStarter: ActivityStarter,
+ private val dialogProvider: (context: Context, theme: Int) -> AlertDialog
+) : ControlsSettingsDialogManager {
+
+ @Inject
+ constructor(
+ secureSettings: SecureSettings,
+ userFileManager: UserFileManager,
+ controlsSettingsRepository: ControlsSettingsRepository,
+ userTracker: UserTracker,
+ activityStarter: ActivityStarter
+ ) : this(
+ secureSettings,
+ userFileManager,
+ controlsSettingsRepository,
+ userTracker,
+ activityStarter,
+ { context, theme -> SettingsDialog(context, theme) }
+ )
+
+ private var dialog: AlertDialog? = null
+ private set
+
+ private val showDeviceControlsInLockscreen: Boolean
+ get() = controlsSettingsRepository.canShowControlsInLockscreen.value
+
+ private val allowTrivialControls: Boolean
+ get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+
+ override fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit) {
+ closeDialog()
+
+ val prefs =
+ userFileManager.getSharedPreferences(
+ DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+ MODE_PRIVATE,
+ userTracker.userId
+ )
+ val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
+ if (
+ attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
+ (showDeviceControlsInLockscreen && allowTrivialControls)
+ ) {
+ onAttemptCompleted()
+ return
+ }
+
+ val listener = DialogListener(prefs, attempts, onAttemptCompleted)
+ val d =
+ dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
+ setIcon(R.drawable.ic_warning)
+ setOnCancelListener(listener)
+ setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
+ setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
+ if (showDeviceControlsInLockscreen) {
+ setTitle(R.string.controls_settings_trivial_controls_dialog_title)
+ setMessage(R.string.controls_settings_trivial_controls_dialog_message)
+ } else {
+ setTitle(R.string.controls_settings_show_controls_dialog_title)
+ setMessage(R.string.controls_settings_show_controls_dialog_message)
+ }
+ }
+
+ SystemUIDialog.registerDismissListener(d) { dialog = null }
+ SystemUIDialog.setDialogSize(d)
+ SystemUIDialog.setShowForAllUsers(d, true)
+ dialog = d
+ d.show()
+ }
+
+ private fun turnOnSettingSecurely(settings: List<String>) {
+ val action =
+ ActivityStarter.OnDismissAction {
+ settings.forEach { setting ->
+ secureSettings.putIntForUser(setting, 1, userTracker.userId)
+ }
+ true
+ }
+ activityStarter.dismissKeyguardThenExecute(
+ action,
+ /* cancel */ null,
+ /* afterKeyguardGone */ true
+ )
+ }
+
+ override fun closeDialog() {
+ dialog?.dismiss()
+ }
+
+ private inner class DialogListener(
+ private val prefs: SharedPreferences,
+ private val attempts: Int,
+ private val onComplete: () -> Unit
+ ) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ override fun onClick(dialog: DialogInterface?, which: Int) {
+ if (dialog == null) return
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ val settings = mutableListOf(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+ if (!showDeviceControlsInLockscreen) {
+ settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+ }
+ turnOnSettingSecurely(settings)
+ }
+ if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+ prefs
+ .edit()
+ .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ .apply()
+ }
+ onComplete()
+ }
+
+ override fun onCancel(dialog: DialogInterface?) {
+ if (dialog == null) return
+ if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+ prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1).apply()
+ }
+ onComplete()
+ }
+ }
+
+ private fun AlertDialog.setNeutralButton(
+ msgId: Int,
+ listener: DialogInterface.OnClickListener
+ ) {
+ setButton(DialogInterface.BUTTON_NEUTRAL, context.getText(msgId), listener)
+ }
+
+ private fun AlertDialog.setPositiveButton(
+ msgId: Int,
+ listener: DialogInterface.OnClickListener
+ ) {
+ setButton(DialogInterface.BUTTON_POSITIVE, context.getText(msgId), listener)
+ }
+
+ private fun AlertDialog.setMessage(msgId: Int) {
+ setMessage(context.getText(msgId))
+ }
+
+ /** This is necessary because the constructors are `protected`. */
+ private class SettingsDialog(context: Context, theme: Int) : AlertDialog(context, theme)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
index 3d10ab906f2f..df2831c6eb96 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.StateFlow
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 9dc422a09674..8e3b5109339c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import android.provider.Settings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 041ed1d557d7..99a10a33ab0f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -19,15 +19,12 @@ package com.android.systemui.controls.ui
import android.annotation.AnyThread
import android.annotation.MainThread
import android.app.Activity
-import android.app.AlertDialog
import android.app.Dialog
import android.app.PendingIntent
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.os.UserHandle
import android.os.VibrationEffect
-import android.provider.Settings.Secure
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
@@ -35,39 +32,36 @@ import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import java.util.Optional
import javax.inject.Inject
@SysUISingleton
class ControlActionCoordinatorImpl @Inject constructor(
- private val context: Context,
- private val bgExecutor: DelayableExecutor,
- @Main private val uiExecutor: DelayableExecutor,
- private val activityStarter: ActivityStarter,
- private val broadcastSender: BroadcastSender,
- private val keyguardStateController: KeyguardStateController,
- private val taskViewFactory: Optional<TaskViewFactory>,
- private val controlsMetricsLogger: ControlsMetricsLogger,
- private val vibrator: VibratorHelper,
- private val secureSettings: SecureSettings,
- private val userContextProvider: UserContextProvider,
- private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val context: Context,
+ private val bgExecutor: DelayableExecutor,
+ @Main private val uiExecutor: DelayableExecutor,
+ private val activityStarter: ActivityStarter,
+ private val broadcastSender: BroadcastSender,
+ private val keyguardStateController: KeyguardStateController,
+ private val taskViewFactory: Optional<TaskViewFactory>,
+ private val controlsMetricsLogger: ControlsMetricsLogger,
+ private val vibrator: VibratorHelper,
+ private val controlsSettingsRepository: ControlsSettingsRepository,
+ private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+ private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -76,16 +70,16 @@ class ControlActionCoordinatorImpl @Inject constructor(
get() = !keyguardStateController.isUnlocked()
private val allowTrivialControls: Boolean
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
- private val showDeviceControlsInLockscreen: Boolean
- get() = controlsSettingsRepository.canShowControlsInLockscreen.value
override lateinit var activityContext: Context
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
- private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
override fun closeDialogs() {
+ if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ controlsSettingsDialogManager.closeDialog()
+ }
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
if (isActivityFinishing == true) {
@@ -253,71 +247,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
if (action.authIsRequired) {
return
}
- val prefs = userContextProvider.userContext.getSharedPreferences(
- PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
- val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
- if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
- (showDeviceControlsInLockscreen && allowTrivialControls)) {
- return
+ if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+ controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
}
- val builder = AlertDialog
- .Builder(activityContext, R.style.Theme_SystemUI_Dialog)
- .setIcon(R.drawable.ic_warning)
- .setOnCancelListener {
- if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1)
- .commit()
- }
- true
- }
- .setNeutralButton(R.string.controls_settings_dialog_neutral_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- true
- }
-
- if (showDeviceControlsInLockscreen) {
- dialog = builder
- .setTitle(R.string.controls_settings_trivial_controls_dialog_title)
- .setMessage(R.string.controls_settings_trivial_controls_dialog_message)
- .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 1,
- UserHandle.USER_CURRENT)
- true
- }
- .create()
- } else {
- dialog = builder
- .setTitle(R.string.controls_settings_show_controls_dialog_title)
- .setMessage(R.string.controls_settings_show_controls_dialog_message)
- .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
- if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
- prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
- MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
- .commit()
- }
- secureSettings.putIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
- 1, UserHandle.USER_CURRENT)
- secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
- 1, UserHandle.USER_CURRENT)
- true
- }
- .create()
- }
-
- SystemUIDialog.registerDismissListener(dialog)
- SystemUIDialog.setDialogSize(dialog)
-
- dialog?.create()
- dialog?.show()
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index bd704c1ff086..5d611c4c8212 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -32,8 +32,10 @@ import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
/**
@@ -47,7 +49,9 @@ class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
- private val featureFlags: FeatureFlags
+ private val featureFlags: FeatureFlags,
+ private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+ private val keyguardStateController: KeyguardStateController
) : ComponentActivity() {
private lateinit var parent: ViewGroup
@@ -92,7 +96,13 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
- uiController.show(parent, { finishOrReturnToDream() }, this)
+ if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
+ controlsSettingsDialogManager.maybeShowDialog(this) {
+ uiController.show(parent, { finishOrReturnToDream() }, this)
+ }
+ } else {
+ uiController.show(parent, { finishOrReturnToDream() }, this)
+ }
ControlsAnimations.enterAnimation(parent).start()
}
@@ -124,6 +134,7 @@ class ControlsActivity @Inject constructor(
mExitToDream = false
uiController.hide()
+ controlsSettingsDialogManager.closeDialog()
}
override fun onDestroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index a07c716bc8f9..fb678aa420bf 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -49,7 +49,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6ed555056cb1..306e92e6c96c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2214,6 +2214,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
case START_KEYGUARD_EXIT_ANIM:
Trace.beginSection(
"KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM");
+ synchronized (KeyguardViewMediator.this) {
+ mHiding = true;
+ }
StartKeyguardExitAnimParams params = (StartKeyguardExitAnimParams) msg.obj;
mNotificationShadeWindowControllerLazy.get().batchApplyWindowLayoutParams(
() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 6c66f0bb1e47..341eb3b0425c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -68,7 +68,6 @@ public class DeviceControlsControllerImpl @Inject constructor(
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
const val PREFS_CONTROLS_FILE = "controls_prefs"
- internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
private const val SEEDING_MAX = 2
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index b267a5c23a49..a94f3427eebe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -110,6 +110,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import javax.inject.Provider;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@@ -261,6 +263,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
initUdfpsController(true /* hasAlternateTouchProvider */);
}
+
private void initUdfpsController(boolean hasAlternateTouchProvider) {
initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
}
@@ -270,8 +273,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
reset(mFingerprintManager);
reset(mScreenLifecycle);
- final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
- hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+ final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider =
+ hasAlternateTouchProvider ? Optional.of(
+ (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
+ : Optional.empty();
mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
@@ -1140,7 +1145,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
- public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath()
+ public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
throws RemoteException {
final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
0L);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 1d00d6b05568..16fb50c15af6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,21 +16,19 @@
package com.android.systemui.controls.ui
-import android.content.Context
-import android.content.SharedPreferences
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import org.junit.Before
import org.junit.Test
@@ -40,8 +38,8 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -71,9 +69,9 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
private lateinit var metricsLogger: ControlsMetricsLogger
@Mock
- private lateinit var secureSettings: SecureSettings
+ private lateinit var featureFlags: FeatureFlags
@Mock
- private lateinit var userContextProvider: UserContextProvider
+ private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -103,23 +101,16 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
taskViewFactory,
metricsLogger,
vibratorHelper,
- secureSettings,
- userContextProvider,
- controlsSettingsRepository
+ controlsSettingsRepository,
+ controlsSettingsDialogManager,
+ featureFlags
))
-
- val userContext = mock(Context::class.java)
- val pref = mock(SharedPreferences::class.java)
- `when`(userContextProvider.userContext).thenReturn(userContext)
- `when`(userContext.getSharedPreferences(
- DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE))
- .thenReturn(pref)
- // Just return 2 so we don't test any Dialog logic which requires a launched activity.
- `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
- .thenReturn(2)
+ coordinator.activityContext = mContext
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
+ `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
+
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
}
@@ -160,15 +151,31 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
`when`(keyguardStateController.isShowing()).thenReturn(true)
- `when`(keyguardStateController.isUnlocked()).thenReturn(false)
coordinator.toggle(cvh, "", true)
verify(coordinator).bouncerOrRun(action)
+ verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
verify(action).invoke()
}
@Test
+ fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
+ `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
+ action = spy(coordinator.Action(ID, {}, false, false))
+ doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
+
+ `when`(keyguardStateController.isShowing()).thenReturn(true)
+ `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+ doNothing().`when`(controlsSettingsDialogManager).maybeShowDialog(any(), any())
+
+ coordinator.toggle(cvh, "", true)
+
+ verify(coordinator).bouncerOrRun(action)
+ verify(controlsSettingsDialogManager, never()).maybeShowDialog(any(), any())
+ }
+
+ @Test
fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 48fc46b7e730..9144b13c7f3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -22,7 +22,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
new file mode 100644
index 000000000000..0c9986d82447
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.controls.settings
+
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.database.ContentObserver
+import android.provider.Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+import android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.TestableAlertDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsSettingsDialogManagerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val SETTING_SHOW = LOCKSCREEN_SHOW_CONTROLS
+ private const val SETTING_ACTION = LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+ private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+ }
+
+ @Mock private lateinit var userFileManager: UserFileManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var activityStarter: ActivityStarter
+ @Mock private lateinit var completedRunnable: () -> Unit
+
+ private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+ private lateinit var sharedPreferences: FakeSharedPreferences
+ private lateinit var secureSettings: FakeSettings
+
+ private lateinit var underTest: ControlsSettingsDialogManagerImpl
+
+ private var dialog: TestableAlertDialog? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controlsSettingsRepository = FakeControlsSettingsRepository()
+ sharedPreferences = FakeSharedPreferences()
+ secureSettings = FakeSettings()
+
+ `when`(userTracker.userId).thenReturn(0)
+ secureSettings.userId = userTracker.userId
+ `when`(
+ userFileManager.getSharedPreferences(
+ eq(DeviceControlsControllerImpl.PREFS_CONTROLS_FILE),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenReturn(sharedPreferences)
+
+ `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+ .thenAnswer { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
+
+ attachRepositoryToSettings()
+ underTest =
+ ControlsSettingsDialogManagerImpl(
+ secureSettings,
+ userFileManager,
+ controlsSettingsRepository,
+ userTracker,
+ activityStarter
+ ) { context, _ -> TestableAlertDialog(context).also { dialog = it } }
+ }
+
+ @After
+ fun tearDown() {
+ underTest.closeDialog()
+ }
+
+ @Test
+ fun dialogNotShownIfPrefsAtMaximum() {
+ sharedPreferences.putAttempts(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogNotShownIfSettingsAreTrue() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, true)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogShownIfAllowTrivialControlsFalse() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+
+ assertThat(dialog?.isShowing ?: false).isTrue()
+ }
+
+ @Test
+ fun dialogDispossedAfterClosing() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ underTest.closeDialog()
+
+ assertThat(dialog?.isShowing ?: false).isFalse()
+ }
+
+ @Test
+ fun dialogNeutralButtonDoesntChangeSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+ }
+
+ @Test
+ fun dialogNeutralButtonPutsMaxAttempts() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ }
+
+ @Test
+ fun dialogNeutralButtonCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogPositiveButtonChangesSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue()
+ }
+
+ @Test
+ fun dialogPositiveButtonPutsMaxAttempts() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+ }
+
+ @Test
+ fun dialogPositiveButtonCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun dialogCancelDoesntChangeSetting() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+ }
+
+ @Test
+ fun dialogCancelPutsOneExtraAttempt() {
+ val attempts = 0
+ sharedPreferences.putAttempts(attempts)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+ .isEqualTo(attempts + 1)
+ }
+
+ @Test
+ fun dialogCancelCallsOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ dialog?.cancel()
+
+ verify(completedRunnable).invoke()
+ }
+
+ @Test
+ fun closeDialogDoesNotCallOnComplete() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, true)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ underTest.closeDialog()
+
+ verify(completedRunnable, never()).invoke()
+ }
+
+ @Test
+ fun dialogPositiveWithBothSettingsFalseTogglesBothSettings() {
+ sharedPreferences.putAttempts(0)
+ secureSettings.putBool(SETTING_SHOW, false)
+ secureSettings.putBool(SETTING_ACTION, false)
+
+ underTest.maybeShowDialog(context, completedRunnable)
+ clickButton(DialogInterface.BUTTON_POSITIVE)
+
+ assertThat(secureSettings.getBool(SETTING_SHOW)).isTrue()
+ assertThat(secureSettings.getBool(SETTING_ACTION)).isTrue()
+ }
+
+ private fun clickButton(which: Int) {
+ dialog?.clickButton(which)
+ }
+
+ private fun attachRepositoryToSettings() {
+ secureSettings.registerContentObserver(
+ SETTING_SHOW,
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ controlsSettingsRepository.setCanShowControlsInLockscreen(
+ secureSettings.getBool(SETTING_SHOW, false)
+ )
+ }
+ }
+ )
+
+ secureSettings.registerContentObserver(
+ SETTING_ACTION,
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(
+ secureSettings.getBool(SETTING_ACTION, false)
+ )
+ }
+ }
+ )
+ }
+
+ private fun SharedPreferences.putAttempts(value: Int) {
+ edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, value).commit()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
index 4b88b44c3f03..b904ac14e707 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import android.content.pm.UserInfo
import android.provider.Settings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
index 8a1bed20e700..b6628db14235 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
*
*/
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index d965e337f47a..779788aa0075 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -35,10 +35,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
-import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index d17e3744edc6..798839dcc1f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
@@ -34,6 +35,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.os.PowerManager;
@@ -41,6 +43,11 @@ import android.os.PowerManager.WakeLock;
import android.telephony.TelephonyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.RemoteAnimationTarget;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -52,21 +59,27 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.DeviceConfigProxy;
@@ -80,8 +93,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import dagger.Lazy;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -96,11 +107,15 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
+ private @Mock WindowManager mWindowManager;
+ private @Mock IActivityManager mActivityManager;
+ private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
private @Mock TrustManager mTrustManager;
private @Mock UserSwitcherController mUserSwitcherController;
private @Mock NavigationModeController mNavigationModeController;
private @Mock KeyguardDisplayManager mKeyguardDisplayManager;
+ private @Mock KeyguardBypassController mKeyguardBypassController;
private @Mock DozeParameters mDozeParameters;
private @Mock SysuiStatusBarStateController mStatusBarStateController;
private @Mock KeyguardStateController mKeyguardStateController;
@@ -110,10 +125,13 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock InteractionJankMonitor mInteractionJankMonitor;
private @Mock ScreenOnCoordinator mScreenOnCoordinator;
private @Mock ShadeController mShadeController;
- private @Mock Lazy<NotificationShadeWindowController> mNotificationShadeWindowControllerLazy;
+ private NotificationShadeWindowController mNotificationShadeWindowController;
private @Mock DreamOverlayStateController mDreamOverlayStateController;
private @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
private @Mock ScrimController mScrimController;
+ private @Mock SysuiColorExtractor mColorExtractor;
+ private @Mock AuthController mAuthController;
+ private @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
private DeviceConfigProxy mDeviceConfig = new DeviceConfigProxyFake();
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
@@ -130,6 +148,14 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
when(mPowerManager.newWakeLock(anyInt(), any())).thenReturn(mock(WakeLock.class));
when(mInteractionJankMonitor.begin(any(), anyInt())).thenReturn(true);
when(mInteractionJankMonitor.end(anyInt())).thenReturn(true);
+ final ViewRootImpl testViewRoot = mock(ViewRootImpl.class);
+ when(testViewRoot.getView()).thenReturn(mock(View.class));
+ when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot);
+ mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
+ mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
+ mConfigurationController, mViewMediator, mKeyguardBypassController,
+ mColorExtractor, mDumpManager, mKeyguardStateController,
+ mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager);
createAndStartViewMediator();
}
@@ -287,6 +313,23 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
verify(mCentralSurfaces).updateIsKeyguard();
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+ RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+
+ mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+ null, callback);
+ TestableLooper.get(this).processAllMessages();
+ assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -315,7 +358,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mInteractionJankMonitor,
mDreamOverlayStateController,
() -> mShadeController,
- mNotificationShadeWindowControllerLazy,
+ () -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
() -> mScrimController);
mViewMediator.start();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dc1d50c435b9..cd777092d438 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1891,7 +1891,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// actions.
taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
ownerActivity.getUid(), ownerActivity.info.processName);
- ownerTask.addChild(taskFragment, POSITION_TOP);
+ final int position;
+ if (creationParams.getPairedPrimaryFragmentToken() != null) {
+ // When there is a paired primary TaskFragment, we want to place the new TaskFragment
+ // right above the paired one to make sure there is no other window in between.
+ final TaskFragment pairedPrimaryTaskFragment = getTaskFragment(
+ creationParams.getPairedPrimaryFragmentToken());
+ final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
+ position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
+ } else {
+ position = POSITION_TOP;
+ }
+ ownerTask.addChild(taskFragment, position);
taskFragment.setWindowingMode(creationParams.getWindowingMode());
taskFragment.setBounds(creationParams.getInitialBounds());
mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2b493145f854..6cb3450ee470 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -552,10 +552,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
@Test
public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
- final IBinder fragmentToken = new Binder();
// Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
- createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+ createTaskFragmentFromOrganizer(mTransaction, ownerActivity, mFragmentToken);
mTransaction.startActivityInTaskFragment(
mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -564,7 +563,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
assertApplyTransactionAllowed(mTransaction);
// Successfully created a TaskFragment.
- final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ mFragmentToken);
assertNotNull(taskFragment);
assertEquals(ownerActivity.getTask(), taskFragment.getTask());
}
@@ -703,6 +703,40 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
}
@Test
+ public void testApplyTransaction_createTaskFragment_withPairedPrimaryFragmentToken() {
+ final Task task = createTask(mDisplayContent);
+ mTaskFragment = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .setFragmentToken(mFragmentToken)
+ .createActivityCount(1)
+ .build();
+ mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+ final ActivityRecord activityOnTop = createActivityRecord(task);
+ final int uid = Binder.getCallingUid();
+ activityOnTop.info.applicationInfo.uid = uid;
+ activityOnTop.getTask().effectiveUid = uid;
+ final IBinder fragmentToken1 = new Binder();
+ final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+ mOrganizerToken, fragmentToken1, activityOnTop.token)
+ .setPairedPrimaryFragmentToken(mFragmentToken)
+ .build();
+ mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+ mTransaction.createTaskFragment(params);
+ assertApplyTransactionAllowed(mTransaction);
+
+ // Successfully created a TaskFragment.
+ final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+ fragmentToken1);
+ assertNotNull(taskFragment);
+ // The new TaskFragment should be positioned right above the paired TaskFragment.
+ assertEquals(task.mChildren.indexOf(mTaskFragment) + 1,
+ task.mChildren.indexOf(taskFragment));
+ // The top activity should remain on top.
+ assertEquals(task.mChildren.indexOf(taskFragment) + 1,
+ task.mChildren.indexOf(activityOnTop));
+ }
+
+ @Test
public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
doReturn(true).when(mTaskFragment).isAttached();